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