1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """
22 Process elevation data for an SABX ride.
23 """
24 import math
25
26 from sabx10.oxm import mile_feet, meter_feet
27
28 from consts import CLIMB_COUNT
29
31 """
32 Takes a raw list of climbs, filters out climbs that are only downhill, then
33 sorts by length and returns the top "count" climbs. The returned list is
34 sorted by steepness. Each climb in the list is a tuple of (grade,length).
35
36 @param climbs: list of climbs to process
37 @type climbs: C{list}
38 @param count: how many climbs to return
39 @type count: C{int}
40
41 @return: top 'count' climbs
42 @rtype: C{list}
43 """
44 climbs = [climb for climb in climbs if climb[0] > 0.0]
45 climbs.sort(key=lambda x: x[1], reverse=True)
46 climbs = climbs[:count]
47 climbs.sort(key=lambda x: x[0], reverse=True)
48
49 return climbs
50
52 """
53 Calculate the rising and falling grades for a set of data.
54
55 @param rise_height: rising height
56 @type rise_height: C{float}
57 @param rise_dist: rising distance
58 @type rise_dist: C{float}
59 @param fall_height: falling height
60 @type fall_height: C{float}
61 @param fall_dist: falling distance
62 @type fall_dist: C{float}
63
64 @return: rising grade, falling grade
65 @rtype: (C{float},C{float})
66 """
67 rise_grade = 0.0
68 if rise_dist > 0.0:
69 rise_grade = (rise_height / (rise_dist * mile_feet)) * 100.0
70 fall_grade = 0.0
71 if fall_dist > 0.0:
72 fall_grade = (fall_height / (fall_dist * mile_feet)) * 100.0
73 return rise_grade, fall_grade
74
75
76
78 """
79 Encapsulates a set of points representing a line and all the operations we
80 might want to do on it.
81
82 @ivar intras: intra-point values used for all our calculations, in the form
83 of a list of (length,rise) tuples
84 @type intras: C{list} of (C{float},C{float})
85 """
86
88 """
89 Calculate and store the distance and rise between each pair of points
90 in the list. This list will be used by all the other methods on this
91 object.
92
93 @param points: list of waypoints
94 @type points: C{list} of L{Point} objects
95 """
96 self.intras = [
97 (points[index].calculate_distance(points[index+1]),
98 (points[index+1].ele - points[index].ele) * meter_feet)
99 for index in range(len(points)-1)]
100
102 """
103 Calculate the length of the line. This is the distance along the set
104 of waypoints.
105
106 @return: length of line
107 @rtype: C{float}
108 """
109 return sum([length for length, rise in self.intras])
110
112 """
113 Calculate the cumulative rise for the line. This is the rise height
114 for every intra-point value that rises.
115
116 @return: rise height, rise distance
117 @rtype: (C{float},C{float})
118 """
119 rise_height = sum([rise for length, rise in self.intras if rise > 0.0])
120 rise_dist = sum([length for length, rise in self.intras if rise > 0.0])
121 return rise_height, rise_dist
122
124 """
125 Calculate the cumulative fall for the line. This is the rise height for
126 every intra-point value that falls.
127
128 @return: fall height, fall distance
129 @rtype: (C{float},C{float})
130 """
131 fall_height = sum([rise for length, rise in self.intras if rise <= 0.0])
132 fall_dist = sum([length for length, rise in self.intras if rise <= 0.0])
133 return fall_height, fall_dist
134
136 """
137 Create a list of climbs in the line. Ignore single-point declines
138 during a climb, since this seems to give more realistic climbs than if
139 single "bumps" cause climbs to restart.
140
141 @return: list of climbs with (grade,distance)
142 @rtype: C{list} of (C{float},C{float})
143 """
144 climb_height = 0.0
145 climb_dist = 0.0
146 climbs = []
147 in_climb = -1
148
149 for length, rise in self.intras:
150 if rise > 0.0:
151 in_climb = 1
152 climb_height += rise
153 climb_dist += length
154 else:
155 if in_climb == 0:
156 if climb_dist > 0.0:
157 climbs.append( (
158 (climb_height / (climb_dist * mile_feet)) * 100.0,
159 climb_dist) )
160 climb_height = 0.0
161 climb_dist = 0.0
162 elif in_climb > 0:
163 climb_height += rise
164 climb_dist += length
165 in_climb -= 1
166
167 return climbs
168
170 """
171 Hold all the values we are interested in for a ride segment and create a
172 dictionary to present them.
173
174 @ivar seg: segment we are processing
175 @type seg: C{Segment}
176 @ivar start_dist: distance into ride for first point of segment
177 @type start_dist: C{float}
178 @ivar length: length of the segment
179 @type length: C{float}
180 @ivar rise_height: cumulative height of ascent in this segment
181 @type rise_height: C{float}
182 @ivar rise_dist: length of the part of the segment going up
183 @type rise_dist: C{float}
184 @ivar fall_height: cumulative height of descent in this segment
185 @type fall_height: C{float}
186 @ivar fall_dist: length of the part of the segment going down
187 @type fall_dist: C{float}
188 @ivar rise_grade: average climbing grade of segment
189 @type rise_grade: C{float}
190 @ivar fall_grade: average descending grade of segment
191 @type fall_grade: C{float}
192 @ivar climbs: list of "top" climbs for the segment
193 @type climbs: C{list}
194 """
195
197 """
198 Initialize and calculate all values for the segment.
199
200 @param seg: segment to analyze
201 @type seg: L{Segment}
202 @param accum: segment accumulator
203 @type accum: L{SegmentAccumulator}
204 """
205 PointsProcessor.__init__(self, seg.waypoints)
206 self.seg = seg
207 self.start_dist = accum.length
208 self.length = self.calc_points_distance()
209 self.rise_height, self.rise_dist = self.calc_points_rise()
210 self.fall_height, self.fall_dist = self.calc_points_fall()
211 self.rise_grade, self.fall_grade = _calc_rise_fall_grades(
212 self.rise_height, self.rise_dist, self.fall_height, self.fall_dist)
213 climbs = self.calc_points_climbs()
214 self.climbs = _filter_climbs(climbs, CLIMB_COUNT)
215
217 """
218 Create a dictionary that can be used by Jinja2 to present this data.
219
220 The dictionary contains the following:
221 - id: id of segment
222 - start_dist: distance in ride for starting point
223 - end_dist: distance in ride for ending point
224 - length: length of ride
225 - net_height: net change in elevation over segment
226 - rise_height: height of climbing in segment
227 - rise_dist: distance spent climbing in segment
228 - rise_grade: average grade of climbing in segment
229 - fall_height: height of descending in segment
230 - fall_dist: distance spent descending in segment
231 - fall_grade: average grade of descending in segment
232 - gradients: table containing gradients and how much time spent in them
233 - climbs: list with climbs, their grades, and their lengths in segment
234
235 @return: presentable data
236 @rtype: C{dict}
237 """
238 return {'id': self.seg.id,
239 'start_dist': self.start_dist,
240 'end_dist': self.start_dist + self.length,
241 'length': self.length,
242 'net_height': self.rise_height - self.fall_height,
243 'rise_height': self.rise_height,
244 'rise_dist': self.rise_dist,
245 'rise_grade': self.rise_grade,
246 'fall_height': self.fall_height,
247 'fall_dist': self.fall_dist,
248 'fall_grade': self.fall_grade,
249 'climbs': self.climbs
250 }
251
253 """
254 Gather all the values we're interested in for a ride by extracting them
255 from the processed segments and accumulating them.
256
257 @ivar distance: length of the ride
258 @type distance: C{float}
259 @ivar rise_height: cumulative ascent in this ride
260 @type rise_height: C{float}
261 @ivar rise_dist: length of part of the ride ascending
262 @type rise_dist: C{float}
263 @ivar fall_height: cumulative descent in this ride
264 @type fall_height: C{float}
265 @ivar fall_dist: length of part of the ride descending
266 @type fall_dist: C{float}
267 @ivar climbs: list of "top" climbs for the ride
268 @type climbs: C{list}
269 """
270
272 """
273 Initialize all values for the ride.
274 """
275 self.length = 0.0
276 self.rise_height = 0.0
277 self.rise_dist = 0.0
278 self.fall_height = 0.0
279 self.fall_dist = 0.0
280 self.climbs = []
281
283 """
284 Extract all relevant values from the given segment processor and add
285 them to the instance variables.
286
287 @param seg: segment processor to get data from
288 @type seg: L{SegmentProcessor}
289 """
290 self.length += seg.length
291 self.rise_height += seg.rise_height
292 self.rise_dist += seg.rise_dist
293 self.fall_height += seg.fall_height
294 self.fall_dist += seg.fall_dist
295 self.climbs.extend(seg.climbs)
296
298 """
299 Create a dictionary that can be used by Jinja2 to present this data.
300 Calculate the values that haven't been accumulated.
301
302 The dictionary contains the following data:
303 - length: length of ride
304 - net_height: net change in elevation over ride
305 - rise_height: height of climbing in ride
306 - rise_dist: distance spent climbing in ride
307 - rise_grade: average grade of climbing in ride
308 - fall_height: height of descending in ride
309 - fall_dist: distance spent descending in ride
310 - fall_grade: average grade of descending in ride
311 - climbs: list with climbs, their grades, and their lengths in ride
312
313 @return: presentable data
314 @rtype: C{dict}
315 """
316 rise_grade, fall_grade = _calc_rise_fall_grades(
317 self.rise_height, self.rise_dist,
318 self.fall_height, self.fall_dist)
319 climbs = _filter_climbs(self.climbs, CLIMB_COUNT)
320
321 return {'length': self.length,
322 'net_height': self.rise_height - self.fall_height,
323 'rise_height': self.rise_height,
324 'rise_dist': self.rise_dist,
325 'rise_grade': rise_grade,
326 'fall_height': self.fall_height,
327 'fall_dist': self.fall_dist,
328 'fall_grade': fall_grade,
329 'climbs': climbs
330 }
331
332
333
335 """
336 This is the main entry point for this module. It goes through the
337 elevation data for a ride and creates lists of processed data. It returns
338 the data as a single dictionary describing the overall ride and a list of
339 dictionaries that each describe a segment of the ride.
340
341 Each dictionary element in the segment list has the following information:
342 - id: id of segment
343 - start_dist: distance in ride for starting point
344 - end_dist: distance in ride for ending point
345 - length: length of ride
346 - net_height: net change in elevation over segment
347 - rise_height: height of climbing in segment
348 - rise_dist: distance spent climbing in segment
349 - rise_grade: average grade of climbing in segment
350 - fall_height: height of descending in segment
351 - fall_dist: distance spent descending in segment
352 - fall_grade: average grade of descending in segment
353 - climbs: list with climbs, their grades, and their lengths in segment
354
355 The overall ride data is a dictionary with the following data:
356 - length: length of ride
357 - net_height: net change in elevation over ride
358 - rise_height: height of climbing in ride
359 - rise_dist: distance spent climbing in ride
360 - rise_grade: average grade of climbing in ride
361 - fall_height: height of descending in ride
362 - fall_dist: distance spent descending in ride
363 - fall_grade: average grade of descending in ride
364 - climbs: list with climbs, their grades, and their lengths in ride
365
366 @param ride: ride to process
367 @type ride: L{Ride}
368
369 @return: ride data, list of segment data
370 @rtype: C{dict}, C{list} of C{dict}
371 """
372 ele_set = []
373 accum = SegmentAccumulator()
374 for seg in ride.segs:
375 seg_proc = SegmentProcessor(seg, accum)
376 ele_set.append(seg_proc.create_element())
377 accum.accumulate_segment(seg_proc)
378
379 return accum.create_element(), ele_set
380