gpx 0.7 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +82 -0
- data/Gemfile +4 -0
- data/README.md +100 -0
- data/Rakefile +10 -31
- data/bin/gpx_distance +10 -0
- data/bin/gpx_smooth +63 -0
- data/gpx.gemspec +17 -9
- data/lib/gpx.rb +16 -16
- data/lib/gpx/bounds.rb +43 -52
- data/lib/gpx/gpx.rb +17 -20
- data/lib/gpx/gpx_file.rb +292 -217
- data/lib/gpx/magellan_track_log.rb +105 -107
- data/lib/gpx/point.rb +58 -74
- data/lib/gpx/route.rb +31 -43
- data/lib/gpx/segment.rb +229 -198
- data/lib/gpx/track.rb +103 -109
- data/lib/gpx/trackpoint.rb +37 -12
- data/lib/gpx/version.rb +3 -0
- data/lib/gpx/waypoint.rb +3 -25
- data/tests/gpx10_test.rb +3 -3
- data/tests/gpx_file_test.rb +27 -25
- data/tests/gpx_files/arches.gpx +1 -1
- data/tests/gpx_files/big.gpx +1 -1
- data/tests/gpx_files/one_track.gpx +2 -1
- data/tests/gpx_files/waypoints.gpx +2 -0
- data/tests/magellan_test.rb +3 -3
- data/tests/output_test.rb +14 -17
- data/tests/route_test.rb +7 -7
- data/tests/segment_test.rb +40 -8
- data/tests/track_file_test.rb +3 -3
- data/tests/track_point_test.rb +30 -0
- data/tests/track_test.rb +20 -20
- data/tests/waypoint_test.rb +11 -7
- metadata +61 -10
- data/ChangeLog +0 -60
- data/Gemfile.lock +0 -23
- data/README.rdoc +0 -46
- data/tests/output/myoutput.gpx +0 -759
- data/tests/output/new_gpx_file_from_scratch.gpx +0 -105
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
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
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
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
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
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
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
|
-
|
254
|
-
|
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
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
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
|
-
|
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
|