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/segment.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,202 +21,233 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
  module GPX
24
-
25
- # A segment is the basic container in a GPX file. A Segment contains points
26
- # (in this lib, they're called TrackPoints). A Track contains Segments. An
27
- # instance of Segment knows its highest point, lowest point, earliest and
28
- # latest points, distance, and bounds.
29
- class Segment < Base
30
-
31
- attr_reader :earliest_point, :latest_point, :bounds, :highest_point, :lowest_point, :distance
32
- attr_accessor :points, :track
33
-
34
- # If a XML::Node object is passed-in, this will initialize a new
35
- # Segment based on its contents. Otherwise, a blank Segment is created.
36
- def initialize(opts = {})
37
- @gpx_file = opts[:gpx_file]
38
- @track = opts[:track]
39
- @points = []
40
- @earliest_point = nil
41
- @latest_point = nil
42
- @highest_point = nil
43
- @lowest_point = nil
44
- @distance = 0.0
45
- @bounds = Bounds.new
46
- if(opts[:element])
47
- segment_element = opts[:element]
48
- last_pt = nil
49
- if segment_element.is_a?(Hpricot::Elem)
50
- segment_element.search("//trkpt").each do |trkpt|
51
- pt = TrackPoint.new(:element => trkpt, :segment => self, :gpx_file => @gpx_file)
52
- unless pt.time.nil?
53
- @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time)
54
- @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time)
55
- end
56
- unless pt.elevation.nil?
57
- @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation)
58
- @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation)
59
- end
60
- @bounds.min_lat = pt.lat if pt.lat < @bounds.min_lat
61
- @bounds.min_lon = pt.lon if pt.lon < @bounds.min_lon
62
- @bounds.max_lat = pt.lat if pt.lat > @bounds.max_lat
63
- @bounds.max_lon = pt.lon if pt.lon > @bounds.max_lon
64
-
65
- @distance += haversine_distance(last_pt, pt) unless last_pt.nil?
66
-
67
- @points << pt
68
- last_pt = pt
69
- end
70
- end
71
- end
72
- end
73
-
74
- # Tack on a point to this Segment. All meta-data will be updated.
75
- def append_point(pt)
76
- last_pt = @points[-1]
77
- @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time)
78
- @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time)
79
- @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation)
80
- @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation)
81
- @bounds.min_lat = pt.lat if pt.lat < @bounds.min_lat
82
- @bounds.min_lon = pt.lon if pt.lon < @bounds.min_lon
83
- @bounds.max_lat = pt.lat if pt.lat > @bounds.max_lat
84
- @bounds.max_lon = pt.lon if pt.lon > @bounds.max_lon
85
- @distance += haversine_distance(last_pt, pt) unless last_pt.nil?
86
- @points << pt
87
- end
88
-
89
- # Returns true if the given time is within this Segment.
90
- def contains_time?(time)
91
- (time >= @earliest_point.time and time <= @latest_point.time) rescue false
92
- end
93
-
94
- # Finds the closest point in time to the passed-in time argument. Useful
95
- # for matching up time-based objects (photos, video, etc) with a
96
- # geographic location.
97
- def closest_point(time)
98
- find_closest(points, time)
99
- end
100
-
101
- # Deletes all points within this Segment that lie outside of the given
102
- # area (which should be a Bounds object).
103
- def crop(area)
104
- delete_if { |pt| not area.contains?(pt) }
105
- end
106
-
107
- # Deletes all points in this Segment that lie within the given area.
108
- def delete_area(area)
109
- delete_if{ |pt| area.contains?(pt) }
110
- end
111
-
112
- # A handy method that deletes points based on a block that is passed in.
113
- # If the passed-in block returns true when given a point, then that point
114
- # is deleted. For example:
115
- # delete_if{ |pt| area.contains?(pt) }
116
- def delete_if
117
- reset_meta_data
118
- keep_points = []
119
- last_pt = nil
120
- points.each do |pt|
121
- unless yield(pt)
122
- keep_points << pt
123
- update_meta_data(pt, last_pt)
124
- last_pt = pt
125
- end
126
- end
127
- @points = keep_points
128
- end
129
-
130
- # Returns true if this Segment has no points.
131
- def empty?
132
- (points.nil? or (points.size == 0))
133
- end
134
-
135
- # Converts this Segment to a XML::Node object.
136
- def to_xml
137
- seg = Node.new('trkseg')
138
- points.each { |pt| seg << pt.to_xml }
139
- seg
140
- end
141
-
142
- # Prints out a nice summary of this Segment.
143
- def to_s
144
- result = "Track Segment\n"
145
- result << "\tSize: #{points.size} points\n"
146
- result << "\tDistance: #{distance} km\n"
147
- result << "\tEarliest Point: #{earliest_point.time.to_s} \n"
148
- result << "\tLatest Point: #{latest_point.time.to_s} \n"
149
- result << "\tLowest Point: #{lowest_point.elevation} \n"
150
- result << "\tHighest Point: #{highest_point.elevation}\n "
151
- result << "\tBounds: #{bounds.to_s}"
152
- result
153
- end
154
-
155
- protected
156
- def find_closest(pts, time)
157
- return pts.first if pts.size == 1
158
- midpoint = pts.size/2
159
- if pts.size == 2
160
- diff_1 = pts[0].time - time
161
- diff_2 = pts[1].time - time
162
- return (diff_1 < diff_2 ? pts[0] : pts[1])
163
- end
164
- if time >= pts[midpoint].time and time <= pts[midpoint+1].time
165
-
166
- return pts[midpoint]
167
-
168
- elsif(time <= pts[midpoint].time)
169
- return find_closest(pts[0..midpoint], time)
170
- else
171
- return find_closest(pts[(midpoint+1)..-1], time)
172
- end
173
- end
174
-
175
- RADIUS = 6371; # earth's mean radius in km
176
-
177
- # Calculate the Haversine distance between two points. This is the method
178
- # the library uses to calculate the cumulative distance of GPX files.
179
- def haversine_distance(p1, p2)
180
- d_lat = p2.latr - p1.latr;
181
- d_lon = p2.lonr - p1.lonr;
182
- a = Math.sin(d_lat/2) * Math.sin(d_lat/2) + Math.cos(p1.latr) * Math.cos(p2.latr) * Math.sin(d_lon/2) * Math.sin(d_lon/2);
183
- c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
184
- d = RADIUS * c;
185
- return d;
186
- end
187
-
188
- # Calculate the plain Pythagorean difference between two points. Not currently used.
189
- def pythagorean_distance(p1, p2)
190
- Math.sqrt((p2.latr - p1.latr)**2 + (p2.lonr - p1.lonr)**2)
191
- end
192
-
193
- # Calculates the distance between two points using the Law of Cosines formula. Not currently used.
194
- def law_of_cosines_distance(p1, p2)
195
- (Math.acos(Math.sin(p1.latr)*Math.sin(p2.latr) + Math.cos(p1.latr)*Math.cos(p2.latr)*Math.cos(p2.lonr-p1.lonr)) * RADIUS)
196
- end
197
-
198
- def reset_meta_data
199
- @earliest_point = nil
200
- @latest_point = nil
201
- @highest_point = nil
202
- @lowest_point = nil
203
- @distance = 0.0
204
- @bounds = Bounds.new
205
- end
206
-
207
- def update_meta_data(pt, last_pt)
208
- unless pt.time.nil?
209
- @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time)
210
- @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time)
211
- end
212
- unless pt.elevation.nil?
213
- @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation)
214
- @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation)
215
- end
216
- @bounds.add(pt)
217
- @distance += haversine_distance(last_pt, pt) unless last_pt.nil?
218
- end
219
-
220
- end
24
+ # A segment is the basic container in a GPX file. A Segment contains points
25
+ # (in this lib, they're called TrackPoints). A Track contains Segments. An
26
+ # instance of Segment knows its highest point, lowest point, earliest and
27
+ # latest points, distance, and bounds.
28
+ class Segment < Base
29
+
30
+ attr_reader :earliest_point, :latest_point, :bounds, :highest_point, :lowest_point, :distance
31
+ attr_accessor :points, :track
32
+
33
+ # If a XML::Node object is passed-in, this will initialize a new
34
+ # Segment based on its contents. Otherwise, a blank Segment is created.
35
+ def initialize(opts = {})
36
+ @gpx_file = opts[:gpx_file]
37
+ @track = opts[:track]
38
+ @points = []
39
+ @earliest_point = nil
40
+ @latest_point = nil
41
+ @highest_point = nil
42
+ @lowest_point = nil
43
+ @distance = 0.0
44
+ @bounds = Bounds.new
45
+ if(opts[:element])
46
+ segment_element = opts[:element]
47
+ last_pt = nil
48
+ if segment_element.is_a?(Nokogiri::XML::Node)
49
+ segment_element.search("trkpt").each do |trkpt|
50
+ pt = TrackPoint.new(:element => trkpt, :segment => self, :gpx_file => @gpx_file)
51
+ append_point(pt)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # Tack on a point to this Segment. All meta-data will be updated.
58
+ def append_point(pt)
59
+ last_pt = @points[-1]
60
+ unless pt.time.nil?
61
+ @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time)
62
+ @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time)
63
+ end
64
+ unless pt.elevation.nil?
65
+ @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation)
66
+ @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation)
67
+ end
68
+ @bounds.min_lat = pt.lat if pt.lat < @bounds.min_lat
69
+ @bounds.min_lon = pt.lon if pt.lon < @bounds.min_lon
70
+ @bounds.max_lat = pt.lat if pt.lat > @bounds.max_lat
71
+ @bounds.max_lon = pt.lon if pt.lon > @bounds.max_lon
72
+ @distance += haversine_distance(last_pt, pt) unless last_pt.nil?
73
+ @points << pt
74
+ end
75
+
76
+ # Returns true if the given time is within this Segment.
77
+ def contains_time?(time)
78
+ (time >= @earliest_point.time and time <= @latest_point.time) rescue false
79
+ end
80
+
81
+ # Finds the closest point in time to the passed-in time argument. Useful
82
+ # for matching up time-based objects (photos, video, etc) with a
83
+ # geographic location.
84
+ def closest_point(time)
85
+ find_closest(points, time)
86
+ end
87
+
88
+ # Deletes all points within this Segment that lie outside of the given
89
+ # area (which should be a Bounds object).
90
+ def crop(area)
91
+ delete_if { |pt| not area.contains?(pt) }
92
+ end
93
+
94
+ # Deletes all points in this Segment that lie within the given area.
95
+ def delete_area(area)
96
+ delete_if{ |pt| area.contains?(pt) }
97
+ end
98
+
99
+ # A handy method that deletes points based on a block that is passed in.
100
+ # If the passed-in block returns true when given a point, then that point
101
+ # is deleted. For example:
102
+ # delete_if{ |pt| area.contains?(pt) }
103
+ def delete_if
104
+ reset_meta_data
105
+ keep_points = []
106
+ last_pt = nil
107
+ points.each do |pt|
108
+ unless yield(pt)
109
+ keep_points << pt
110
+ update_meta_data(pt, last_pt)
111
+ last_pt = pt
112
+ end
113
+ end
114
+ @points = keep_points
115
+ end
116
+
117
+ # Returns true if this Segment has no points.
118
+ def empty?
119
+ (points.nil? or (points.size == 0))
120
+ end
121
+
122
+ # Prints out a nice summary of this Segment.
123
+ def to_s
124
+ result = "Track Segment\n"
125
+ result << "\tSize: #{points.size} points\n"
126
+ result << "\tDistance: #{distance} km\n"
127
+ result << "\tEarliest Point: #{earliest_point.time.to_s} \n"
128
+ result << "\tLatest Point: #{latest_point.time.to_s} \n"
129
+ result << "\tLowest Point: #{lowest_point.elevation} \n"
130
+ result << "\tHighest Point: #{highest_point.elevation}\n "
131
+ result << "\tBounds: #{bounds.to_s}"
132
+ result
133
+ end
134
+
135
+ def find_point_by_time_or_offset(indicator)
136
+ if indicator.nil?
137
+ return nil
138
+ elsif indicator.is_a?(Integer)
139
+ return closest_point(@earliest_point.time + indicator)
140
+ elsif(indicator.is_a?(Time))
141
+ return closest_point(indicator)
142
+ else
143
+ raise Exception, "find_end_point_by_time_or_offset requires an argument of type Time or Integer"
144
+ end
145
+ end
146
+
147
+ # smooths the location data in the segment (by recalculating the location as an average of 20 neighbouring points. Useful for removing noise from GPS traces.
148
+ def smooth_location_by_average(opts={})
149
+ seconds_either_side = opts[:averaging_window] || 20
150
+
151
+ #calculate the first and last points to which the smoothing should be applied
152
+ earliest = (find_point_by_time_or_offset(opts[:start]) || @earliest_point).time
153
+ latest = (find_point_by_time_or_offset(opts[:end]) || @latest_point).time
154
+
155
+ tmp_points = []
156
+
157
+ @points.each do |point|
158
+ if point.time > latest || point.time < earliest
159
+ tmp_points.push point #add the point unaltered
160
+ next
161
+ end
162
+ lat_av = 0.to_f
163
+ lon_av = 0.to_f
164
+ alt_av = 0.to_f
165
+ n = 0
166
+ # k ranges from the time of the current point +/- 20s
167
+ (-1*seconds_either_side..seconds_either_side).each do |k|
168
+ # find the point nearest to the time offset indicated by k
169
+ contributing_point = closest_point(point.time + k)
170
+ #sum up the contributions to the average
171
+ lat_av += contributing_point.lat
172
+ lon_av += contributing_point.lon
173
+ alt_av += contributing_point.elevation
174
+ n += 1
175
+ end
176
+ # calculate the averages
177
+ tmp_point = point.clone
178
+ tmp_point.lon = ((lon_av) / n).round(7)
179
+ tmp_point.elevation = ((alt_av) / n).round(2)
180
+ tmp_point.lat = ((lat_av) / n).round(7)
181
+ tmp_points.push tmp_point
182
+ end
183
+ last_pt = nil
184
+ @distance = 0
185
+ @points.clear
186
+ reset_meta_data
187
+ #now commit the averages back and recalculate the distances
188
+ tmp_points.each do |point|
189
+ append_point(point)
190
+ end
191
+ end
192
+
193
+ protected
194
+ def find_closest(pts, time)
195
+ return pts.first if pts.size == 1
196
+ midpoint = pts.size/2
197
+ if pts.size == 2
198
+ diff_1 = pts[0].time - time
199
+ diff_2 = pts[1].time - time
200
+ return (diff_1 < diff_2 ? pts[0] : pts[1])
201
+ end
202
+ if time >= pts[midpoint].time and time <= pts[midpoint+1].time
203
+
204
+ return pts[midpoint]
205
+
206
+ elsif(time <= pts[midpoint].time)
207
+ return find_closest(pts[0..midpoint], time)
208
+ else
209
+ return find_closest(pts[(midpoint+1)..-1], time)
210
+ end
211
+ end
212
+
213
+ # Calculate the Haversine distance between two points. This is the method
214
+ # the library uses to calculate the cumulative distance of GPX files.
215
+ def haversine_distance(p1, p2)
216
+ p1.haversine_distance_from(p2)
217
+ end
218
+
219
+ # Calculate the plain Pythagorean difference between two points. Not currently used.
220
+ def pythagorean_distance(p1, p2)
221
+ p1.pythagorean_distance_from(p2)
222
+ end
223
+
224
+ # Calculates the distance between two points using the Law of Cosines formula. Not currently used.
225
+ def law_of_cosines_distance(p1, p2)
226
+ p1.law_of_cosines_distance_from(p2)
227
+ end
228
+
229
+ def reset_meta_data
230
+ @earliest_point = nil
231
+ @latest_point = nil
232
+ @highest_point = nil
233
+ @lowest_point = nil
234
+ @distance = 0.0
235
+ @bounds = Bounds.new
236
+ end
237
+
238
+ def update_meta_data(pt, last_pt)
239
+ unless pt.time.nil?
240
+ @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time)
241
+ @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time)
242
+ end
243
+ unless pt.elevation.nil?
244
+ @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation)
245
+ @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation)
246
+ end
247
+ @bounds.add(pt)
248
+ @distance += haversine_distance(last_pt, pt) unless last_pt.nil?
249
+ end
250
+
251
+ end
221
252
 
222
253
  end
data/lib/gpx/track.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,129 +21,123 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
  module GPX
24
+ # In GPX, a single Track can hold multiple Segments, each of which hold
25
+ # multiple points (in this library, those points are instances of
26
+ # TrackPoint). Each instance of this class has its own meta-data, including
27
+ # low point, high point, and distance. Of course, each track references an
28
+ # array of the segments that copmrise it, but additionally each track holds
29
+ # a reference to all of its points as one big array called "points".
30
+ class Track < Base
31
+ attr_reader :points, :bounds, :lowest_point, :highest_point, :distance
32
+ attr_accessor :segments, :name, :gpx_file
24
33
 
25
- # In GPX, a single Track can hold multiple Segments, each of which hold
26
- # multiple points (in this library, those points are instances of
27
- # TrackPoint). Each instance of this class has its own meta-data, including
28
- # low point, high point, and distance. Of course, each track references an
29
- # array of the segments that copmrise it, but additionally each track holds
30
- # a reference to all of its points as one big array called "points".
31
- class Track < Base
32
- attr_reader :points, :bounds, :lowest_point, :highest_point, :distance
33
- attr_accessor :segments, :name, :gpx_file
34
-
35
- # Initialize a track from a XML::Node, or, if no :element option is
36
- # passed, initialize a blank Track object.
37
- def initialize(opts = {})
38
- @gpx_file = opts[:gpx_file]
39
- @segments = []
40
- @points = []
41
- reset_meta_data
42
- if(opts[:element])
43
- trk_element = opts[:element]
44
- @name = (trk_element.at("//name").inner_text rescue "")
45
- trk_element.search("//trkseg").each do |seg_element|
46
- seg = Segment.new(:element => seg_element, :track => self, :gpx_file => @gpx_file)
47
- update_meta_data(seg)
48
- @segments << seg
49
- end
50
- end
51
- end
52
-
53
- # Append a segment to this track, updating its meta data along the way.
54
- def append_segment(seg)
55
- update_meta_data(seg)
56
- @segments << seg
57
- @points.concat(seg.points) unless seg.nil?
34
+ # Initialize a track from a XML::Node, or, if no :element option is
35
+ # passed, initialize a blank Track object.
36
+ def initialize(opts = {})
37
+ @gpx_file = opts[:gpx_file]
38
+ @segments = []
39
+ @points = []
40
+ reset_meta_data
41
+ if(opts[:element])
42
+ trk_element = opts[:element]
43
+ @name = (trk_element.at("name").inner_text rescue "")
44
+ trk_element.search("trkseg").each do |seg_element|
45
+ seg = Segment.new(:element => seg_element, :track => self, :gpx_file => @gpx_file)
46
+ update_meta_data(seg)
47
+ @segments << seg
48
+ end
58
49
  end
50
+ end
59
51
 
60
- # Returns true if the given time occurs within any of the segments of this track.
61
- def contains_time?(time)
62
- segments.each do |seg|
63
- return true if seg.contains_time?(time)
64
- end
65
- return false
66
- end
52
+ # Append a segment to this track, updating its meta data along the way.
53
+ def append_segment(seg)
54
+ update_meta_data(seg)
55
+ @segments << seg
56
+ @points.concat(seg.points) unless seg.nil?
57
+ end
67
58
 
68
- # Finds the closest point (to "time") within this track. Useful for
69
- # correlating things like pictures, video, and other events, if you are
70
- # working with a timestamp.
71
- def closest_point(time)
72
- segment = segments.select { |s| s.contains_time?(time) }
73
- segment.first
59
+ # Returns true if the given time occurs within any of the segments of this track.
60
+ def contains_time?(time)
61
+ segments.each do |seg|
62
+ return true if seg.contains_time?(time)
74
63
  end
64
+ return false
65
+ end
75
66
 
76
- # Removes all points outside of a given area and updates the meta data.
77
- # The "area" paremeter is usually a Bounds object.
78
- def crop(area)
79
- reset_meta_data
80
- segments.each do |seg|
81
- seg.crop(area)
82
- update_meta_data(seg) unless seg.empty?
83
- end
84
- segments.delete_if { |seg| seg.empty? }
85
- end
67
+ # Finds the closest point (to "time") within this track. Useful for
68
+ # correlating things like pictures, video, and other events, if you are
69
+ # working with a timestamp.
70
+ def closest_point(time)
71
+ segment = segments.select { |s| s.contains_time?(time) }
72
+ segment.first
73
+ end
86
74
 
87
- # Deletes all points within a given area and updates the meta data.
88
- def delete_area(area)
89
- reset_meta_data
90
- segments.each do |seg|
91
- seg.delete_area(area)
92
- update_meta_data(seg) unless seg.empty?
93
- end
94
- segments.delete_if { |seg| seg.empty? }
75
+ # Removes all points outside of a given area and updates the meta data.
76
+ # The "area" paremeter is usually a Bounds object.
77
+ def crop(area)
78
+ reset_meta_data
79
+ segments.each do |seg|
80
+ seg.crop(area)
81
+ update_meta_data(seg) unless seg.empty?
95
82
  end
83
+ segments.delete_if { |seg| seg.empty? }
84
+ end
96
85
 
97
- # Returns true if this track has no points in it. This should return
98
- # true even when the track has empty segments.
99
- def empty?
100
- (points.nil? or points.size.zero?)
86
+ # Deletes all points within a given area and updates the meta data.
87
+ def delete_area(area)
88
+ reset_meta_data
89
+ segments.each do |seg|
90
+ seg.delete_area(area)
91
+ update_meta_data(seg) unless seg.empty?
101
92
  end
93
+ segments.delete_if { |seg| seg.empty? }
94
+ end
102
95
 
103
- # Creates a new XML::Node from the contents of this instance.
104
- def to_xml
105
- trk= Node.new('trk')
106
- name_elem = Node.new('name')
107
- name_elem << name
108
- trk << name_elem
109
- segments.each do |seg|
110
- trk << seg.to_xml
111
- end
112
- trk
113
- end
96
+ # Returns true if this track has no points in it. This should return
97
+ # true even when the track has empty segments.
98
+ def empty?
99
+ (points.nil? or points.size.zero?)
100
+ end
114
101
 
115
- # Prints out a friendly summary of this track (sans points). Useful for
116
- # debugging and sanity checks.
102
+ # Prints out a friendly summary of this track (sans points). Useful for
103
+ # debugging and sanity checks.
117
104
 
118
- def to_s
119
- result = "Track \n"
120
- result << "\tName: #{name}\n"
121
- result << "\tSize: #{points.size} points\n"
122
- result << "\tSegments: #{segments.size} \n"
123
- result << "\tDistance: #{distance} km\n"
124
- result << "\tLowest Point: #{lowest_point.elevation} \n"
125
- result << "\tHighest Point: #{highest_point.elevation}\n "
126
- result << "\tBounds: #{bounds.to_s}"
127
- result
128
- end
129
-
130
- protected
105
+ def to_s
106
+ result = "Track \n"
107
+ result << "\tName: #{name}\n"
108
+ result << "\tSize: #{points.size} points\n"
109
+ result << "\tSegments: #{segments.size} \n"
110
+ result << "\tDistance: #{distance} km\n"
111
+ result << "\tLowest Point: #{lowest_point.elevation} \n"
112
+ result << "\tHighest Point: #{highest_point.elevation}\n "
113
+ result << "\tBounds: #{bounds.to_s}"
114
+ result
115
+ end
131
116
 
132
- def update_meta_data(seg)
133
- @lowest_point = seg.lowest_point if(@lowest_point.nil? or seg.lowest_point.elevation < @lowest_point.elevation)
134
- @highest_point = seg.highest_point if(@highest_point.nil? or seg.highest_point.elevation > @highest_point.elevation)
135
- @bounds.add(seg.bounds)
136
- @distance += seg.distance
137
- @points.concat(seg.points)
117
+ def recalculate_distance
118
+ @distance = 0
119
+ @segments.each do |seg|
120
+ @distance += seg.distance
138
121
  end
122
+ end
123
+
124
+ protected
139
125
 
140
- def reset_meta_data
141
- @bounds = Bounds.new
142
- @highest_point = nil
143
- @lowest_point = nil
144
- @distance = 0.0
145
- @points = []
146
- end
126
+ def update_meta_data(seg)
127
+ @lowest_point = seg.lowest_point if(@lowest_point.nil? or seg.lowest_point.elevation < @lowest_point.elevation)
128
+ @highest_point = seg.highest_point if(@highest_point.nil? or seg.highest_point.elevation > @highest_point.elevation)
129
+ @bounds.add(seg.bounds)
130
+ @distance += seg.distance
131
+ @points.concat(seg.points)
132
+ end
133
+
134
+ def reset_meta_data
135
+ @bounds = Bounds.new
136
+ @highest_point = nil
137
+ @lowest_point = nil
138
+ @distance = 0.0
139
+ @points = []
140
+ end
147
141
 
148
- end
142
+ end
149
143
  end