This is a small ruby file I wrote to process the TCX file that I can download from MotionBased website that is processed from the data off my Garmin Forerunner 305. It processes the XML file and generates a little badge/infographic that I can put on a website.
1
2 // insert code here..
3 require "date"
4 require "rexml/document"
5 require 'rubygems'
6 require 'RMagick'
7
8 class ForeRunner
9
10 M2MI = 1609.344
11 MINIMUM_LAP_TIME = 300
12
13 attr_accessor :laps, :total_time, :distance, :calories
14
15 def initialize(file)
16 @source_doc = REXML::Document.new file
17
18 @laps = 0
19 @lap_times = Array.new
20 @lap_bpm = Array.new
21 @full_laps = 0
22
23 @total_time = 0
24 @distance = 0
25 @calories = 0
26 @map_data = []
27
28 self.process_file
29 end
30
31 def generate_infographic(output_filename)
32 canvas = Magick::Image.new(250, 80)
33 map_size = 50
34 map_color = 'green'
35
36 max_lap_height = 25
37
38 gc = Magick::Draw.new
39
40
41 gc.stroke('grey50')
42 gc.stroke_width(2)
43 gc.fill_opacity(0)
44
45
46 lap = 0
47 max_time = @lap_times.max
48 @lap_times.each do |s|
49 lap += 1
50 x = 10 + (lap * 5)
51 y = 60 - (s / (max_time / max_lap_height))
52 gc.line(x, 60, x, y)
53 end
54
55
56 gc.stroke('#c9a')
57
58 lap = 0
59 lx = nil
60 ly = nil
61 max_bpm = @lap_bpm.max
62 min_bpm = @lap_bpm.min
63
64 max_bpm_height = 18
65
66 @lap_bpm.each do |s|
67 lap += 1
68 x = 10 + (lap * 5)
69 y = 21 - ( (s - min_bpm) / ((max_bpm - min_bpm) / max_bpm_height))
70 if !lx
71 lx = x
72 ly = y
73 end
74
75 gc.line(lx, ly, x, y)
76
77 lx = x
78 ly = y
79 end
80
81
82 lat_diff = (@map_data['max_lat'] - @map_data['min_lat']).abs
83 lon_diff = (@map_data['max_lon'] - @map_data['min_lon']).abs
84
85 lat_off = lat_diff / map_size
86 lon_off = lon_diff / map_size
87
88 gc.fill(map_color)
89 @map_data['map_data'].each do |i|
90 lt = (map_size - ((i[0] - @map_data['min_lat']) / lat_off)).round
91 lg = ((i[1] - @map_data['min_lon']) / lon_off).round
92 gc.point((240 - map_size) + lg, (65 - map_size) + lt)
93 end
94
95
96 gc.stroke('transparent')
97 gc.fill('black')
98 gc.text(120, 15, @distance.to_s[0, 5] + ' mi')
99 gc.text(120, 30, (@total_time / 60 / 60).to_s[0, 4] + ' hr')
100 gc.text(120, 45, @calories.to_s + ' cal')
101
102 gc.fill('#555')
103 total_hr = 0
104 @lap_bpm.each { |hr| total_hr += hr }
105 gc.text(15, 32, 'avg bpm: ' + (total_hr / @laps).round.to_s)
106 gc.text(15, 75, 'avg pace: ' + ((@total_time / @full_laps) / 60).to_s[0, 4] + ' min/lap')
107
108 gc.draw(canvas)
109 canvas.write(output_filename)
110 end
111
112 protected
113
114 def process_file
115 @source_doc.elements.each('TrainingCenterDatabase/Activities/Activity/Lap/*') do |element|
116 if element.name == "TotalTimeSeconds"
117 @lap_times << element.text.to_f
118 if element.text.to_f > 300
119 @total_time += element.text.to_f
120 @full_laps += 1
121 end
122 @laps += 1
123 end
124
125 if element.name == "DistanceMeters"
126 @distance += (element.text.to_f / M2MI)
127 end
128
129 if element.name == "Calories"
130 @calories += element.text.to_f
131 end
132
133 if element.name == "AverageHeartRateBpm"
134 element.elements.each('Value') { |v| @lap_bpm << v.text.to_f }
135 end
136 end
137 @map_data = self.generate_map_points
138 end
139
140 def generate_map_points
141 map = []
142
143 max_lat = -300
144 max_lon = -300
145 min_lat = 300
146 min_lon = 300
147
148 @source_doc.elements.each('TrainingCenterDatabase/Activities/Activity/Lap/Track/Trackpoint/*') do |element|
149 if element.elements['LatitudeDegrees']
150 lat = element.elements['LatitudeDegrees'].text.to_f
151 lon = element.elements['LongitudeDegrees'].text.to_f
152
153 map << [lat, lon]
154 max_lat = lat if (lat > max_lat)
155 max_lon = lon if (lon > max_lon)
156 min_lat = lat if (lat < min_lat)
157 min_lon = lon if (lon < min_lon)
158 end
159 end
160 {'map_data' => map, 'max_lat' => max_lat, 'min_lat' => min_lat, 'max_lon' => max_lon, 'min_lon' => min_lon}
161 end
162
163
164 end
165
166
167 if ARGV[0].nil?
168 puts "This program takes one argument, a Garmin Forerunner TCX File"
169 puts "Like this: run.rb filename.tcx"
170 exit
171 end
172
173 output_filename = ARGV[0].gsub(/\.tcx$/, "-" + Time.now.strftime("%Y%m%d") + ".png")
174 puts "Generating Graph #{output_filename}"
175
176 fr = ForeRunner.new(File.new(ARGV[0]))
177 fr.generate_infographic(output_filename)