<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DZone Snippets: motionbased code</title>
    <link>http://snippets.dzone.com/posts</link>
    <pubDate>Sun, 27 Jul 2008 00:46:33 GMT</pubDate>
    <description>DZone Snippets: motionbased code</description>
    <item>
      <title>Garmin Forerunner TCX file processing</title>
      <link>http://snippets.dzone.com/posts/show/3712</link>
      <description>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.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;// insert code here..&lt;br /&gt;require "date"&lt;br /&gt;require "rexml/document"&lt;br /&gt;require 'rubygems'&lt;br /&gt;require 'RMagick'&lt;br /&gt;&lt;br /&gt;class ForeRunner&lt;br /&gt;  &lt;br /&gt;  M2MI = 1609.344 # meters to miles&lt;br /&gt;  MINIMUM_LAP_TIME = 300 # minimum seconds to count as a full lap&lt;br /&gt;  &lt;br /&gt;  attr_accessor :laps, :total_time, :distance, :calories&lt;br /&gt;  &lt;br /&gt;  def initialize(file)&lt;br /&gt;    @source_doc = REXML::Document.new file&lt;br /&gt;    &lt;br /&gt;    @laps = 0&lt;br /&gt;    @lap_times = Array.new&lt;br /&gt;    @lap_bpm = Array.new&lt;br /&gt;    @full_laps = 0&lt;br /&gt;&lt;br /&gt;    @total_time = 0    &lt;br /&gt;    @distance = 0&lt;br /&gt;    @calories = 0&lt;br /&gt;    @map_data = []&lt;br /&gt;    &lt;br /&gt;    self.process_file  &lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def generate_infographic(output_filename)&lt;br /&gt;    canvas = Magick::Image.new(250, 80)&lt;br /&gt;    map_size = 50&lt;br /&gt;    map_color = 'green'&lt;br /&gt;&lt;br /&gt;    max_lap_height = 25&lt;br /&gt;    &lt;br /&gt;    gc = Magick::Draw.new&lt;br /&gt;&lt;br /&gt;    # Draw ellipse&lt;br /&gt;    gc.stroke('grey50')&lt;br /&gt;    gc.stroke_width(2)&lt;br /&gt;    gc.fill_opacity(0)&lt;br /&gt;&lt;br /&gt;    # draw the relative lap times&lt;br /&gt;    lap = 0&lt;br /&gt;    max_time = @lap_times.max&lt;br /&gt;    @lap_times.each do |s|&lt;br /&gt;      lap += 1&lt;br /&gt;      x = 10 + (lap * 5)&lt;br /&gt;      y = 60 - (s / (max_time / max_lap_height))&lt;br /&gt;      gc.line(x, 60, x, y)&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    #draw the heartbeat avg&lt;br /&gt;    gc.stroke('#c9a')&lt;br /&gt;    &lt;br /&gt;    lap = 0&lt;br /&gt;    lx = nil&lt;br /&gt;    ly = nil&lt;br /&gt;    max_bpm = @lap_bpm.max&lt;br /&gt;    min_bpm = @lap_bpm.min&lt;br /&gt;    &lt;br /&gt;    max_bpm_height = 18&lt;br /&gt;    &lt;br /&gt;    @lap_bpm.each do |s|&lt;br /&gt;      lap += 1&lt;br /&gt;      x = 10 + (lap * 5)&lt;br /&gt;      y = 21 - ( (s - min_bpm) / ((max_bpm - min_bpm) / max_bpm_height))&lt;br /&gt;      if !lx&lt;br /&gt;        lx = x&lt;br /&gt;        ly = y&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;      gc.line(lx, ly, x, y)&lt;br /&gt;&lt;br /&gt;      lx = x&lt;br /&gt;      ly = y&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    # draw the map&lt;br /&gt;    lat_diff = (@map_data['max_lat'] - @map_data['min_lat']).abs&lt;br /&gt;    lon_diff = (@map_data['max_lon'] - @map_data['min_lon']).abs&lt;br /&gt;&lt;br /&gt;    lat_off = lat_diff / map_size&lt;br /&gt;    lon_off = lon_diff / map_size&lt;br /&gt;    &lt;br /&gt;    gc.fill(map_color)&lt;br /&gt;    @map_data['map_data'].each do |i|&lt;br /&gt;      lt = (map_size - ((i[0] - @map_data['min_lat']) / lat_off)).round&lt;br /&gt;      lg = ((i[1] - @map_data['min_lon']) / lon_off).round&lt;br /&gt;      gc.point((240 - map_size) + lg, (65 - map_size) + lt)&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    # Annotate&lt;br /&gt;    gc.stroke('transparent')&lt;br /&gt;    gc.fill('black')&lt;br /&gt;    gc.text(120, 15, @distance.to_s[0, 5] + ' mi')&lt;br /&gt;    gc.text(120, 30, (@total_time / 60 / 60).to_s[0, 4] + ' hr')&lt;br /&gt;    gc.text(120, 45, @calories.to_s + ' cal')&lt;br /&gt;&lt;br /&gt;    gc.fill('#555')&lt;br /&gt;    total_hr = 0 &lt;br /&gt;    @lap_bpm.each { |hr| total_hr += hr }&lt;br /&gt;    gc.text(15, 32, 'avg bpm: ' + (total_hr / @laps).round.to_s)&lt;br /&gt;    gc.text(15, 75, 'avg pace: ' + ((@total_time / @full_laps) / 60).to_s[0, 4] + ' min/lap')&lt;br /&gt;&lt;br /&gt;    gc.draw(canvas)&lt;br /&gt;    canvas.write(output_filename)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  protected &lt;br /&gt;  &lt;br /&gt;  def process_file&lt;br /&gt;    @source_doc.elements.each('TrainingCenterDatabase/Activities/Activity/Lap/*') do |element| &lt;br /&gt;      if element.name == "TotalTimeSeconds"&lt;br /&gt;        @lap_times &lt;&lt; element.text.to_f&lt;br /&gt;        if element.text.to_f &gt; 300  # for removing warmup and warmdown laps&lt;br /&gt;          @total_time += element.text.to_f&lt;br /&gt;          @full_laps += 1&lt;br /&gt;        end&lt;br /&gt;        @laps += 1&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;      if element.name == "DistanceMeters" &lt;br /&gt;        @distance += (element.text.to_f / M2MI)&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;      if element.name == "Calories" &lt;br /&gt;        @calories += element.text.to_f&lt;br /&gt;      end&lt;br /&gt;&lt;br /&gt;      if element.name == "AverageHeartRateBpm" &lt;br /&gt;        element.elements.each('Value') { |v| @lap_bpm &lt;&lt; v.text.to_f }&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;    @map_data = self.generate_map_points&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def generate_map_points  &lt;br /&gt;    map = []&lt;br /&gt;&lt;br /&gt;    max_lat = -300&lt;br /&gt;    max_lon = -300&lt;br /&gt;    min_lat = 300&lt;br /&gt;    min_lon = 300&lt;br /&gt;&lt;br /&gt;    @source_doc.elements.each('TrainingCenterDatabase/Activities/Activity/Lap/Track/Trackpoint/*') do |element|&lt;br /&gt;      if element.elements['LatitudeDegrees']&lt;br /&gt;        lat = element.elements['LatitudeDegrees'].text.to_f&lt;br /&gt;        lon = element.elements['LongitudeDegrees'].text.to_f&lt;br /&gt;&lt;br /&gt;        map &lt;&lt; [lat, lon]&lt;br /&gt;        max_lat = lat if (lat &gt; max_lat) &lt;br /&gt;        max_lon = lon if (lon &gt; max_lon) &lt;br /&gt;        min_lat = lat if (lat &lt; min_lat) &lt;br /&gt;        min_lon = lon if (lon &lt; min_lon) &lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;    {'map_data' =&gt; map, 'max_lat' =&gt; max_lat, 'min_lat' =&gt; min_lat, 'max_lon' =&gt; max_lon, 'min_lon' =&gt; min_lon}&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if ARGV[0].nil? &lt;br /&gt;  puts "This program takes one argument, a Garmin Forerunner TCX File"&lt;br /&gt;  puts "Like this: run.rb filename.tcx"&lt;br /&gt;  exit&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;output_filename = ARGV[0].gsub(/\.tcx$/, "-" + Time.now.strftime("%Y%m%d") + ".png")&lt;br /&gt;puts "Generating Graph #{output_filename}"&lt;br /&gt;&lt;br /&gt;fr = ForeRunner.new(File.new(ARGV[0]))&lt;br /&gt;fr.generate_infographic(output_filename)&lt;br /&gt;&lt;/code&gt;</description>
      <pubDate>Wed, 21 Mar 2007 19:05:29 GMT</pubDate>
      <guid>http://snippets.dzone.com/posts/show/3712</guid>
      <author>schacon (Scott Chacon)</author>
    </item>
  </channel>
</rss>
