#!/usr/bin/env ruby # The above line makes the script executable - don't forget to chmod +x the file as well. # # Usage: # './parseruby.rb gpxfilename' # # Uses the hpricot library to parse gpx files # require 'rubygems' require 'hpricot' # Trackpt class contains points with latitude, longitude and elevation # class Trackpt attr_accessor :lat, :lon, :ele def initialize(lat,lon,ele) @lat = lat @lon = lon @ele = ele end end # Calculate distance on globe between two Coord points; result in m; using earth radius of 6371 km # def calc_distance(lat1, lon1, lat2, lon2) diff = 2 * Math.asin(Math.sqrt((Math.sin((lat1 - lat2)/2))**2 + Math.cos(lat1)*Math.cos(lat2)*(Math.sin((lon1-lon2)/2))**2)) diff *= 6371.0 * 1000 return diff end # Some variables; elarray contains the raw elevation data, elfiltered the low-pass filtered elevation data (simple discrete filter based on Euler differentation), # elfilteredhyst the filtered and hysteresis-quantized elevation data, diff the metric distance between the trackpoints # lonarray = Array.new latarray = Array.new elarray = Array.new elfiltered = Array.new elfilteredhyst = Array.new ptarray = Array.new diff = Array.new meter2ft = 3.28084 km2miles = 0.62137 # Uses command line parameter as filename; alternatively, use something like # puts 'Enter filename:' # filename = gets.chomp # here to prompt for filename # filename = ARGV[0] # Open GPX file and parse it # begin doc = open(filename) { |f| Hpricot::XML(f) } # Rescue loop that prompts for filename if file not found or not entered correctly as command-line argument # rescue begin puts 'File not found; please enter filename:' filename = gets.chomp doc = open(filename) { |f| Hpricot::XML(f) } rescue retry end end (doc/:trkpt).each do |trkpt| latarray.push(trkpt.attributes['lat']) lonarray.push(trkpt.attributes['lon']) elarray.push(trkpt.at("ele").innerHTML) end # Some initialization # prev_ele = (elarray[1].to_f * meter2ft).round prev_pt = Trackpt.new(latarray[1].to_f * Math::PI/180,lonarray[1].to_f * Math::PI/180,prev_ele) prev_ele_hyst = prev_ele dist = 0.0 eltotal = 0 desctotal = 0 # 'Smoothing' constant - similar to the rc time constant in first order systems but here in the spatial domain rc = 40 # 'Hysteresis' constant (in feet) hyst = 3 # Calculate distance array, filter elevation data, calculate ascent and descent totals # elarray.each_index do |i| ptarray[i] = Trackpt.new(latarray[i].to_f * Math::PI/180, lonarray[i].to_f * Math::PI/180, (elarray[i].to_f * meter2ft).round) diff[i] = calc_distance(ptarray[i].lat, ptarray[i].lon, prev_pt.lat, prev_pt.lon) * meter2ft if (diff[i] + rc) == 0: elfiltered[i] = prev_ele else alpha = diff[i]/(diff[i] + rc) elfiltered[i] = (alpha * ptarray[i].ele + (1-alpha) * prev_ele) end elfilteredhyst[i] = case when elfiltered[i] > (prev_ele_hyst + hyst): elfiltered[i].round when elfiltered[i] < (prev_ele_hyst - hyst): elfiltered[i].round else prev_ele_hyst end if (elfilteredhyst[i]-prev_ele_hyst) > 0: eltotal += elfilteredhyst[i]-prev_ele_hyst elsif (elfilteredhyst[i]-prev_ele_hyst) < 0: desctotal += prev_ele_hyst-elfilteredhyst[i] end prev_pt = ptarray[i] prev_ele = elfiltered[i] prev_ele_hyst = elfilteredhyst[i] dist += diff[i] end # Output totals in miles/feet, write distance + elevation + filtered + filtered-quantized elevation data in text format (separated by tabs) to output file named 'outputdata.txt' # ...includes some messing around to get the miles in two digits after the decimal point # puts "Total distance: #{(dist/(10 * meter2ft) * km2miles).round/100.to_f} miles" puts "Total elevation gain: #{eltotal} feet" puts "Total elevation loss: #{desctotal} feet" outputfile = File.new('outputdata.txt','w') diff.each_index do |i| outputfile.puts "#{diff[i].round}\t#{ptarray[i].ele}\t#{elfiltered[i].round}\t#{elfilteredhyst[i]}" end outputfile.close