gpx 0.7 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/gpx/gpx.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006 Doug Fales
2
+ # Copyright (c) 2006 Doug Fales
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -21,29 +21,26 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
  module GPX
24
- VERSION = "0.7"
25
-
26
24
  # A common base class which provides a useful initializer method to many
27
25
  # class in the GPX library.
28
26
  class Base
27
+ # This initializer can take an XML::Node and scrape out any text
28
+ # elements with the names given in the "text_elements" array. Each
29
+ # element found underneath "parent" with a name in "text_elements" causes
30
+ # an attribute to be initialized on the instance. This means you don't
31
+ # have to pick out individual text elements in each initializer of each
32
+ # class (Route, TrackPoint, Track, etc). Just pass an array of possible
33
+ # attributes to this method.
34
+ def instantiate_with_text_elements(parent, text_elements)
35
+ text_elements.each do |el|
36
+ child_xpath = "#{el}"
37
+ unless parent.at(child_xpath).nil?
38
+ val = parent.at(child_xpath).inner_text
39
+ self.send("#{el}=", val)
40
+ end
41
+ end
29
42
 
30
- # This initializer can take an XML::Node and scrape out any text
31
- # elements with the names given in the "text_elements" array. Each
32
- # element found underneath "parent" with a name in "text_elements" causes
33
- # an attribute to be initialized on the instance. This means you don't
34
- # have to pick out individual text elements in each initializer of each
35
- # class (Route, TrackPoint, Track, etc). Just pass an array of possible
36
- # attributes to this method.
37
- def instantiate_with_text_elements(parent, text_elements)
38
- text_elements.each do |el|
39
- child_xpath = "//#{el}"
40
- unless parent.at(child_xpath).nil?
41
- val = parent.at(child_xpath).inner_text
42
- self.send("#{el}=", val)
43
- end
44
- end
45
-
46
- end
43
+ end
47
44
 
48
45
  end
49
46
  end
data/lib/gpx/gpx_file.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006 Doug Fales
2
+ # Copyright (c) 2006 Doug Fales
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -21,250 +21,325 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
  module GPX
24
- class GPXFile < Base
25
- attr_accessor :tracks, :routes, :waypoints, :bounds, :lowest_point, :highest_point, :duration, :ns, :version, :time, :name
24
+ class GPXFile < Base
25
+ attr_accessor :tracks, :routes, :waypoints, :bounds, :lowest_point, :highest_point, :duration, :ns, :time, :name, :version, :creator, :description
26
26
 
27
+ DEFAULT_CREATOR = "GPX RubyGem #{GPX::VERSION} -- http://dougfales.github.io/gpx/".freeze
27
28
 
28
- # This initializer can be used to create a new GPXFile from an existing
29
- # file or to create a new GPXFile instance with no data (so that you can
30
- # add tracks and points and write it out to a new file later).
31
- # To read an existing GPX file, do this:
32
- # gpx_file = GPXFile.new(:gpx_file => 'mygpxfile.gpx')
33
- # puts "Speed: #{gpx_file.average_speed}"
34
- # puts "Duration: #{gpx_file.duration}"
35
- # puts "Bounds: #{gpx_file.bounds}"
36
- #
37
- # To read a GPX file from a string, use :gpx_data.
38
- # gpx_file = GPXFile.new(:gpx_data => '<xml ...><gpx>...</gpx>)
39
- # To create a new blank GPXFile instance:
40
- # gpx_file = GPXFile.new
41
- # Note that you can pass in any instance variables to this form of the initializer, including Tracks or Segments:
42
- # some_track = get_track_from_csv('some_other_format.csv')
43
- # gpx_file = GPXFile.new(:tracks => [some_track])
44
- #
45
- def initialize(opts = {})
46
- @duration = 0
47
- if(opts[:gpx_file] or opts[:gpx_data])
48
- if opts[:gpx_file]
49
- gpx_file = opts[:gpx_file]
50
- #case gpx_file
51
- #when String
52
- # gpx_file = File.open(gpx_file)
53
- #end
54
- gpx_file = gpx_file.name if gpx_file.is_a?(File)
55
- @xml = Hpricot(File.open(gpx_file))
56
- else
57
- @xml = Hpricot(opts[:gpx_data])
58
- end
59
- # set XML namespace for XML find
60
- #if @xml.root.namespaces.namespace
61
- # @ns = 'gpx:' + @xml.root.namespaces.namespace.href
62
- #else
63
- # @ns = 'gpx:http://www.topografix.com/GPX/1/1' # default to GPX 1.1
64
- #end
65
-
66
- reset_meta_data
67
- bounds_element = (@xml.at("//metadata/bounds") rescue nil)
68
- 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})
73
- else
74
- get_bounds = true
75
- end
29
+ # This initializer can be used to create a new GPXFile from an existing
30
+ # file or to create a new GPXFile instance with no data (so that you can
31
+ # add tracks and points and write it out to a new file later).
32
+ # To read an existing GPX file, do this:
33
+ # gpx_file = GPXFile.new(:gpx_file => 'mygpxfile.gpx')
34
+ # puts "Speed: #{gpx_file.average_speed}"
35
+ # puts "Duration: #{gpx_file.duration}"
36
+ # puts "Bounds: #{gpx_file.bounds}"
37
+ #
38
+ # To read a GPX file from a string, use :gpx_data.
39
+ # gpx_file = GPXFile.new(:gpx_data => '<xml ...><gpx>...</gpx>)
40
+ # To create a new blank GPXFile instance:
41
+ # gpx_file = GPXFile.new
42
+ # Note that you can pass in any instance variables to this form of the initializer, including Tracks or Segments:
43
+ # some_track = get_track_from_csv('some_other_format.csv')
44
+ # gpx_file = GPXFile.new(:tracks => [some_track])
45
+ #
46
+ def initialize(opts = {})
47
+ @duration = 0
48
+ @attributes = {}
49
+ @namespace_defs = []
50
+ if(opts[:gpx_file] or opts[:gpx_data])
51
+ if opts[:gpx_file]
52
+ gpx_file = opts[:gpx_file]
53
+ gpx_file = File.open(gpx_file) unless gpx_file.is_a?(File)
54
+ @xml = Nokogiri::XML(gpx_file)
55
+ else
56
+ @xml = Nokogiri::XML(opts[:gpx_data])
57
+ end
76
58
 
77
- @time = Time.parse(@xml.at("//metadata/time").inner_text) rescue nil
78
- @name = @xml.at("//metadata/name").inner_text rescue nil
79
- @tracks = []
80
- @xml.search("//trk").each do |trk|
81
- trk = Track.new(:element => trk, :gpx_file => self)
82
- update_meta_data(trk, get_bounds)
83
- @tracks << trk
84
- end
85
- @waypoints = []
86
- @xml.search("//wpt").each { |wpt| @waypoints << Waypoint.new(:element => wpt, :gpx_file => self) }
87
- @routes = []
88
- @xml.search("//rte").each { |rte| @routes << Route.new(:element => rte, :gpx_file => self) }
89
- @tracks.delete_if { |t| t.empty? }
59
+ gpx_element = @xml.at('gpx')
60
+ @attributes = gpx_element.attributes
61
+ @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
+ @version = gpx_element['version']
66
+ reset_meta_data
67
+ bounds_element = (@xml.at("metadata/bounds") rescue nil)
68
+ 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})
73
+ else
74
+ get_bounds = true
75
+ end
90
76
 
91
- calculate_duration
92
- else
93
- reset_meta_data
94
- opts.each { |attr_name, value| instance_variable_set("@#{attr_name.to_s}", value) }
95
- unless(@tracks.nil? or @tracks.size.zero?)
96
- @tracks.each { |trk| update_meta_data(trk) }
97
- calculate_duration
98
- end
99
- end
100
- @tracks ||= []
101
- @routes ||= []
102
- @waypoints ||= []
103
- end
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)
83
+ update_meta_data(trk, get_bounds)
84
+ @tracks << trk
85
+ end
86
+ @waypoints = []
87
+ @xml.search("wpt").each { |wpt| @waypoints << Waypoint.new(:element => wpt, :gpx_file => self) }
88
+ @routes = []
89
+ @xml.search("rte").each { |rte| @routes << Route.new(:element => rte, :gpx_file => self) }
90
+ @tracks.delete_if { |t| t.empty? }
104
91
 
105
- def get_bounds_attr_value(el, possible_names)
106
- result = nil
107
- possible_names.each do |name|
108
- result = el[name]
109
- break unless result.nil?
110
- end
111
- return (result.to_f rescue nil)
92
+ calculate_duration
93
+ else
94
+ 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?)
97
+ @tracks.each { |trk| update_meta_data(trk) }
98
+ calculate_duration
99
+ end
112
100
  end
101
+ @tracks ||= []
102
+ @routes ||= []
103
+ @waypoints ||= []
104
+ end
113
105
 
114
- # Returns the distance, in kilometers, meters, or miles, of all of the
115
- # tracks and segments contained in this GPXFile.
116
- def distance(opts = { :units => 'kilometers' })
117
- case opts[:units]
118
- when /kilometers/i
119
- return @distance
120
- when /meters/i
121
- return (@distance * 1000)
122
- when /miles/i
123
- return (@distance * 0.62)
124
- end
106
+ def get_bounds_attr_value(el, possible_names)
107
+ result = nil
108
+ possible_names.each do |name|
109
+ result = el[name]
110
+ break unless result.nil?
125
111
  end
112
+ return (result.to_f rescue nil)
113
+ end
126
114
 
127
- # Returns the average speed, in km/hr, meters/hr, or miles/hr, of this
128
- # GPXFile. The calculation is based on the total distance divided by the
129
- # total duration of the entire file.
130
- def average_speed(opts = { :units => 'kilometers' })
131
- case opts[:units]
132
- when /kilometers/i
133
- return @distance / (@duration/3600.0)
134
- when /meters/i
135
- return (@distance * 1000) / (@duration/3600.0)
136
- when /miles/i
137
- return (@distance * 0.62) / (@duration/3600.0)
138
- end
115
+ # Returns the distance, in kilometers, meters, or miles, of all of the
116
+ # tracks and segments contained in this GPXFile.
117
+ def distance(opts = { :units => 'kilometers' })
118
+ case opts[:units]
119
+ when /kilometers/i
120
+ return @distance
121
+ when /meters/i
122
+ return (@distance * 1000)
123
+ when /miles/i
124
+ return (@distance * 0.62)
139
125
  end
126
+ end
140
127
 
141
- # Crops any points falling within a rectangular area. Identical to the
142
- # delete_area method in every respect except that the points outside of
143
- # the given area are deleted. Note that this method automatically causes
144
- # the meta data to be updated after deletion.
145
- def crop(area)
146
- reset_meta_data
147
- keep_tracks = []
148
- tracks.each do |trk|
149
- trk.crop(area)
150
- unless trk.empty?
151
- update_meta_data(trk)
152
- keep_tracks << trk
153
- end
154
- end
155
- @tracks = keep_tracks
156
- routes.each { |rte| rte.crop(area) }
157
- waypoints.each { |wpt| wpt.crop(area) }
128
+ # Returns the average speed, in km/hr, meters/hr, or miles/hr, of this
129
+ # GPXFile. The calculation is based on the total distance divided by the
130
+ # total duration of the entire file.
131
+ def average_speed(opts = { :units => 'kilometers' })
132
+ case opts[:units]
133
+ when /kilometers/i
134
+ return @distance / (@duration/3600.0)
135
+ when /meters/i
136
+ return (@distance * 1000) / (@duration/3600.0)
137
+ when /miles/i
138
+ return (@distance * 0.62) / (@duration/3600.0)
158
139
  end
140
+ end
159
141
 
160
- # Deletes any points falling within a rectangular area. The "area"
161
- # parameter is usually an instance of the Bounds class. Note that this
162
- # method cascades into similarly named methods of subordinate classes
163
- # (i.e. Track, Segment), which means, if you want the deletion to apply
164
- # to all the data, you only call this one (and not the one in Track or
165
- # Segment classes). Note that this method automatically causes the meta
166
- # data to be updated after deletion.
167
- def delete_area(area)
168
- reset_meta_data
169
- keep_tracks = []
170
- tracks.each do |trk|
171
- trk.delete_area(area)
172
- unless trk.empty?
173
- update_meta_data(trk)
174
- keep_tracks << trk
175
- end
176
- end
177
- @tracks = keep_tracks
178
- routes.each { |rte| rte.delete_area(area) }
179
- waypoints.each { |wpt| wpt.delete_area(area) }
142
+ # Crops any points falling within a rectangular area. Identical to the
143
+ # delete_area method in every respect except that the points outside of
144
+ # the given area are deleted. Note that this method automatically causes
145
+ # the meta data to be updated after deletion.
146
+ def crop(area)
147
+ reset_meta_data
148
+ keep_tracks = []
149
+ tracks.each do |trk|
150
+ trk.crop(area)
151
+ unless trk.empty?
152
+ update_meta_data(trk)
153
+ keep_tracks << trk
154
+ end
180
155
  end
156
+ @tracks = keep_tracks
157
+ routes.each { |rte| rte.crop(area) }
158
+ waypoints.each { |wpt| wpt.crop(area) }
159
+ end
181
160
 
182
- # Resets the meta data for this GPX file. Meta data includes the bounds,
183
- # the high and low points, and the distance.
184
- def reset_meta_data
185
- @bounds = Bounds.new
186
- @highest_point = nil
187
- @lowest_point = nil
188
- @distance = 0.0
161
+ # Deletes any points falling within a rectangular area. The "area"
162
+ # parameter is usually an instance of the Bounds class. Note that this
163
+ # method cascades into similarly named methods of subordinate classes
164
+ # (i.e. Track, Segment), which means, if you want the deletion to apply
165
+ # to all the data, you only call this one (and not the one in Track or
166
+ # Segment classes). Note that this method automatically causes the meta
167
+ # data to be updated after deletion.
168
+ def delete_area(area)
169
+ reset_meta_data
170
+ keep_tracks = []
171
+ tracks.each do |trk|
172
+ trk.delete_area(area)
173
+ unless trk.empty?
174
+ update_meta_data(trk)
175
+ keep_tracks << trk
176
+ end
189
177
  end
178
+ @tracks = keep_tracks
179
+ routes.each { |rte| rte.delete_area(area) }
180
+ waypoints.each { |wpt| wpt.delete_area(area) }
181
+ end
190
182
 
191
- # Updates the meta data for this GPX file. Meta data includes the
192
- # bounds, the high and low points, and the distance. This is useful when
193
- # you modify the GPX data (i.e. by adding or deleting points) and you
194
- # want the meta data to accurately reflect the new data.
195
- def update_meta_data(trk, get_bounds = true)
196
- @lowest_point = trk.lowest_point if(@lowest_point.nil? or (!trk.lowest_point.nil? and trk.lowest_point.elevation < @lowest_point.elevation))
197
- @highest_point = trk.highest_point if(@highest_point.nil? or (!trk.highest_point.nil? and trk.highest_point.elevation > @highest_point.elevation))
198
- @bounds.add(trk.bounds) if get_bounds
199
- @distance += trk.distance
200
- end
183
+ # Resets the meta data for this GPX file. Meta data includes the bounds,
184
+ # the high and low points, and the distance.
185
+ def reset_meta_data
186
+ @bounds = Bounds.new
187
+ @highest_point = nil
188
+ @lowest_point = nil
189
+ @distance = 0.0
190
+ end
201
191
 
202
- # Serialize the current GPXFile to a gpx file named <filename>.
203
- # If the file does not exist, it is created. If it does exist, it is overwritten.
204
- def write(filename, update_time = true)
205
- @time = Time.now if(@time.nil? or update_time)
206
- @name ||= File.basename(filename)
207
- doc = generate_xml_doc
208
- doc.save(filename, :indent => true)
192
+ # Updates the meta data for this GPX file. Meta data includes the
193
+ # bounds, the high and low points, and the distance. This is useful when
194
+ # you modify the GPX data (i.e. by adding or deleting points) and you
195
+ # want the meta data to accurately reflect the new data.
196
+ def update_meta_data(trk, get_bounds = true)
197
+ @lowest_point = trk.lowest_point if(@lowest_point.nil? or (!trk.lowest_point.nil? and trk.lowest_point.elevation < @lowest_point.elevation))
198
+ @highest_point = trk.highest_point if(@highest_point.nil? or (!trk.highest_point.nil? and trk.highest_point.elevation > @highest_point.elevation))
199
+ @bounds.add(trk.bounds) if get_bounds
200
+ @distance += trk.distance
201
+ end
202
+
203
+ # Serialize the current GPXFile to a gpx file named <filename>.
204
+ # If the file does not exist, it is created. If it does exist, it is overwritten.
205
+ def write(filename, update_time = true)
206
+ @time = Time.now if(@time.nil? or update_time)
207
+ @name ||= File.basename(filename)
208
+ doc = generate_xml_doc
209
+ File.open(filename, 'w+') { |f| f.write(doc.to_xml) }
210
+ end
211
+
212
+ def to_s(update_time = true)
213
+ @time = Time.now if(@time.nil? or update_time)
214
+ doc = generate_xml_doc
215
+ doc.to_xml
216
+ end
217
+
218
+ def inspect
219
+ "<#{self.class.name}:...>"
220
+ end
221
+
222
+ def recalculate_distance
223
+ @distance = 0
224
+ @tracks.each do |track|
225
+ track.recalculate_distance
226
+ @distance += track.distance
209
227
  end
228
+ end
229
+
230
+ private
231
+ def attributes_and_nsdefs_as_gpx_attributes
232
+ #$stderr.puts @namespace_defs.inspect
233
+ gpx_header = {}
234
+ @attributes.each do |k,v|
235
+ k = v.namespace.prefix + ':' + k if v.namespace
236
+ gpx_header[k] = v.value
237
+ end
210
238
 
211
- def to_s(update_time = true)
212
- @time = Time.now if(@time.nil? or update_time)
213
- doc = generate_xml_doc
214
- doc.to_s
239
+ @namespace_defs.each do |nsd|
240
+ tag = 'xmlns'
241
+ if nsd.prefix
242
+ tag += ':' + nsd.prefix
243
+ end
244
+ gpx_header[tag] = nsd.href
215
245
  end
246
+ return gpx_header
247
+ end
248
+
249
+ def generate_xml_doc
250
+ @version ||= '1.1'
251
+ version_dir = version.gsub('.','/')
216
252
 
217
- private
218
- def generate_xml_doc
219
- doc = Document.new
220
- doc.root = Node.new('gpx')
221
- gpx_elem = doc.root
222
- gpx_elem['xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
223
- @version = '1.1' if (@version.nil? || !(['1.0', '1.1'].include?(@version))) # default to version 1.1 of the schema (only version 1.0 and 1.1 of the schema exist)
224
- version_dir = @version.gsub('.','/')
225
- gpx_elem['xmlns'] = @ns || "http://www.topografix.com/GPX/#{version_dir}"
226
- gpx_elem['version'] = "#{@version}"
227
- gpx_elem['creator'] = "GPX RubyGem #{GPX::VERSION} Copyright 2006-2009 Doug Fales -- http://gpx.rubyforge.org/"
228
- gpx_elem['xsi:schemaLocation'] = "http://www.topografix.com/GPX/#{version_dir} http://www.topografix.com/GPX/#{version_dir}/gpx.xsd"
253
+ gpx_header = attributes_and_nsdefs_as_gpx_attributes
254
+
255
+ gpx_header['version'] = @version.to_s if !gpx_header['version']
256
+ gpx_header['creator'] = DEFAULT_CREATOR if !gpx_header['creator']
257
+ 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']
258
+ gpx_header['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" if !gpx_header['xsi'] and !gpx_header['xmlns:xsi']
259
+
260
+ #$stderr.puts gpx_header.keys.inspect
229
261
 
230
- # setup the metadata elements
231
- name_elem = Node.new('name')
232
- name_elem << @name
233
- time_elem = Node.new('time')
234
- time_elem << @time.xmlschema
262
+ doc = Nokogiri::XML::Builder.new do |xml|
263
+ xml.gpx(gpx_header) \
264
+ {
265
+ # version 1.0 of the schema doesn't support the metadata element, so push them straight to the root 'gpx' element
266
+ if (@version == '1.0') then
267
+ xml.name @name
268
+ xml.time @time.xmlschema
269
+ xml.bound(
270
+ minlat: bounds.min_lat,
271
+ minlon: bounds.min_lon,
272
+ maxlat: bounds.max_lat,
273
+ maxlon: bounds.max_lon,
274
+ )
275
+ else
276
+ xml.metadata {
277
+ xml.name @name
278
+ xml.time @time.xmlschema
279
+ xml.bound(
280
+ minlat: bounds.min_lat,
281
+ minlon: bounds.min_lon,
282
+ maxlat: bounds.max_lat,
283
+ maxlon: bounds.max_lon,
284
+ )
285
+ }
286
+ end
235
287
 
236
- # version 1.0 of the schema doesn't support the metadata element, so push them straight to the root 'gpx' element
237
- if (@version == '1.0') then
238
- gpx_elem << name_elem
239
- gpx_elem << time_elem
240
- gpx_elem << bounds.to_xml
241
- else
242
- meta_data_elem = Node.new('metadata')
243
- meta_data_elem << name_elem
244
- meta_data_elem << time_elem
245
- meta_data_elem << bounds.to_xml
246
- gpx_elem << meta_data_elem
247
- end
288
+ tracks.each do |t|
289
+ xml.trk {
290
+ xml.name t.name
248
291
 
249
- tracks.each { |t| gpx_elem << t.to_xml } unless tracks.nil?
250
- waypoints.each { |w| gpx_elem << w.to_xml } unless waypoints.nil?
251
- routes.each { |r| gpx_elem << r.to_xml } unless routes.nil?
292
+ t.segments.each do |seg|
293
+ xml.trkseg {
294
+ seg.points.each do |p|
295
+ xml.trkpt(lat: p.lat, lon: p.lon) {
296
+ xml.time p.time.xmlschema unless p.time.nil?
297
+ xml.ele p.elevation unless p.elevation.nil?
298
+ xml << p.extensions.to_xml unless p.extensions.nil?
299
+ }
300
+ end
301
+ }
302
+ end
303
+ }
304
+ end unless tracks.nil?
252
305
 
253
- return doc
254
- end
306
+ waypoints.each do |w|
307
+ xml.wpt(lat: w.lat, lon: w.lon) {
308
+ xml.time w.time.xmlschema unless w.time.nil?
309
+ Waypoint::SUB_ELEMENTS.each do |sub_elem|
310
+ xml.send(sub_elem, w.send(sub_elem)) if w.respond_to?(sub_elem) && !w.send(sub_elem).nil?
311
+ end
312
+ }
313
+ end unless waypoints.nil?
255
314
 
256
- # Calculates and sets the duration attribute by subtracting the time on
257
- # the very first point from the time on the very last point.
258
- def calculate_duration
259
- @duration = 0
260
- if(@tracks.nil? or @tracks.size.zero? or @tracks[0].segments.nil? or @tracks[0].segments.size.zero?)
261
- return @duration
262
- end
263
- @duration = (@tracks[-1].segments[-1].points[-1].time - @tracks.first.segments.first.points.first.time)
264
- rescue
265
- @duration = 0
315
+ routes.each do |r|
316
+ xml.rte {
317
+ xml.name r.name
318
+
319
+ r.points.each do |p|
320
+ xml.rtept(lat: p.lat, lon: p.lon) {
321
+ xml.time p.time.xmlschema unless p.time.nil?
322
+ xml.ele p.elevation unless p.elevation.nil?
323
+ }
324
+ end
325
+ }
326
+ end unless routes.nil?
327
+ }
266
328
  end
267
329
 
330
+ return doc
331
+ end
268
332
 
269
- end
333
+ # Calculates and sets the duration attribute by subtracting the time on
334
+ # the very first point from the time on the very last point.
335
+ def calculate_duration
336
+ @duration = 0
337
+ if(@tracks.nil? or @tracks.size.zero? or @tracks[0].segments.nil? or @tracks[0].segments.size.zero?)
338
+ return @duration
339
+ end
340
+ @duration = (@tracks[-1].segments[-1].points[-1].time - @tracks.first.segments.first.points.first.time)
341
+ rescue
342
+ @duration = 0
343
+ end
344
+ end
270
345
  end