gpx 0.9.0 → 1.1.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.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +36 -0
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +162 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +6 -5
  7. data/CHANGELOG.md +15 -0
  8. data/Gemfile +2 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +20 -5
  11. data/Rakefile +22 -12
  12. data/bin/gpx_distance +5 -6
  13. data/bin/gpx_smooth +25 -26
  14. data/gpx.gemspec +13 -12
  15. data/lib/gpx/bounds.rb +13 -31
  16. data/lib/gpx/geo_json.rb +199 -0
  17. data/lib/gpx/gpx.rb +4 -26
  18. data/lib/gpx/gpx_file.rb +140 -134
  19. data/lib/gpx/magellan_track_log.rb +34 -66
  20. data/lib/gpx/point.rb +22 -35
  21. data/lib/gpx/route.rb +10 -31
  22. data/lib/gpx/segment.rb +63 -90
  23. data/lib/gpx/track.rb +38 -42
  24. data/lib/gpx/track_point.rb +32 -0
  25. data/lib/gpx/version.rb +3 -1
  26. data/lib/gpx/waypoint.rb +10 -34
  27. data/lib/gpx.rb +13 -34
  28. data/tests/geojson_files/combined_data.json +68 -0
  29. data/tests/geojson_files/line_string_data.json +83 -0
  30. data/tests/geojson_files/multi_line_string_data.json +74 -0
  31. data/tests/geojson_files/multi_point_data.json +14 -0
  32. data/tests/geojson_files/point_data.json +22 -0
  33. data/tests/geojson_test.rb +92 -0
  34. data/tests/gpx10_test.rb +7 -6
  35. data/tests/gpx_file_test.rb +31 -19
  36. data/tests/gpx_files/one_segment_mixed_times.gpx +884 -0
  37. data/tests/gpx_files/routes_without_names.gpx +29 -0
  38. data/tests/gpx_files/with_empty_tracks.gpx +72 -0
  39. data/tests/magellan_test.rb +12 -11
  40. data/tests/output_test.rb +93 -94
  41. data/tests/route_test.rb +75 -30
  42. data/tests/segment_test.rb +104 -93
  43. data/tests/track_file_test.rb +50 -70
  44. data/tests/track_point_test.rb +22 -11
  45. data/tests/track_test.rb +73 -61
  46. data/tests/waypoint_test.rb +46 -48
  47. metadata +47 -18
  48. data/lib/gpx/trackpoint.rb +0 -60
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module GPX
6
+ # Class to parse GeoJSON LineStrings, MultiLineStrings, Points,
7
+ # and MultiPoint geometric objects to GPX format. For the full
8
+ # specification of GeoJSON, see:
9
+ # http://geojson.org/geojson-spec.html
10
+ # Note that GeoJSON coordinates are specified in lon/lat format,
11
+ # instead of the more traditional lat/lon format.
12
+ #
13
+ class GeoJSON
14
+ class << self
15
+ FEATURE = 'Feature'
16
+ LINESTRING = 'LineString'
17
+ MULTILINESTRING = 'MultiLineString'
18
+ POINT = 'Point'
19
+ MULTIPOINT = 'MultiPoint'
20
+
21
+ # Conversion can be initiated by either specifying a file,
22
+ # file name, or by passing in GeoJSON data as a string.
23
+ # Examples:
24
+ # GPX::GeoJSON.convert_to_gpx(geojson_file: 'mygeojsonfile.json')
25
+ # or
26
+ # file = File.new('mygeojsonfile.json', 'r')
27
+ # GPX::GeoJSON.convert_to_gpx(geojson_file: file)
28
+ # or
29
+ # data = JSON.generate(my_geojson_hash)
30
+ # GPX::GeoJSON.convert_to_gpx(geojson_data: data)
31
+ #
32
+ # Returns a GPX::GPX_File object populated with the converted data.
33
+ #
34
+ def convert_to_gpx(opts = {})
35
+ geojson = geojson_data_from(opts)
36
+ gpx_file = GPX::GPXFile.new
37
+ add_tracks_to(gpx_file, geojson)
38
+ add_waypoints_to(gpx_file, geojson)
39
+ gpx_file
40
+ end
41
+
42
+ private
43
+
44
+ def geojson_data_from(opts)
45
+ if opts[:geojson_file]
46
+ parse_geojson_data_from_file(opts[:geojson_file])
47
+ elsif opts[:geojson_data]
48
+ parse_geojson_data(opts[:geojson_data])
49
+ else
50
+ raise ArgumentError,
51
+ 'Must pass value for \':geojson_file\' ' \
52
+ 'or \':geojson_data\' to convert_to_gpx'
53
+ end
54
+ end
55
+
56
+ def parse_geojson_data_from_file(filename)
57
+ parse_geojson_data(IO.read(filename))
58
+ end
59
+
60
+ def parse_geojson_data(data)
61
+ JSON.parse(data)
62
+ end
63
+
64
+ def add_tracks_to(gpx_file, geojson)
65
+ tracks = [line_strings_to_track(geojson)] +
66
+ multi_line_strings_to_tracks(geojson)
67
+ tracks.compact!
68
+ gpx_file.tracks += tracks
69
+ gpx_file.tracks.each { |t| gpx_file.update_meta_data(t) }
70
+ end
71
+
72
+ def add_waypoints_to(gpx_file, geojson)
73
+ gpx_file.waypoints +=
74
+ points_to_waypoints(geojson, gpx_file) +
75
+ multi_points_to_waypoints(geojson, gpx_file)
76
+ end
77
+
78
+ # Converts GeoJSON 'LineString' features.
79
+ # Current strategy is to convert each LineString into a
80
+ # Track Segment, returning a Track for all LineStrings.
81
+ #
82
+ def line_strings_to_track(geojson)
83
+ line_strings = line_strings_in(geojson)
84
+ return nil unless line_strings.any?
85
+
86
+ track = GPX::Track.new
87
+ line_strings.each do |ls|
88
+ coords = ls['geometry']['coordinates']
89
+ track.append_segment(coords_to_segment(coords))
90
+ end
91
+ track
92
+ end
93
+
94
+ # Converts GeoJSON 'MultiLineString' features.
95
+ # Current strategy is to convert each MultiLineString
96
+ # into a Track, with each set of LineString coordinates
97
+ # within a MultiLineString a Track Segment.
98
+ #
99
+ def multi_line_strings_to_tracks(geojson)
100
+ tracks = []
101
+ multi_line_strings_in(geojson).each do |mls|
102
+ track = GPX::Track.new
103
+ mls['geometry']['coordinates'].each do |coords|
104
+ seg = coords_to_segment(coords)
105
+ seg.track = track
106
+ track.append_segment(seg)
107
+ end
108
+ tracks << track
109
+ end
110
+ tracks
111
+ end
112
+
113
+ # Converts GeoJSON 'Point' features.
114
+ # Current strategy is to convert each Point
115
+ # feature into a GPX waypoint.
116
+ #
117
+ def points_to_waypoints(geojson, gpx_file)
118
+ points_in(geojson).reduce([]) do |acc, pt|
119
+ coords = pt['geometry']['coordinates']
120
+ acc << point_to_waypoint(coords, gpx_file)
121
+ end
122
+ end
123
+
124
+ # Converts GeoJSON 'MultiPoint' features.
125
+ # Current strategy is to convert each coordinate
126
+ # point in a MultiPoint to a GPX waypoint.
127
+ #
128
+ # NOTE: It is debatable that a MultiPoint feature
129
+ # might translate best into a GPX route, which is
130
+ # described as
131
+ # "an ordered list of waypoints representing a
132
+ # series of turn points leading to a destination."
133
+ # See http://www.topografix.com/gpx/1/1/#type_rteType
134
+ #
135
+ def multi_points_to_waypoints(geojson, gpx_file)
136
+ multi_points_in(geojson).reduce([]) do |acc, mpt|
137
+ mpt['geometry']['coordinates'].each do |coords|
138
+ acc << point_to_waypoint(coords, gpx_file)
139
+ end
140
+ end
141
+ end
142
+
143
+ # Given an array of [lng, lat, ele] coordinates,
144
+ # return a GPX track segment.
145
+ #
146
+ def coords_to_segment(coords)
147
+ seg = GPX::Segment.new
148
+ coords.each do |pt|
149
+ seg.append_point(point_to_track_point(pt, seg))
150
+ end
151
+ seg
152
+ end
153
+
154
+ # Given a GeoJSON coordinate point, return
155
+ # a GPX::Waypoint
156
+ def point_to_waypoint(point, gpx_file)
157
+ GPX::Waypoint.new(gpx_file: gpx_file,
158
+ lon: point[0],
159
+ lat: point[1],
160
+ elevation: point[2])
161
+ end
162
+
163
+ # Given a GeoJSON coorindate point, and
164
+ # GPX segment, return a GPX::TrackPoint.
165
+ #
166
+ def point_to_track_point(point, seg)
167
+ GPX::TrackPoint.new(segment: seg,
168
+ lon: point[0],
169
+ lat: point[1],
170
+ elevation: point[2])
171
+ end
172
+
173
+ # Returns all features in the passed geojson
174
+ # that match the type.
175
+ #
176
+ def features_for(geojson, type)
177
+ geojson['features'].find_all do |f|
178
+ f['type'] == FEATURE && f['geometry']['type'] == type
179
+ end
180
+ end
181
+
182
+ def points_in(geojson)
183
+ features_for(geojson, POINT)
184
+ end
185
+
186
+ def multi_points_in(geojson)
187
+ features_for(geojson, MULTIPOINT)
188
+ end
189
+
190
+ def line_strings_in(geojson)
191
+ features_for(geojson, LINESTRING)
192
+ end
193
+
194
+ def multi_line_strings_in(geojson)
195
+ features_for(geojson, MULTILINESTRING)
196
+ end
197
+ end
198
+ end
199
+ end
data/lib/gpx/gpx.rb CHANGED
@@ -1,25 +1,5 @@
1
- #--
2
- # Copyright (c) 2006 Doug Fales
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
1
+ # frozen_string_literal: true
2
+
23
3
  module GPX
24
4
  # A common base class which provides a useful initializer method to many
25
5
  # class in the GPX library.
@@ -33,14 +13,12 @@ module GPX
33
13
  # attributes to this method.
34
14
  def instantiate_with_text_elements(parent, text_elements)
35
15
  text_elements.each do |el|
36
- child_xpath = "#{el}"
16
+ child_xpath = el.to_s
37
17
  unless parent.at(child_xpath).nil?
38
18
  val = parent.at(child_xpath).inner_text
39
- self.send("#{el}=", val)
19
+ send("#{el}=", val)
40
20
  end
41
21
  end
42
-
43
22
  end
44
-
45
23
  end
46
24
  end
data/lib/gpx/gpx_file.rb CHANGED
@@ -1,30 +1,11 @@
1
- #--
2
- # Copyright (c) 2006 Doug Fales
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
1
+ # frozen_string_literal: true
2
+
23
3
  module GPX
24
4
  class GPXFile < Base
25
- attr_accessor :tracks, :routes, :waypoints, :bounds, :lowest_point, :highest_point, :duration, :ns, :time, :name, :version, :creator, :description, :moving_duration
5
+ attr_accessor :tracks,
6
+ :routes, :waypoints, :bounds, :lowest_point, :highest_point, :duration, :ns, :time, :name, :version, :creator, :description, :moving_duration
26
7
 
27
- DEFAULT_CREATOR = "GPX RubyGem #{GPX::VERSION} -- http://dougfales.github.io/gpx/".freeze
8
+ DEFAULT_CREATOR = "GPX RubyGem #{GPX::VERSION} -- http://dougfales.github.io/gpx/"
28
9
 
29
10
  # This initializer can be used to create a new GPXFile from an existing
30
11
  # file or to create a new GPXFile instance with no data (so that you can
@@ -44,10 +25,14 @@ module GPX
44
25
  # gpx_file = GPXFile.new(:tracks => [some_track])
45
26
  #
46
27
  def initialize(opts = {})
28
+ super()
47
29
  @duration = 0
48
30
  @attributes = {}
49
31
  @namespace_defs = []
50
- if(opts[:gpx_file] or opts[:gpx_data])
32
+ @tracks = []
33
+ @time = nil
34
+
35
+ if opts[:gpx_file] || opts[:gpx_data]
51
36
  if opts[:gpx_file]
52
37
  gpx_file = opts[:gpx_file]
53
38
  gpx_file = File.open(gpx_file) unless gpx_file.is_a?(File)
@@ -59,41 +44,54 @@ module GPX
59
44
  gpx_element = @xml.at('gpx')
60
45
  @attributes = gpx_element.attributes
61
46
  @namespace_defs = gpx_element.namespace_definitions
62
- #$stderr.puts gpx_element.attributes.sort.inspect
63
- #$stderr.puts @xmlns.inspect
64
- #$stderr.puts @xsi.inspect
65
47
  @version = gpx_element['version']
66
48
  reset_meta_data
67
- bounds_element = (@xml.at("metadata/bounds") rescue nil)
49
+ bounds_element = (
50
+ begin
51
+ @xml.at('metadata/bounds')
52
+ rescue StandardError
53
+ nil
54
+ end)
68
55
  if bounds_element
69
- @bounds.min_lat = get_bounds_attr_value(bounds_element, %w{ min_lat minlat minLat })
70
- @bounds.min_lon = get_bounds_attr_value(bounds_element, %w{ min_lon minlon minLon})
71
- @bounds.max_lat = get_bounds_attr_value(bounds_element, %w{ max_lat maxlat maxLat})
72
- @bounds.max_lon = get_bounds_attr_value(bounds_element, %w{ max_lon maxlon maxLon})
56
+ @bounds.min_lat = get_bounds_attr_value(bounds_element, %w[min_lat minlat minLat])
57
+ @bounds.min_lon = get_bounds_attr_value(bounds_element, %w[min_lon minlon minLon])
58
+ @bounds.max_lat = get_bounds_attr_value(bounds_element, %w[max_lat maxlat maxLat])
59
+ @bounds.max_lon = get_bounds_attr_value(bounds_element, %w[max_lon maxlon maxLon])
73
60
  else
74
61
  get_bounds = true
75
62
  end
76
63
 
77
- @time = Time.parse(@xml.at("metadata/time").inner_text) rescue nil
78
- @name = @xml.at("metadata/name").inner_text rescue nil
79
- @description = @xml.at('metadata/desc').inner_text rescue nil
80
- @tracks = []
81
- @xml.search("trk").each do |trk|
82
- trk = Track.new(:element => trk, :gpx_file => self)
64
+ @time = begin
65
+ Time.parse(@xml.at('metadata/time').inner_text)
66
+ rescue StandardError
67
+ nil
68
+ end
69
+ @name = begin
70
+ @xml.at('metadata/name').inner_text
71
+ rescue StandardError
72
+ nil
73
+ end
74
+ @description = begin
75
+ @xml.at('metadata/desc').inner_text
76
+ rescue StandardError
77
+ nil
78
+ end
79
+ @xml.search('trk').each do |trk|
80
+ trk = Track.new(element: trk, gpx_file: self)
83
81
  update_meta_data(trk, get_bounds)
84
82
  @tracks << trk
85
83
  end
86
84
  @waypoints = []
87
- @xml.search("wpt").each { |wpt| @waypoints << Waypoint.new(:element => wpt, :gpx_file => self) }
85
+ @xml.search('wpt').each { |wpt| @waypoints << Waypoint.new(element: wpt, gpx_file: self) }
88
86
  @routes = []
89
- @xml.search("rte").each { |rte| @routes << Route.new(:element => rte, :gpx_file => self) }
90
- @tracks.delete_if { |t| t.empty? }
87
+ @xml.search('rte').each { |rte| @routes << Route.new(element: rte, gpx_file: self) }
88
+ @tracks.delete_if(&:empty?)
91
89
 
92
90
  calculate_duration
93
91
  else
94
92
  reset_meta_data
95
- opts.each { |attr_name, value| instance_variable_set("@#{attr_name.to_s}", value) }
96
- unless(@tracks.nil? or @tracks.size.zero?)
93
+ opts.each { |attr_name, value| instance_variable_set("@#{attr_name}", value) }
94
+ unless @tracks.nil? || @tracks.empty?
97
95
  @tracks.each { |trk| update_meta_data(trk) }
98
96
  calculate_duration
99
97
  end
@@ -109,19 +107,24 @@ module GPX
109
107
  result = el[name]
110
108
  break unless result.nil?
111
109
  end
112
- return (result.to_f rescue nil)
110
+ (
111
+ begin
112
+ result.to_f
113
+ rescue StandardError
114
+ nil
115
+ end)
113
116
  end
114
117
 
115
118
  # Returns the distance, in kilometers, meters, or miles, of all of the
116
119
  # tracks and segments contained in this GPXFile.
117
- def distance(opts = { :units => 'kilometers' })
120
+ def distance(opts = { units: 'kilometers' })
118
121
  case opts[:units]
119
122
  when /kilometers/i
120
- return @distance
123
+ @distance
121
124
  when /meters/i
122
- return (@distance * 1000)
125
+ (@distance * 1000)
123
126
  when /miles/i
124
- return (@distance * 0.62)
127
+ (@distance * 0.62)
125
128
  end
126
129
  end
127
130
 
@@ -129,14 +132,14 @@ module GPX
129
132
  # GPXFile. The calculation is based on the total distance divided by the
130
133
  # sum of duration of all segments of all tracks
131
134
  # (not taking into accounting pause time).
132
- def average_speed(opts = { :units => 'kilometers' })
135
+ def average_speed(opts = { units: 'kilometers' })
133
136
  case opts[:units]
134
137
  when /kilometers/i
135
- return distance / (moving_duration/3600.0)
138
+ distance / (moving_duration / 3600.0)
136
139
  when /meters/i
137
- return (distance * 1000) / (moving_duration/3600.0)
140
+ (distance * 1000) / (moving_duration / 3600.0)
138
141
  when /miles/i
139
- return (distance * 0.62) / (moving_duration/3600.0)
142
+ (distance * 0.62) / (moving_duration / 3600.0)
140
143
  end
141
144
  end
142
145
 
@@ -176,7 +179,7 @@ module GPX
176
179
  keep_tracks << trk
177
180
  end
178
181
  end
179
- @tracks = keep_tracks
182
+ @tracks = keep_tracks
180
183
  routes.each { |rte| rte.delete_area(area) }
181
184
  waypoints.each { |wpt| wpt.delete_area(area) }
182
185
  end
@@ -191,13 +194,15 @@ module GPX
191
194
  @moving_duration = 0.0
192
195
  end
193
196
 
197
+ # rubocop:disable Style/OptionalBooleanParameter
198
+
194
199
  # Updates the meta data for this GPX file. Meta data includes the
195
200
  # bounds, the high and low points, and the distance. This is useful when
196
201
  # you modify the GPX data (i.e. by adding or deleting points) and you
197
202
  # want the meta data to accurately reflect the new data.
198
203
  def update_meta_data(trk, get_bounds = true)
199
- @lowest_point = trk.lowest_point if(@lowest_point.nil? or (!trk.lowest_point.nil? and trk.lowest_point.elevation < @lowest_point.elevation))
200
- @highest_point = trk.highest_point if(@highest_point.nil? or (!trk.highest_point.nil? and trk.highest_point.elevation > @highest_point.elevation))
204
+ @lowest_point = trk.lowest_point if @lowest_point.nil? || (!trk.lowest_point.nil? && (trk.lowest_point.elevation < @lowest_point.elevation))
205
+ @highest_point = trk.highest_point if @highest_point.nil? || (!trk.highest_point.nil? && (trk.highest_point.elevation > @highest_point.elevation))
201
206
  @bounds.add(trk.bounds) if get_bounds
202
207
  @distance += trk.distance
203
208
  @moving_duration += trk.moving_duration
@@ -206,17 +211,18 @@ module GPX
206
211
  # Serialize the current GPXFile to a gpx file named <filename>.
207
212
  # If the file does not exist, it is created. If it does exist, it is overwritten.
208
213
  def write(filename, update_time = true)
209
- @time = Time.now if(@time.nil? or update_time)
214
+ @time = Time.now if @time.nil? || update_time
210
215
  @name ||= File.basename(filename)
211
216
  doc = generate_xml_doc
212
217
  File.open(filename, 'w+') { |f| f.write(doc.to_xml) }
213
218
  end
214
219
 
215
220
  def to_s(update_time = true)
216
- @time = Time.now if(@time.nil? or update_time)
221
+ @time = Time.now if @time.nil? || update_time
217
222
  doc = generate_xml_doc
218
223
  doc.to_xml
219
224
  end
225
+ # rubocop:enable Style/OptionalBooleanParameter
220
226
 
221
227
  def inspect
222
228
  "<#{self.class.name}:...>"
@@ -231,117 +237,117 @@ module GPX
231
237
  end
232
238
 
233
239
  private
240
+
234
241
  def attributes_and_nsdefs_as_gpx_attributes
235
- #$stderr.puts @namespace_defs.inspect
242
+ # $stderr.puts @namespace_defs.inspect
236
243
  gpx_header = {}
237
- @attributes.each do |k,v|
238
- k = v.namespace.prefix + ':' + k if v.namespace
244
+ @attributes.each do |k, v|
245
+ k = "#{v.namespace.prefix}:#{k}" if v.namespace
239
246
  gpx_header[k] = v.value
240
- end
247
+ end
241
248
 
242
249
  @namespace_defs.each do |nsd|
243
250
  tag = 'xmlns'
244
- if nsd.prefix
245
- tag += ':' + nsd.prefix
246
- end
251
+ tag += ":#{nsd.prefix}" if nsd.prefix
247
252
  gpx_header[tag] = nsd.href
248
253
  end
249
- return gpx_header
254
+ gpx_header
250
255
  end
251
-
256
+
252
257
  def generate_xml_doc
253
258
  @version ||= '1.1'
254
- version_dir = version.gsub('.','/')
259
+ version_dir = version.tr('.', '/')
255
260
 
256
261
  gpx_header = attributes_and_nsdefs_as_gpx_attributes
257
-
258
- gpx_header['version'] = @version.to_s if !gpx_header['version']
259
- gpx_header['creator'] = DEFAULT_CREATOR if !gpx_header['creator']
260
- gpx_header['xsi:schemaLocation'] = "http://www.topografix.com/GPX/#{version_dir} http://www.topografix.com/GPX/#{version_dir}/gpx.xsd" if !gpx_header['xsi:schemaLocation']
261
- gpx_header['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" if !gpx_header['xsi'] and !gpx_header['xmlns:xsi']
262
-
263
- #$stderr.puts gpx_header.keys.inspect
264
-
265
- doc = Nokogiri::XML::Builder.new do |xml|
266
- xml.gpx(gpx_header) \
267
- {
268
- # version 1.0 of the schema doesn't support the metadata element, so push them straight to the root 'gpx' element
269
- if (@version == '1.0') then
262
+
263
+ gpx_header['version'] = @version.to_s unless gpx_header['version']
264
+ gpx_header['creator'] = DEFAULT_CREATOR unless gpx_header['creator']
265
+ gpx_header['xsi:schemaLocation'] = "http://www.topografix.com/GPX/#{version_dir} http://www.topografix.com/GPX/#{version_dir}/gpx.xsd" unless gpx_header['xsi:schemaLocation']
266
+ gpx_header['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance' if !gpx_header['xsi'] && !gpx_header['xmlns:xsi']
267
+
268
+ # $stderr.puts gpx_header.keys.inspect
269
+
270
+ # rubocop:disable Metrics/BlockLength
271
+ Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
272
+ xml.gpx(gpx_header) do
273
+ # version 1.0 of the schema doesn't support the metadata element, so push them straight to the root 'gpx' element
274
+ if @version == '1.0'
275
+ xml.name @name
276
+ xml.time @time.xmlschema
277
+ xml.bound(
278
+ minlat: bounds.min_lat,
279
+ minlon: bounds.min_lon,
280
+ maxlat: bounds.max_lat,
281
+ maxlon: bounds.max_lon
282
+ )
283
+ else
284
+ xml.metadata do
270
285
  xml.name @name
271
286
  xml.time @time.xmlschema
272
287
  xml.bound(
273
288
  minlat: bounds.min_lat,
274
289
  minlon: bounds.min_lon,
275
290
  maxlat: bounds.max_lat,
276
- maxlon: bounds.max_lon,
291
+ maxlon: bounds.max_lon
277
292
  )
278
- else
279
- xml.metadata {
280
- xml.name @name
281
- xml.time @time.xmlschema
282
- xml.bound(
283
- minlat: bounds.min_lat,
284
- minlon: bounds.min_lon,
285
- maxlat: bounds.max_lat,
286
- maxlon: bounds.max_lon,
287
- )
288
- }
289
293
  end
294
+ end
295
+
296
+ tracks&.each do |t|
297
+ xml.trk do
298
+ xml.name t.name
290
299
 
291
- tracks.each do |t|
292
- xml.trk {
293
- xml.name t.name
294
-
295
- t.segments.each do |seg|
296
- xml.trkseg {
297
- seg.points.each do |p|
298
- xml.trkpt(lat: p.lat, lon: p.lon) {
299
- xml.time p.time.xmlschema unless p.time.nil?
300
- xml.ele p.elevation unless p.elevation.nil?
301
- xml << p.extensions.to_xml unless p.extensions.nil?
302
- }
300
+ t.segments.each do |seg|
301
+ xml.trkseg do
302
+ seg.points.each do |p|
303
+ xml.trkpt(lat: p.lat, lon: p.lon) do
304
+ xml.time p.time.xmlschema unless p.time.nil?
305
+ xml.ele p.elevation unless p.elevation.nil?
306
+ xml << p.extensions.to_xml unless p.extensions.nil?
303
307
  end
304
- }
308
+ end
305
309
  end
306
- }
307
- end unless tracks.nil?
308
-
309
- waypoints.each do |w|
310
- xml.wpt(lat: w.lat, lon: w.lon) {
311
- xml.time w.time.xmlschema unless w.time.nil?
312
- Waypoint::SUB_ELEMENTS.each do |sub_elem|
313
- xml.send(sub_elem, w.send(sub_elem)) if w.respond_to?(sub_elem) && !w.send(sub_elem).nil?
314
- end
315
- }
316
- end unless waypoints.nil?
317
-
318
- routes.each do |r|
319
- xml.rte {
320
- xml.name r.name
321
-
322
- r.points.each do |p|
323
- xml.rtept(lat: p.lat, lon: p.lon) {
324
- xml.time p.time.xmlschema unless p.time.nil?
325
- xml.ele p.elevation unless p.elevation.nil?
326
- }
310
+ end
311
+ end
312
+ end
313
+
314
+ waypoints&.each do |w|
315
+ xml.wpt(lat: w.lat, lon: w.lon) do
316
+ xml.time w.time.xmlschema unless w.time.nil?
317
+ Waypoint::SUB_ELEMENTS.each do |sub_elem|
318
+ xml.send(sub_elem, w.send(sub_elem)) if w.respond_to?(sub_elem) && !w.send(sub_elem).nil?
319
+ end
320
+ end
321
+ end
322
+
323
+ routes&.each do |r|
324
+ xml.rte do
325
+ xml.name r.name
326
+
327
+ r.points.each do |p|
328
+ xml.rtept(lat: p.lat, lon: p.lon) do
329
+ xml.time p.time.xmlschema unless p.time.nil?
330
+ xml.ele p.elevation unless p.elevation.nil?
327
331
  end
328
- }
329
- end unless routes.nil?
330
- }
332
+ end
333
+ end
334
+ end
335
+ end
331
336
  end
332
-
333
- return doc
337
+ # rubocop:enable Metrics/BlockLength
334
338
  end
335
339
 
336
340
  # Calculates and sets the duration attribute by subtracting the time on
337
341
  # the very first point from the time on the very last point.
338
342
  def calculate_duration
339
343
  @duration = 0
340
- if(@tracks.nil? or @tracks.size.zero? or @tracks[0].segments.nil? or @tracks[0].segments.size.zero?)
344
+ if @tracks.nil? || @tracks.empty? || @tracks[0].segments.nil? || @tracks[0].segments.empty?
341
345
  return @duration
346
+
342
347
  end
348
+
343
349
  @duration = (@tracks[-1].segments[-1].points[-1].time - @tracks.first.segments.first.points.first.time)
344
- rescue
350
+ rescue StandardError
345
351
  @duration = 0
346
352
  end
347
353
  end