joule 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +48 -3
- data/Rakefile +13 -8
- data/lib/joule.rb +21 -1
- data/lib/joule/base.rb +1 -0
- data/lib/joule/base/parser.rb +50 -0
- data/lib/joule/calculator/marker_calculator.rb +36 -9
- data/lib/joule/calculator/peak_power_calculator.rb +6 -11
- data/lib/joule/calculator/power_calculator.rb +6 -2
- data/lib/joule/csv.rb +18 -1
- data/lib/joule/csv/parser.rb +2 -52
- data/lib/joule/data_point.rb +21 -8
- data/lib/joule/exception.rb +1 -0
- data/lib/joule/exception/unsupported_file_type_exception.rb +7 -0
- data/lib/joule/hashable.rb +9 -0
- data/lib/joule/ibike/parser.rb +22 -22
- data/lib/joule/marker.rb +51 -51
- data/lib/joule/peak_power.rb +13 -0
- data/lib/joule/powertap/parser.rb +15 -14
- data/lib/joule/srm.rb +7 -1
- data/lib/joule/srm/parser.rb +32 -71
- data/lib/joule/tcx.rb +7 -1
- data/lib/joule/tcx/parser.rb +26 -71
- data/lib/joule/tcx/properties.rb +4 -2
- data/lib/joule/units_conversion.rb +2 -2
- data/lib/joule/workout.rb +29 -0
- metadata +49 -23
- data/lib/joule/float.rb +0 -39
data/lib/joule/tcx.rb
CHANGED
data/lib/joule/tcx/parser.rb
CHANGED
@@ -1,49 +1,33 @@
|
|
1
1
|
require 'nokogiri'
|
2
|
+
require 'time'
|
2
3
|
|
3
4
|
module Joule
|
4
5
|
module TCX
|
5
|
-
class Parser
|
6
|
-
include Joule::Calculator::MarkerCalculator
|
7
|
-
include Joule::Calculator::PeakPowerCalculator
|
8
|
-
|
9
|
-
attr_reader :data_points, :markers, :properties, :peak_powers
|
10
|
-
|
11
|
-
def initialize(string_or_io)
|
12
|
-
@string_or_io = string_or_io
|
13
|
-
@data_points = Array.new
|
14
|
-
@properties = Joule::TCX::Properties.new
|
15
|
-
@markers = Array.new
|
16
|
-
@peak_powers = Array.new
|
17
|
-
@has_native_speed = false
|
18
|
-
end
|
6
|
+
class Parser < Joule::Base::Parser
|
19
7
|
|
20
|
-
def
|
21
|
-
@properties.record_interval = 1
|
8
|
+
def parse_workout()
|
22
9
|
@total_record_count = 0
|
23
10
|
parse_activity("Biking")
|
24
11
|
create_workout_marker()
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
if(options[:calculate_peak_power_values])
|
31
|
-
calculate_peak_power_values(:durations => options[:durations], :total_duration => @markers.first.duration_seconds)
|
32
|
-
end
|
33
|
-
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_properties
|
15
|
+
@workout.properties = Joule::TCX::Properties.new
|
16
|
+
@workout.properties.record_interval = 1
|
34
17
|
end
|
35
18
|
|
36
19
|
private
|
37
20
|
def create_workout_marker
|
38
|
-
if(@markers.size > 1)
|
39
|
-
@markers << Marker.new(:start => 0, :end => @data_points.size - 1)
|
21
|
+
if(@workout.markers.size > 1)
|
22
|
+
@workout.markers << Marker.new(:start => 0, :end => @workout.data_points.size - 1)
|
40
23
|
end
|
41
24
|
end
|
25
|
+
|
42
26
|
|
43
27
|
def parse_activity(sport)
|
44
|
-
document = Nokogiri::XML::Document.parse(@
|
28
|
+
document = Nokogiri::XML::Document.parse(@data)
|
45
29
|
document.xpath("//xmlns:Activity[@Sport='#{sport}']").each do |activity|
|
46
|
-
@properties.
|
30
|
+
@workout.properties.date_time = Time.parse(activity.at("./xmlns:Id").content)
|
47
31
|
|
48
32
|
activity.children.each do |child|
|
49
33
|
parse_lap(child) if child.name == "Lap"
|
@@ -57,30 +41,19 @@ module Joule
|
|
57
41
|
def parse_lap(lap_node)
|
58
42
|
|
59
43
|
marker = Marker.new
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
@properties.start_date_time = marker.start_time
|
64
|
-
end
|
65
|
-
|
66
|
-
if @data_points.size > 0
|
67
|
-
marker.start = @data_points.last.time + 1
|
44
|
+
|
45
|
+
if @workout.data_points.size > 0
|
46
|
+
marker.start = @workout.data_points.last.time + 1
|
68
47
|
else
|
69
48
|
marker.start= 0
|
70
49
|
end
|
71
50
|
|
72
51
|
lap_node.children.each do |child|
|
73
52
|
marker.duration_seconds = child.content.to_i if child.name == "TotalTimeSeconds"
|
74
|
-
# puts "Distance in meters: #{child.content}" if child.name == "DistanceMeters"
|
75
|
-
# puts "Maximum Speed: #{child.content}" if child.name == "MaximumSpeed"
|
76
|
-
# puts "Calories: #{child.content}" if child.name == "Calories"
|
77
|
-
# puts "Intensity: #{child.content}" if child.name == "Intensity"
|
78
|
-
# puts "Cadence: #{child.content}" if child.name == "Cadence"
|
79
|
-
# puts "Trigger Method: #{child.content}" if child.name == "TriggerMethod"
|
80
53
|
parse_track(child) if(child.name == "Track")
|
81
54
|
end
|
82
|
-
marker.end = @data_points.last.time
|
83
|
-
@markers << marker
|
55
|
+
marker.end = @workout.data_points.last.time
|
56
|
+
@workout.markers << marker
|
84
57
|
end
|
85
58
|
|
86
59
|
def parse_track(track)
|
@@ -104,21 +77,21 @@ module Joule
|
|
104
77
|
parse_extensions(data, data_point) if data.name == "Extensions"
|
105
78
|
parse_position(data, data_point) if data.name == "Position"
|
106
79
|
end
|
107
|
-
@data_points << data_point
|
80
|
+
@workout.data_points << data_point
|
108
81
|
@trackpoint_count = @trackpoint_count + 1
|
109
82
|
@total_record_count = @total_record_count + 1
|
110
83
|
end
|
111
84
|
|
112
85
|
def parse_times(data, data_point)
|
113
|
-
time_of_day =
|
86
|
+
time_of_day = Time.parse(data.content)
|
114
87
|
data_point.time_of_day = (time_of_day.hour * 3600) + (time_of_day.min * 60) + time_of_day.sec
|
115
|
-
data_point.time = @total_record_count * @properties.record_interval
|
88
|
+
data_point.time = @total_record_count * @workout.properties.record_interval
|
116
89
|
|
117
90
|
if(@trackpoint_count == 0)
|
118
91
|
track_start_time = data_point.time_of_day
|
119
|
-
@track_offset_in_seconds = track_start_time - @properties.start_time_in_seconds
|
92
|
+
@track_offset_in_seconds = track_start_time - @workout.properties.start_time_in_seconds
|
120
93
|
end
|
121
|
-
data_point.time_with_pauses = (@trackpoint_count * @properties.record_interval) + @track_offset_in_seconds
|
94
|
+
data_point.time_with_pauses = (@trackpoint_count * @workout.properties.record_interval) + @track_offset_in_seconds
|
122
95
|
end
|
123
96
|
|
124
97
|
def parse_heartrate(heartrate, data_point)
|
@@ -143,32 +116,14 @@ module Joule
|
|
143
116
|
end
|
144
117
|
end
|
145
118
|
|
146
|
-
def calculate_marker_values
|
147
|
-
@markers.each_with_index { |marker, i|
|
148
|
-
calculate_marker_averages marker
|
149
|
-
calculate_marker_maximums marker
|
150
|
-
calculate_marker_training_metrics marker
|
151
|
-
|
152
|
-
if i.eql?(0)
|
153
|
-
marker.distance = @data_points.last.distance
|
154
|
-
else
|
155
|
-
marker.distance = @data_points[marker.end + 1].distance - @data_points[marker.start].distance
|
156
|
-
end
|
157
|
-
|
158
|
-
marker.duration_seconds = (marker.end - marker.start + 1) * @properties.record_interval
|
159
|
-
marker.energy = (marker.average_power.round * marker.duration_seconds)/1000
|
160
|
-
|
161
|
-
}
|
162
|
-
end
|
163
|
-
|
164
119
|
def calculate_speed
|
165
|
-
@data_points.each_with_index { |v, i|
|
120
|
+
@workout.data_points.each_with_index { |v, i|
|
166
121
|
if(i == 0)
|
167
122
|
delta = v.distance
|
168
123
|
else
|
169
|
-
delta = v.distance - @data_points[i-1].distance
|
124
|
+
delta = v.distance - @workout.data_points[i-1].distance
|
170
125
|
end
|
171
|
-
v.speed = delta / @properties.record_interval
|
126
|
+
v.speed = delta / @workout.properties.record_interval
|
172
127
|
}
|
173
128
|
end
|
174
129
|
end
|
data/lib/joule/tcx/properties.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
module Joule
|
2
2
|
module TCX
|
3
3
|
class Properties
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :record_interval, :date_time
|
5
5
|
|
6
6
|
def start_time_in_seconds
|
7
|
-
(@
|
7
|
+
(@date_time.hour * 3600) + (@date_time.min * 60) + @date_time.sec
|
8
8
|
end
|
9
|
+
|
10
|
+
|
9
11
|
end
|
10
12
|
end
|
11
13
|
end
|
@@ -2,7 +2,7 @@ module Joule
|
|
2
2
|
module UnitsConversion
|
3
3
|
def convert_speed(speed)
|
4
4
|
#convert to mm/s
|
5
|
-
if
|
5
|
+
if @workout.properties.speed_units_are_english?
|
6
6
|
miles_per_hour_to_millimeters_per_second speed
|
7
7
|
else
|
8
8
|
kilometers_per_hour_to_millimeters_per_second speed
|
@@ -11,7 +11,7 @@ module Joule
|
|
11
11
|
|
12
12
|
def convert_distance(distance)
|
13
13
|
#convert distance to mm
|
14
|
-
if
|
14
|
+
if @workout.properties.distance_units_are_english?
|
15
15
|
miles_to_millimeters distance
|
16
16
|
else
|
17
17
|
kilometers_to_millimeters distance
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Joule
|
2
|
+
class Workout
|
3
|
+
# An Array of DataPoint objects. Contains all the data points.
|
4
|
+
attr_accessor :data_points
|
5
|
+
|
6
|
+
# An Array of Marker objects. The first marker always represents the entire
|
7
|
+
# set of data.
|
8
|
+
attr_accessor :markers
|
9
|
+
|
10
|
+
# An Array of Hash objects representing the peak powers for a give duration (5
|
11
|
+
# second, 5 minute, 20 minute, etc). This is also sometimes referred to the
|
12
|
+
# mean maximal power. Peak power calculations can add a significant amount
|
13
|
+
# of time to parsing, so you can perform these later on if you want to with
|
14
|
+
# the Joule::Calculator::PeakPowerCalculator.
|
15
|
+
attr_accessor :peak_powers
|
16
|
+
|
17
|
+
# The properties object represents device properties specific to the data
|
18
|
+
# being parsed.
|
19
|
+
attr_accessor :properties
|
20
|
+
|
21
|
+
def initialize()
|
22
|
+
@data_points = Array.new
|
23
|
+
@markers = Array.new
|
24
|
+
@peak_powers = Array.new
|
25
|
+
@properties = Object.new
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: joule
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 21
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 1.0.1
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Andrew Olson
|
@@ -9,30 +15,42 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date: 2010-
|
18
|
+
date: 2010-09-15 00:00:00 -04:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
22
|
name: nokogiri
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
27
|
- - ">="
|
22
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 5
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 4
|
33
|
+
- 1
|
23
34
|
version: 1.4.1
|
24
|
-
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
25
37
|
- !ruby/object:Gem::Dependency
|
26
38
|
name: fastercsv
|
27
|
-
|
28
|
-
|
29
|
-
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
30
42
|
requirements:
|
31
43
|
- - ">="
|
32
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 4
|
49
|
+
- 0
|
33
50
|
version: 1.4.0
|
34
|
-
|
35
|
-
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
description: Joule parses and does some basic analyzing of powermeter data. Supported formats include SRM(.srm), Saris PowerTap(.csv), iBike(.csv), and Garmin(.tcx)
|
36
54
|
email: anolson@gmail.com
|
37
55
|
executables: []
|
38
56
|
|
@@ -41,41 +59,42 @@ extensions: []
|
|
41
59
|
extra_rdoc_files:
|
42
60
|
- README.rdoc
|
43
61
|
files:
|
44
|
-
- lib/joule
|
45
62
|
- lib/joule/array.rb
|
46
|
-
- lib/joule/
|
63
|
+
- lib/joule/base/parser.rb
|
64
|
+
- lib/joule/base.rb
|
47
65
|
- lib/joule/calculator/marker_calculator.rb
|
48
66
|
- lib/joule/calculator/peak_power_calculator.rb
|
49
67
|
- lib/joule/calculator/power_calculator.rb
|
50
68
|
- lib/joule/calculator.rb
|
51
|
-
- lib/joule/csv
|
52
69
|
- lib/joule/csv/parser.rb
|
53
70
|
- lib/joule/csv.rb
|
54
71
|
- lib/joule/data_point.rb
|
55
|
-
- lib/joule/
|
56
|
-
- lib/joule/
|
72
|
+
- lib/joule/exception/unsupported_file_type_exception.rb
|
73
|
+
- lib/joule/exception.rb
|
74
|
+
- lib/joule/hashable.rb
|
57
75
|
- lib/joule/ibike/parser.rb
|
58
76
|
- lib/joule/ibike/properties.rb
|
59
77
|
- lib/joule/ibike.rb
|
60
78
|
- lib/joule/marker.rb
|
61
|
-
- lib/joule/
|
79
|
+
- lib/joule/peak_power.rb
|
62
80
|
- lib/joule/powertap/parser.rb
|
63
81
|
- lib/joule/powertap/properties.rb
|
64
82
|
- lib/joule/powertap.rb
|
65
|
-
- lib/joule/srm
|
66
83
|
- lib/joule/srm/parser.rb
|
67
84
|
- lib/joule/srm/properties.rb
|
68
85
|
- lib/joule/srm.rb
|
69
|
-
- lib/joule/tcx
|
70
86
|
- lib/joule/tcx/parser.rb
|
71
87
|
- lib/joule/tcx/properties.rb
|
72
88
|
- lib/joule/tcx.rb
|
73
89
|
- lib/joule/units_conversion.rb
|
90
|
+
- lib/joule/workout.rb
|
74
91
|
- lib/joule.rb
|
75
92
|
- README.rdoc
|
76
93
|
- Rakefile
|
77
94
|
has_rdoc: true
|
78
95
|
homepage: http://github.com/anolson/joule
|
96
|
+
licenses: []
|
97
|
+
|
79
98
|
post_install_message:
|
80
99
|
rdoc_options:
|
81
100
|
- --line-numbers
|
@@ -87,23 +106,30 @@ rdoc_options:
|
|
87
106
|
require_paths:
|
88
107
|
- lib
|
89
108
|
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
90
110
|
requirements:
|
91
111
|
- - ">="
|
92
112
|
- !ruby/object:Gem::Version
|
113
|
+
hash: 3
|
114
|
+
segments:
|
115
|
+
- 0
|
93
116
|
version: "0"
|
94
|
-
version:
|
95
117
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
96
119
|
requirements:
|
97
120
|
- - ">="
|
98
121
|
- !ruby/object:Gem::Version
|
122
|
+
hash: 11
|
123
|
+
segments:
|
124
|
+
- 1
|
125
|
+
- 2
|
99
126
|
version: "1.2"
|
100
|
-
version:
|
101
127
|
requirements: []
|
102
128
|
|
103
129
|
rubyforge_project:
|
104
|
-
rubygems_version: 1.3.
|
130
|
+
rubygems_version: 1.3.7
|
105
131
|
signing_key:
|
106
|
-
specification_version:
|
132
|
+
specification_version: 3
|
107
133
|
summary: A Ruby library for parsing bicycle powermeter data.
|
108
134
|
test_files: []
|
109
135
|
|
data/lib/joule/float.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
# Copyright (c) 2004-2009 David Heinemeier Hansson
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
-
# a copy of this software and associated documentation files (the
|
5
|
-
# "Software"), to deal in the Software without restriction, including
|
6
|
-
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
-
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
-
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
-
# the following conditions:
|
10
|
-
#
|
11
|
-
# The above copyright notice and this permission notice shall be
|
12
|
-
# included in all copies or substantial portions of the Software.
|
13
|
-
#
|
14
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
-
|
22
|
-
class Float
|
23
|
-
remove_method :round
|
24
|
-
|
25
|
-
# Rounds the float with the specified precision.
|
26
|
-
#
|
27
|
-
# x = 1.337
|
28
|
-
# x.round # => 1
|
29
|
-
# x.round(1) # => 1.3
|
30
|
-
# x.round(2) # => 1.34
|
31
|
-
def round(precision = nil)
|
32
|
-
if precision
|
33
|
-
magnitude = 10.0 ** precision
|
34
|
-
(self * magnitude).round / magnitude
|
35
|
-
else
|
36
|
-
super()
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|