gpx 0.7 → 0.8.1

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.
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