gpx 0.9.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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