From 371a3fa0b971c9a66c66f941eb4f5c3fa18e424f Mon Sep 17 00:00:00 2001 From: Doug Fales Date: Sat, 14 Oct 2006 13:20:23 +0000 Subject: [PATCH] Initial import of gpx gem. --- README | 43 + Rakefile | 81 + lib/gpx.rb | 37 + lib/gpx/bounds.rb | 83 + lib/gpx/gpx.rb | 53 + lib/gpx/gpx_file.rb | 222 + lib/gpx/magellan_track_log.rb | 134 + lib/gpx/point.rb | 103 + lib/gpx/route.rb | 65 + lib/gpx/segment.rb | 217 + lib/gpx/track.rb | 149 + lib/gpx/trackpoint.rb | 35 + lib/gpx/waypoint.rb | 73 + tests/gpx_files/arches.gpx | 1 + tests/gpx_files/magellan_track.log | 306 ++ tests/gpx_files/one_segment.gpx | 770 ++++ tests/gpx_files/one_track.gpx | 756 ++++ tests/gpx_files/routes.gpx | 1 + tests/gpx_files/tracks.gpx | 6304 ++++++++++++++++++++++++++++ tests/gpx_files/waypoints.gpx | 1 + tests/magellan_test.rb | 18 + tests/segment_test.rb | 57 + tests/track_file_test.rb | 75 + tests/track_test.rb | 65 + 24 files changed, 9649 insertions(+) create mode 100644 README create mode 100644 Rakefile create mode 100644 lib/gpx.rb create mode 100644 lib/gpx/bounds.rb create mode 100644 lib/gpx/gpx.rb create mode 100644 lib/gpx/gpx_file.rb create mode 100644 lib/gpx/magellan_track_log.rb create mode 100644 lib/gpx/point.rb create mode 100644 lib/gpx/route.rb create mode 100644 lib/gpx/segment.rb create mode 100644 lib/gpx/track.rb create mode 100644 lib/gpx/trackpoint.rb create mode 100644 lib/gpx/waypoint.rb create mode 100644 tests/gpx_files/arches.gpx create mode 100755 tests/gpx_files/magellan_track.log create mode 100644 tests/gpx_files/one_segment.gpx create mode 100644 tests/gpx_files/one_track.gpx create mode 100644 tests/gpx_files/routes.gpx create mode 100644 tests/gpx_files/tracks.gpx create mode 100644 tests/gpx_files/waypoints.gpx create mode 100644 tests/magellan_test.rb create mode 100644 tests/segment_test.rb create mode 100644 tests/track_file_test.rb create mode 100644 tests/track_test.rb diff --git a/README b/README new file mode 100644 index 0000000..443be5b --- /dev/null +++ b/README @@ -0,0 +1,43 @@ += GPX Gem +Copyright (C) 2006 Doug Fales +Doug Fales mailto:doug.fales@gmail.com + +== What It Does +This library reads GPX files and provides an API for reading and manipulating +the data as objects. For more info on the GPX format, see +http://www.topografix.com/gpx.asp. + +In addition to parsing GPX files, this library is capable of converting +Magellan NMEA files to GPX, and writing new GPX files. It can crop and delete +rectangular areas within a file, and it also calculates some meta-data about +the tracks and points in a file (such as distance, duration, average speed, +etc). + +== Examples +Reading a GPX file, and cropping its contents to a given area: + gpx = GPX::GPXFile.new(:gpx_file => filename) # Read GPX file + bounds = GPX::Bounds.new(params) # Create a rectangular area to crop + gpx.crop(bounds) # Crop it + gpx.write(filename) # Save it + +Converting a Magellan track log to GPX: + if GPX::MagellanTrackLog::is_magellan_file?(filename) + GPX::MagellanTrackLog::convert_to_gpx(filename, "#{filename}.gpx") + end + + +== Notes +This library was written to bridge the gap between my Garmin Geko +and my website, WalkingBoss.org. For that reason, it has always been more of a +work-in-progress than an attempt at full GPX compliance. The track side of the +library has seen much more use than the route/waypoint side, so if you're doing +something with routes or waypoints, you may need to tweak some things. + +Since this code uses REXML to read an entire GPX file into memory, it is not +the fastest possible solution for working with GPX data, especially if you are +working with tracks from several days or weeks. + +Finally, it should be noted that none of the distance/speed calculation or +crop/delete code has been tested under International Date Line-crossing +conditions. That particular part of the code will likely be unreliable if +you're zig-zagging across 180 degrees longitude routinely. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..528d7a5 --- /dev/null +++ b/Rakefile @@ -0,0 +1,81 @@ +require 'rubygems' +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' +require 'rake/gempackagetask' +require File.dirname(__FILE__) + '/lib/gpx' + +PKG_VERSION = GPX::VERSION +PKG_NAME = "gpx" +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" +RUBY_FORGE_PROJECT = "gpx" +RUBY_FORGE_USER = ENV['RUBY_FORGE_USER'] || "dougfales" +RELEASE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" + +PKG_FILES = FileList[ + "lib/**/*", "bin/*", "tests/**/*", "[A-Z]*", "Rakefile", "doc/**/*" +] + +desc "Default Task" +task :default => [ :test ] + +# Run the unit tests +desc "Run all unit tests" +Rake::TestTask.new("test") { |t| + t.libs << "lib" + t.pattern = 'tests/*_test.rb' + t.verbose = true +} + +# Make a console, useful when working on tests +desc "Generate a test console" +task :console do + verbose( false ) { sh "irb -I lib/ -r 'gpx'" } +end + +# Genereate the RDoc documentation +desc "Create documentation" +Rake::RDocTask.new("doc") { |rdoc| + rdoc.title = "Ruby GPX API" + rdoc.rdoc_dir = 'html' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +} + +# Genereate the package +spec = Gem::Specification.new do |s| + + s.name = 'gpx' + s.version = PKG_VERSION + s.summary = <<-EOF + A basic API for reading and writing GPX files. + EOF + s.description = <<-EOF + A basic API for reading and writing GPX files. + EOF + + s.files = PKG_FILES + + s.require_path = 'lib' + s.autorequire = 'gpx' + + s.has_rdoc = true + + s.author = "Doug Fales" + s.email = "doug.fales@gmail.com" + s.homepage = "http://gpx.rubyforge.com/" +end + +Rake::GemPackageTask.new(spec) do |pkg| + pkg.need_zip = true + pkg.need_tar = true +end + +desc "Report code statistics (KLOCs, etc) from the application" +task :stats do + require 'code_statistics' + CodeStatistics.new( + ["Library", "lib"], + ["Units", "tests"] + ).to_s +end diff --git a/lib/gpx.rb b/lib/gpx.rb new file mode 100644 index 0000000..eaa6489 --- /dev/null +++ b/lib/gpx.rb @@ -0,0 +1,37 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ +$:.unshift(File.dirname(__FILE__)) +require 'rexml/document' +require 'date' +require 'time' +require 'csv' +require 'gpx/gpx' +require 'gpx/gpx_file' +require 'gpx/bounds' +require 'gpx/track' +require 'gpx/route' +require 'gpx/segment' +require 'gpx/point' +require 'gpx/trackpoint' +require 'gpx/waypoint' +require 'gpx/magellan_track_log' diff --git a/lib/gpx/bounds.rb b/lib/gpx/bounds.rb new file mode 100644 index 0000000..8e6eee7 --- /dev/null +++ b/lib/gpx/bounds.rb @@ -0,0 +1,83 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ +module GPX + class Bounds < Base + attr_accessor :min_lat, :max_lat, :max_lon, :min_lon, :center_lat, :center_lon + + # Creates a new bounds object with the passed-in min and max longitudes + # and latitudes. + def initialize(opts = { :min_lat => 90.0, :max_lat => -90.0, :min_lon => 180.0, :max_lon => -180.0}) + @min_lat, @max_lat = opts[:min_lat].to_f, opts[:max_lat].to_f + @min_lon, @max_lon = opts[:min_lon].to_f, opts[:max_lon].to_f + end + + # Returns the middle latitude. + def center_lat + distance = (max_lat - min_lat)/2.0 + (min_lat + distance) + end + + # Returns the middle longitude. + def center_lon + distance = (max_lon - min_lon)/2.0 + (min_lon + distance) + end + + def to_xml + bnd = REXML::Element.new('bounds') + bnd.attributes['min_lat'] = min_lat + bnd.attributes['min_lon'] = min_lon + bnd.attributes['max_lat'] = max_lat + bnd.attributes['max_lon'] = max_lon + bnd + end + + # Returns true if the pt is within these bounds. + def contains?(pt) + (pt.lat >= min_lat and pt.lat <= max_lat and pt.lon >= min_lon and pt.lon <= max_lon) + end + + # Adds an item to itself, expanding its min/max lat/lon as needed to + # contain the given item. The item can be either another instance of + # Bounds or a Point. + def add(item) + if(item.respond_to?(:lat) and item.respond_to?(:lon)) + @min_lat = item.lat if item.lat < @min_lat + @min_lon = item.lon if item.lon < @min_lon + @max_lat = item.lat if item.lat > @max_lat + @max_lon = item.lon if item.lon > @max_lon + else + @min_lat = item.min_lat if item.min_lat < @min_lat + @min_lon = item.min_lon if item.min_lon < @min_lon + @max_lat = item.max_lat if item.max_lat > @max_lat + @max_lon = item.max_lon if item.max_lon > @max_lon + end + end + + # Returns the min_lat, min_lon, max_lat, and max_lon in a labeled string. + def to_s + "min_lat: #{min_lat} min_lon: #{min_lon} max_lat: #{max_lat} max_lon: #{max_lon}" + end + + end +end diff --git a/lib/gpx/gpx.rb b/lib/gpx/gpx.rb new file mode 100644 index 0000000..9ae5ffd --- /dev/null +++ b/lib/gpx/gpx.rb @@ -0,0 +1,53 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ +module GPX + VERSION = "0.1" + + # A common base class which provides a useful initializer method to many + # class in the GPX library. + class Base + include REXML + + # This initializer can take a REXML::Element and scrape out any text + # elements with the names given in the "text_elements" array. Each + # element found underneath "parent" with a name in "text_elements" causes + # an attribute to be initialized on the instance. This means you don't + # have to pick out individual text elements in each initializer of each + # class (Route, TrackPoint, Track, etc). Just pass an array of possible + # attributes to this method. + def instantiate_with_text_elements(parent, text_elements) + text_elements.each do |el| + unless parent.elements[el].nil? + val = parent.elements[el].text + code = <<-code + attr_accessor #{ el } + #{el}=#{val} + code + class_eval code + end + end + + end + + end +end diff --git a/lib/gpx/gpx_file.rb b/lib/gpx/gpx_file.rb new file mode 100644 index 0000000..3caf234 --- /dev/null +++ b/lib/gpx/gpx_file.rb @@ -0,0 +1,222 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ +module GPX + class GPXFile < Base + attr_reader :tracks, :routes, :waypoints, :bounds, :lowest_point, :highest_point, :distance, :duration, :average_speed + + + # This initializer can be used to create a new GPXFile from an existing + # file or to create a new GPXFile instance with no data (so that you can + # add tracks and points and write it out to a new file later). + # To read an existing GPX file, do this: + # gpx_file = GPXFile.new(:gpx_file => 'mygpxfile.gpx') + # puts "Speed: #{gpx_file.average_speed}" + # puts "Duration: #{gpx_file.duration}" + # puts "Bounds: #{gpx_file.bounds}" + # + # To create a new blank GPXFile instance: + # gpx_file = GPXFile.new + # Note that you can pass in any instance variables to this form of the initializer, including Tracks or Segments: + # some_track = get_track_from_csv('some_other_format.csv') + # gpx_file = GPXFile.new(:tracks => [some_track]) + # + def initialize(opts = {}) + @duration = 0 + if(opts[:gpx_file]) + gpx_file = opts[:gpx_file] + case gpx_file + when String + gpx_file = File.open(gpx_file) + end + reset_meta_data + @xml = Document.new(gpx_file, :ignore_whitespace_nodes => :all) + + bounds_element = (XPath.match(@xml, "/gpx/metadata/bounds").first rescue nil) + if bounds_element + @bounds.min_lat = bounds_element.attributes["min_lat"].to_f + @bounds.min_lon = bounds_element.attributes["min_lon"].to_f + @bounds.max_lat = bounds_element.attributes["max_lat"].to_f + @bounds.max_lon = bounds_element.attributes["max_lon"].to_f + else + get_bounds = true + end + + @tracks = XPath.match(@xml, "/gpx/trk").collect do |trk| + trk = Track.new(:element => trk, :gpx_file => self) + update_meta_data(trk, get_bounds) + trk + end + @waypoints = XPath.match(@xml, "/gpx/wpt").collect { |wpt| Waypoint.new(:element => wpt, :gpx_file => self) } + @routes = XPath.match(@xml, "/gpx/rte").collect { |rte| Route.new(:element => rte, :gpx_file => self) } + + @tracks.delete_if { |t| t.empty? } + + calculate_duration + else + reset_meta_data + opts.each { |attr_name, value| instance_variable_set("@#{attr_name.to_s}", value) } + unless(@tracks.nil? or @tracks.size.zero?) + @tracks.each { |trk| update_meta_data(trk) } + calculate_duration + end + end + end + + # Returns the distance, in kilometers, meters, or miles, of all of the + # tracks and segments contained in this GPXFile. + def distance(opts = { :units => 'kilometers' }) + case opts[:units] + when /kilometers/i + return @distance + when /meters/i + return (@distance * 1000) + when /miles/i + return (@distance * 0.62) + end + end + + # Returns the average speed, in km/hr, meters/hr, or miles/hr, of this + # GPXFile. The calculation is based on the total distance divided by the + # total duration of the entire file. + def average_speed(opts = { :units => 'kilometers' }) + case opts[:units] + when /kilometers/i + return @distance / (@duration/3600.0) + when /meters/i + return (@distance * 1000) / (@duration/3600.0) + when /miles/i + return (@distance * 0.62) / (@duration/3600.0) + end + end + + # Crops any points falling within a rectangular area. Identical to the + # delete_area method in every respect except that the points outside of + # the given area are deleted. Note that this method automatically causes + # the meta data to be updated after deletion. + def crop(area) + reset_meta_data + keep_tracks = [] + tracks.each do |trk| + trk.crop(area) + unless trk.empty? + update_meta_data(trk) + keep_tracks << trk + end + end + @tracks = keep_tracks + routes.each { |rte| rte.crop(area) } + waypoints.each { |wpt| wpt.crop(area) } + end + + # Deletes any points falling within a rectangular area. The "area" + # parameter is usually an instance of the Bounds class. Note that this + # method cascades into similarly named methods of subordinate classes + # (i.e. Track, Segment), which means, if you want the deletion to apply + # to all the data, you only call this one (and not the one in Track or + # Segment classes). Note that this method automatically causes the meta + # data to be updated after deletion. + def delete_area(area) + reset_meta_data + keep_tracks = [] + tracks.each do |trk| + trk.delete_area(area) + unless trk.empty? + update_meta_data(trk) + keep_tracks << trk + end + end + @tracks = keep_tracks + routes.each { |rte| rte.delete_area(area) } + waypoints.each { |wpt| wpt.delete_area(area) } + end + + # Resets the meta data for this GPX file. Meta data includes the bounds, + # the high and low points, and the distance. + def reset_meta_data + @bounds = Bounds.new + @highest_point = nil + @lowest_point = nil + @distance = 0.0 + end + + # Updates the meta data for this GPX file. Meta data includes the + # bounds, the high and low points, and the distance. This is useful when + # you modify the GPX data (i.e. by adding or deleting points) and you + # want the meta data to accurately reflect the new data. + def update_meta_data(trk, get_bounds = true) + @lowest_point = trk.lowest_point if(@lowest_point.nil? or trk.lowest_point.elevation < @lowest_point.elevation) + @highest_point = trk.highest_point if(@highest_point.nil? or trk.highest_point.elevation > @highest_point.elevation) + @bounds.add(trk.bounds) if get_bounds + @distance += trk.distance + end + + # Serialize the current GPXFile to a gpx file named . + # If the file does not exist, it is created. If it does exist, it is overwritten. + def write(filename) + + doc = Document.new + gpx_elem = Element.new('gpx') + doc.add(gpx_elem) + gpx_elem.attributes['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" + gpx_elem.attributes['xmlns'] = "http://www.topografix.com/GPX/1/1" + gpx_elem.attributes['version'] = "1.1" + gpx_elem.attributes['creator'] = "GPX RubyGem 0.1 Copyright 2006 Doug Fales -- http://walkingboss.com" + gpx_elem.attributes['xsi:schemaLocation'] = "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" + + meta_data_elem = Element.new('metadata') + name_elem = Element.new('name') + name_elem.text = File.basename(filename) + meta_data_elem.elements << name_elem + + time_elem = Element.new('time') + time_elem.text = Time.now.xmlschema + meta_data_elem.elements << time_elem + + meta_data_elem.elements << bounds.to_xml + + gpx_elem.elements << meta_data_elem + + tracks.each { |t| gpx_elem.add_element t.to_xml } unless tracks.nil? + waypoints.each { |w| gpx_elem.add_element w.to_xml } unless waypoints.nil? + routes.each { |r| gpx_elem.add_element r.to_xml } unless routes.nil? + + File.open(filename, 'w') { |f| doc.write(f) } + end + + private + + # Calculates and sets the duration attribute by subtracting the time on + # the very first point from the time on the very last point. + def calculate_duration + @duration = 0 + if(@tracks.nil? or @tracks.size.zero? or @tracks[0].segments.nil? or @tracks[0].segments.size.zero?) + return @duration + end + @duration = (@tracks[-1].segments[-1].points[-1].time - @tracks.first.segments.first.points.first.time) + rescue + @duration = 0 + end + + + end +end diff --git a/lib/gpx/magellan_track_log.rb b/lib/gpx/magellan_track_log.rb new file mode 100644 index 0000000..e2fa052 --- /dev/null +++ b/lib/gpx/magellan_track_log.rb @@ -0,0 +1,134 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ +module GPX + + # This class will parse the lat/lon and time data from a Magellan track log, + # which is a NMEA formatted CSV list of points. + + class MagellanTrackLog + #PMGNTRK + # This message is to be used to transmit Track information (basically a list of previous position fixes) + # which is often displayed on the plotter or map screen of the unit. The first field in this message + # is the Latitude, followed by N or S. The next field is the Longitude followed by E or W. The next + # field is the altitude followed by “F” for feet or “M” for meters. The next field is + # the UTC time of the fix. The next field consists of a status letter of “A” to indicated that + # the data is valid, or “V” to indicate that the data is not valid. The last character field is + # the name of the track, for those units that support named tracks. The last field contains the UTC date + # of the fix. Note that this field is (and its preceding comma) is only produced by the unit when the + # command PMGNCMD,TRACK,2 is given. It is not present when a simple command of PMGNCMD,TRACK is issued. + + #NOTE: The Latitude and Longitude Fields are shown as having two decimal + # places. As many additional decimal places may be added as long as the total + # length of the message does not exceed 82 bytes. + + # $PMGNTRK,llll.ll,a,yyyyy.yy,a,xxxxx,a,hhmmss.ss,A,c----c,ddmmyy*hh + require 'csv' + + LAT = 1 + LAT_HEMI = 2 + LON = 3 + LON_HEMI = 4 + ELE = 5 + ELE_UNITS = 6 + TIME = 7 + INVALID_FLAG = 8 + DATE = 10 + + FEET_TO_METERS = 0.3048 + + class << self + + # Takes the name of a magellan file, converts the contents to GPX, and + # writes the result to gpx_filename. + def convert_to_gpx(magellan_filename, gpx_filename) + + segment = Segment.new + + CSV.open(magellan_filename, "r") do |row| + next if(row.size < 10 or row[INVALID_FLAG] == 'V') + + lat_deg = row[LAT][0..1] + lat_min = row[LAT][2...-1] + lat_hemi = row[LAT_HEMI] + + lat = lat_deg.to_f + (lat_min.to_f / 60.0) + lat = (-lat) if(lat_hemi == 'S') + + lon_deg = row[LON][0..2] + lon_min = row[LON][3..-1] + lon_hemi = row[LON_HEMI] + + lon = lon_deg.to_f + (lon_min.to_f / 60.0) + lon = (-lon) if(lon_hemi == 'W') + + + ele = row[ELE] + ele_units = row[ELE_UNITS] + ele = ele.to_f + if(ele_units == 'F') + ele *= FEET_TO_METERS + end + + hrs = row[TIME][0..1].to_i + mins = row[TIME][2..3].to_i + secs = row[TIME][4..5].to_i + day = row[DATE][0..1].to_i + mon = row[DATE][2..3].to_i + yr = 2000 + row[DATE][4..5].to_i + + time = Time.gm(yr, mon, day, hrs, mins, secs) + + #must create point + pt = TrackPoint.new(:lat => lat, :lon => lon, :time => time, :elevation => ele) + segment.append_point(pt) + + end + + trk = Track.new + trk.append_segment(segment) + gpx_file = GPXFile.new(:tracks => [trk]) + gpx_file.write(gpx_filename) + + end + + # Tests to see if the given file is a magellan NMEA track log. + def is_magellan_file?(filename) + i = 0 + File.open(filename, "r") do |f| + f.each do |line| + i += 1 + if line =~ /^\$PMGNTRK/ + return true + elsif line =~ /<\?xml/ + return false + elsif(i > 10) + return false + end + end + end + return false + end + end + + end +end diff --git a/lib/gpx/point.rb b/lib/gpx/point.rb new file mode 100644 index 0000000..8aa0565 --- /dev/null +++ b/lib/gpx/point.rb @@ -0,0 +1,103 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ +include Math +module GPX + # The base class for all points. Trackpoint and Waypoint both descend from this base class. + class Point < Base + D_TO_R = PI/180.0; + attr_accessor :lat, :lon, :time, :elevation + + # When you need to manipulate individual points, you can create a Point + # object with a latitude, a longitude, an elevation, and a time. In + # addition, you can pass a REXML element to this initializer, and the + # relevant info will be parsed out. + def initialize(opts = {:lat => 0.0, :lon => 0.0, :elevation => 0.0, :time => Time.now } ) + if (opts[:element]) + elem = opts[:element] + @lat, @lon = elem.attributes["lat"].to_f, elem.attributes["lon"].to_f + @latr, @lonr = (D_TO_R * @lat), (D_TO_R * @lon) + #'-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)? + @time = (Time.xmlschema(elem.elements["time"].text) rescue nil) + @elevation = elem.elements["ele"].text.to_f if elem.elements["ele"] + else + @lat = opts[:lat] + @lon = opts[:lon] + @elevation = opts[:elevation] + @time = opts[:time] + end + + end + + + # Returns the latitude and longitude (in that order), separated by the + # given delimeter. This is useful for passing a point into another API + # (i.e. the Google Maps javascript API). + def lat_lon(delim = ', ') + "#{lat}#{delim}#{lon}" + end + + # Returns the longitude and latitude (in that order), separated by the + # given delimeter. This is useful for passing a point into another API + # (i.e. the Google Maps javascript API). + def lon_lat(delim = ', ') + "#{lon}#{delim}#{lat}" + end + + # Latitude in radians. + def latr + @latr ||= (@lat * D_TO_R) + end + + # Longitude in radians. + def lonr + @lonr ||= (@lon * D_TO_R) + end + + # Set the latitude (in degrees). + def lat=(latitude) + @latr = (latitude * D_TO_R) + @lat = latitude + end + + # Set the longitude (in degrees). + def lon=(longitude) + @lonr = (longitude * D_TO_R) + @lon = longitude + end + + # Convert this point to a REXML::Element. + def to_xml(elem_name = 'trkpt') + pt = Element.new('trkpt') + pt.attributes['lat'] = lat + pt.attributes['lon'] = lon + time_elem = Element.new('time') + time_elem.text = time.xmlschema + pt.elements << time_elem + elev = Element.new('ele') + elev.text = elevation + pt.elements << elev + pt + end + + end +end diff --git a/lib/gpx/route.rb b/lib/gpx/route.rb new file mode 100644 index 0000000..f3c1717 --- /dev/null +++ b/lib/gpx/route.rb @@ -0,0 +1,65 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ +module GPX + + # A Route in GPX is very similar to a Track, but it is created by a user + # from a series of Waypoints, whereas a Track is created by the GPS device + # automatically logging your progress at regular intervals. + class Route < Base + + attr_reader :points, :name, :gpx_file + + # Initialize a Route from a REXML::Element. + def initialize(opts = {}) + rte_element = opts[:element] + @gpx_file = opts[:gpx_file] + @name = rte_element.elements["child::name"].text + @points = [] + XPath.each(rte_element, "child::rtept") do |point| + @points << Point.new(:element => point) + end + + end + + # Delete points outside of a given area. + def crop(area) + points.delete_if{ |pt| not area.contains? pt } + end + + # Delete points within the given area. + def delete_area(area) + points.delete_if{ |pt| area.contains? pt } + end + + # Convert this Route to a REXML::Element. + def to_xml + rte = Element.new('rte') + name_elem = Element.new('name') + name_elem.text = name + rte.elements << name_elem + points.each { |rte_pt| rte.elements << rte_pt.to_xml('rtept') } + rte + end + + end +end diff --git a/lib/gpx/segment.rb b/lib/gpx/segment.rb new file mode 100644 index 0000000..0cb68d4 --- /dev/null +++ b/lib/gpx/segment.rb @@ -0,0 +1,217 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ +module GPX + + # A segment is the basic container in a GPX file. A Segment contains points + # (in this lib, they're called TrackPoints). A Track contains Segments. An + # instance of Segment knows its highest point, lowest point, earliest and + # latest points, distance, and bounds. + class Segment < Base + + attr_reader :earliest_point, :latest_point, :bounds, :highest_point, :lowest_point, :distance + attr_accessor :points, :track + + # If a REXML::Element object is passed-in, this will initialize a new + # Segment based on its contents. Otherwise, a blank Segment is created. + def initialize(opts = {}) + @track = opts[:track] + @points = [] + @earliest_point = nil + @latest_point = nil + @highest_point = nil + @lowest_point = nil + @distance = 0.0 + @bounds = Bounds.new + if(opts[:element]) + segment_element = opts[:element] + last_pt = nil + unless segment_element.is_a?(Text) + XPath.each(segment_element, "child::trkpt") do |trkpt| + pt = TrackPoint.new(:element => trkpt, :segment => self) + @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time) + @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time) + unless pt.elevation.nil? + @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation) + @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation) + end + @bounds.min_lat = pt.lat if pt.lat < @bounds.min_lat + @bounds.min_lon = pt.lon if pt.lon < @bounds.min_lon + @bounds.max_lat = pt.lat if pt.lat > @bounds.max_lat + @bounds.max_lon = pt.lon if pt.lon > @bounds.max_lon + + @distance += haversine_distance(last_pt, pt) unless last_pt.nil? + + @points << pt + last_pt = pt + end + end + end + end + + # Tack on a point to this Segment. All meta-data will be updated. + def append_point(pt) + last_pt = @points[-1] + @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time) + @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time) + @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation) + @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation) + @bounds.min_lat = pt.lat if pt.lat < @bounds.min_lat + @bounds.min_lon = pt.lon if pt.lon < @bounds.min_lon + @bounds.max_lat = pt.lat if pt.lat > @bounds.max_lat + @bounds.max_lon = pt.lon if pt.lon > @bounds.max_lon + @distance += haversine_distance(last_pt, pt) unless last_pt.nil? + @points << pt + end + + # Returns true if the given time is within this Segment. + def contains_time?(time) + (time >= @earliest_point.time and time <= @latest_point.time) + end + + # Finds the closest point in time to the passed-in time argument. Useful + # for matching up time-based objects (photos, video, etc) with a + # geographic location. + def closest_point(time) + find_closest(points, time) + end + + # Deletes all points within this Segment that lie outside of the given + # area (which should be a Bounds object). + def crop(area) + delete_if { |pt| not area.contains?(pt) } + end + + # Deletes all points in this Segment that lie within the given area. + def delete_area(area) + delete_if{ |pt| area.contains?(pt) } + end + + # A handy method that deletes points based on a block that is passed in. + # If the passed-in block returns true when given a point, then that point + # is deleted. For example: + # delete_if{ |pt| area.contains?(pt) } + def delete_if + reset_meta_data + keep_points = [] + last_pt = nil + points.each do |pt| + unless yield(pt) + keep_points << pt + update_meta_data(pt, last_pt) + last_pt = pt + end + end + @points = keep_points + end + + # Returns true if this Segment has no points. + def empty? + (points.nil? or (points.size == 0)) + end + + # Converts this Segment to a REXML::Element object. + def to_xml + seg = Element.new('trkseg') + points.each { |pt| seg.elements << pt.to_xml } + seg + end + + # Prints out a nice summary of this Segment. + def to_s + result = "Track Segment\n" + result << "\tSize: #{points.size} points\n" + result << "\tDistance: #{distance} km\n" + result << "\tEarliest Point: #{earliest_point.time.to_s} \n" + result << "\tLatest Point: #{latest_point.time.to_s} \n" + result << "\tLowest Point: #{lowest_point.elevation} \n" + result << "\tHighest Point: #{highest_point.elevation}\n " + result << "\tBounds: #{bounds.to_s}" + result + end + + protected + def find_closest(pts, time) + return pts.first if pts.size == 1 + midpoint = pts.size/2 + if pts.size == 2 + diff_1 = pts[0].time - time + diff_2 = pts[1].time - time + return (diff_1 < diff_2 ? pts[0] : pts[1]) + end + if time >= pts[midpoint].time and time <= pts[midpoint+1].time + + return pts[midpoint] + + elsif(time <= pts[midpoint].time) + return find_closest(pts[0..midpoint], time) + else + return find_closest(pts[(midpoint+1)..-1], time) + end + end + + RADIUS = 6371; # earth's mean radius in km + + # Calculate the Haversine distance between two points. This is the method + # the library uses to calculate the cumulative distance of GPX files. + def haversine_distance(p1, p2) + d_lat = p2.latr - p1.latr; + d_lon = p2.lonr - p1.lonr; + a = Math.sin(d_lat/2) * Math.sin(d_lat/2) + Math.cos(p1.latr) * Math.cos(p2.latr) * Math.sin(d_lon/2) * Math.sin(d_lon/2); + c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + d = RADIUS * c; + return d; + end + + # Calculate the plain Pythagorean difference between two points. Not currently used. + def pythagorean_distance(p1, p2) + Math.sqrt((p2.latr - p1.latr)**2 + (p2.lonr - p1.lonr)**2) + end + + # Calculates the distance between two points using the Law of Cosines formula. Not currently used. + def law_of_cosines_distance(p1, p2) + (Math.acos(Math.sin(p1.latr)*Math.sin(p2.latr) + Math.cos(p1.latr)*Math.cos(p2.latr)*Math.cos(p2.lonr-p1.lonr)) * RADIUS) + end + + def reset_meta_data + @earliest_point = nil + @latest_point = nil + @highest_point = nil + @lowest_point = nil + @distance = 0.0 + @bounds = Bounds.new + end + + def update_meta_data(pt, last_pt) + @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time) + @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time) + unless pt.elevation.nil? + @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation) + @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation) + end + @bounds.add(pt) + @distance += haversine_distance(last_pt, pt) unless last_pt.nil? + end + + end + +end diff --git a/lib/gpx/track.rb b/lib/gpx/track.rb new file mode 100644 index 0000000..8ca11d9 --- /dev/null +++ b/lib/gpx/track.rb @@ -0,0 +1,149 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ +module GPX + + # In GPX, a single Track can hold multiple Segments, each of which hold + # multiple points (in this library, those points are instances of + # TrackPoint). Each instance of this class has its own meta-data, including + # low point, high point, and distance. Of course, each track references an + # array of the segments that copmrise it, but additionally each track holds + # a reference to all of its points as one big array called "points". + class Track < Base + attr_reader :points, :bounds, :lowest_point, :highest_point, :distance + attr_accessor :segments, :name, :gpx_file + + # Initialize a track from a REXML::Element, or, if no :element option is + # passed, initialize a blank Track object. + def initialize(opts = {}) + @gpx_file = opts[:gpx_file] + @segments = [] + @points = [] + reset_meta_data + if(opts[:element]) + trk_element = opts[:element] + @name = (trk_element.elements["child::name"].text rescue "") + XPath.each(trk_element, "child::trkseg") do |seg_element| + seg = Segment.new(:element => seg_element, :track => self) + update_meta_data(seg) + @segments << seg + @points.concat(seg.points) unless seg.nil? + end + end + end + + # Append a segment to this track, updating its meta data along the way. + def append_segment(seg) + update_meta_data(seg) + @segments << seg + @points.concat(seg.points) unless seg.nil? + end + + # Returns true if the given time occurs within any of the segments of this track. + def contains_time?(time) + segments.each do |seg| + return true if seg.contains_time?(time) + end + return false + end + + # Finds the closest point (to "time") within this track. Useful for + # correlating things like pictures, video, and other events, if you are + # working with a timestamp. + def closest_point(time) + segment = segments.find { |s| s.contains_time?(time) } + segment.closest_point(time) + end + + # Removes all points outside of a given area and updates the meta data. + # The "area" paremeter is usually a Bounds object. + def crop(area) + reset_meta_data + segments.each do |seg| + seg.crop(area) + update_meta_data(seg) unless seg.empty? + end + segments.delete_if { |seg| seg.empty? } + end + + # Deletes all points within a given area and updates the meta data. + def delete_area(area) + reset_meta_data + segments.each do |seg| + seg.delete_area(area) + update_meta_data(seg) unless seg.empty? + end + segments.delete_if { |seg| seg.empty? } + end + + # Returns true if this track has no points in it. This should return + # true even when the track has empty segments. + def empty? + (points.nil? or points.size.zero?) + end + + # Creates a new REXML::Element from the contents of this instance. + def to_xml + trk= Element.new('trk') + name_elem = Element.new('name') + name_elem.text = name + trk.elements << name_elem + segments.each do |seg| + trk.elements << seg.to_xml + end + trk + end + + # Prints out a friendly summary of this track (sans points). Useful for + # debugging and sanity checks. + def to_s + result = "Track \n" + result << "\tName: #{name}\n" + result << "\tSize: #{points.size} points\n" + result << "\tSegments: #{segments.size} \n" + result << "\tDistance: #{distance} km\n" + result << "\tLowest Point: #{lowest_point.elevation} \n" + result << "\tHighest Point: #{highest_point.elevation}\n " + result << "\tBounds: #{bounds.to_s}" + result + end + + protected + + def update_meta_data(seg) + @lowest_point = seg.lowest_point if(@lowest_point.nil? or seg.lowest_point.elevation < @lowest_point.elevation) + @highest_point = seg.highest_point if(@highest_point.nil? or seg.highest_point.elevation > @highest_point.elevation) + @bounds.add(seg.bounds) + @distance += seg.distance + @points.concat(seg.points) + end + + def reset_meta_data + @bounds = Bounds.new + @highest_point = nil + @lowest_point = nil + @distance = 0.0 + @points = [] + end + + end +end diff --git a/lib/gpx/trackpoint.rb b/lib/gpx/trackpoint.rb new file mode 100644 index 0000000..1b8c267 --- /dev/null +++ b/lib/gpx/trackpoint.rb @@ -0,0 +1,35 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ +module GPX + # Basically the same as a point, the TrackPoint class is supposed to + # represent the points that are children of Segment elements. So, the only + # real difference is that TrackPoints hold a reference to their parent + # Segments. + class TrackPoint < Point + attr_accessor :segment + def initialize(opts = {}) + super(opts) + @segment = opts[:segment] + end + end +end diff --git a/lib/gpx/waypoint.rb b/lib/gpx/waypoint.rb new file mode 100644 index 0000000..838cc11 --- /dev/null +++ b/lib/gpx/waypoint.rb @@ -0,0 +1,73 @@ +#-- +# Copyright (c) 2006 Doug Fales +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +module GPX + + # This class supports the concept of a waypoint. Beware that this class has + # not seen much use yet, since WalkingBoss does not use waypoints right now. + class Waypoint < Point + + SUB_ELEMENTS = %q{ magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions } + + attr_reader :gpx_file + + # Not implemented + def crop(area) + end + + # Not implemented + def delete_area(area) + end + + # Initializes a waypoint from a REXML::Element. + def initialize(opts = {}) + wpt_elem = opts[:element] + super(:element => wpt_elem) + instantiate_with_text_elements(wpt_elem, SUB_ELEMENTS) + @gpx_file = opts[:gpx_file] + end + + # Converts a waypoint to a REXML::Element. + def to_xml + wpt = Element.new('wpt') + wpt.attributes['lat'] = lat + wpt.attributes['lon'] = lon + if self.respond_to? :name + name_elem = Element.new('name') + name_elem.text = self.name + wpt.elements << name_elem + end + if self.respond_to? :sym + sym_elem = Element.new('sym') + sym_elem.text = self.sym + wpt.elements << sym_elem + end + if self.respond_to? :ele + elev_elem = Element.new('ele') + elev_elem.text = self.ele + wpt.elements << elev_elem + end + wpt + end + end +end diff --git a/tests/gpx_files/arches.gpx b/tests/gpx_files/arches.gpx new file mode 100644 index 0000000..c23752d --- /dev/null +++ b/tests/gpx_files/arches.gpx @@ -0,0 +1 @@ +1564.6831563.7211565.1641565.6451565.1641566.1251566.1251565.6451566.6061566.6061566.6061566.6061567.0861566.6061567.0861566.6061566.6061567.0861568.5281570.4511570.4511572.3741572.3741573.3351573.3351575.2571575.2571575.2571575.2571574.7771572.8541569.971569.491565.6451563.7211562.761559.8761560.3571560.3571560.8381557.9541562.281561.7991560.8381560.3571561.7991559.8761561.7991561.7991561.7991561.3181560.8381561.3181563.7211564.2021564.2021564.2021562.281563.2411563.7211564.6831564.2021565.6451565.6451565.6451565.6451569.971571.4121572.3741569.971570.4511569.491569.0091570.9321571.8931572.3741572.3741572.3741570.4511567.0861566.6061565.6451566.6061565.1641566.1251567.0861570.4511573.3351577.181576.6991577.6611577.6611577.6611578.6221579.1031576.2191576.2191576.2191575.7381576.2191577.6611578.6221579.1031580.0641581.0261581.5061581.9871581.0261581.5061582.9481582.4681584.8711587.7551586.3131592.0811592.5611594.0031592.5611588.7161585.8321582.4681583.4291581.0261580.0641580.5451580.5451582.4681585.3511586.3131587.2741585.8321588.2351588.7161589.1971588.2351588.2351585.3511585.3511584.391584.391584.8711584.8711584.391584.8711584.8711586.3131587.2741586.7931588.2351588.2351587.7551589.1971589.6771589.1971588.7161590.1581589.6771591.61589.6771589.6771593.5221592.0811592.0811590.6381589.1971591.1191590.6381590.1581587.7551589.1971589.1971590.1581590.1581584.391585.3511586.3131588.7161589.6771586.3131575.7381572.8541267.155 \ No newline at end of file diff --git a/tests/gpx_files/magellan_track.log b/tests/gpx_files/magellan_track.log new file mode 100755 index 0000000..317345d --- /dev/null +++ b/tests/gpx_files/magellan_track.log @@ -0,0 +1,306 @@ +$PMGNTRK,4001.386,N,10515.295,W,01609,M,010748.87,A,,270506*69 +$PMGNTRK,4001.342,N,10515.498,W,01610,M,010839.87,A,,270506*6B +$PMGNTRK,4001.259,N,10515.511,W,01609,M,010857.88,A,,270506*6F +$PMGNTRK,4000.894,N,10515.515,W,01611,M,011030.88,A,,270506*60 +$PMGNTRK,4000.879,N,10516.346,W,01622,M,011237.88,A,,270506*65 +$PMGNTRK,4000.924,N,10516.351,W,01622,M,011248.87,A,,270506*6D +$PMGNTRK,4000.927,N,10516.452,W,01623,M,011312.89,A,,270506*6B +$PMGNTRK,4000.934,N,10516.454,W,01629,M,011347.89,A,,270506*65 +$PMGNTRK,4000.940,N,10516.452,W,01629,M,011449.87,A,,270506*67 +$PMGNTRK,4013.479,N,10715.997,W,01634,M,170142.15,A,,280506*67 +$PMGNTRK,4013.679,N,10716.238,W,02319,M,170241.14,A,,280506*60 +$PMGNTRK,4013.671,N,10716.244,W,02324,M,170309.15,A,,280506*61 +$PMGNTRK,4013.676,N,10716.240,W,02315,M,170702.14,A,,280506*6E +$PMGNTRK,4008.847,N,10708.162,W,02313,M,180916.27,A,,280506*66 +$PMGNTRK,4008.985,N,10708.319,W,02977,M,180945.29,A,,280506*67 +$PMGNTRK,4008.977,N,10708.328,W,02977,M,181304.28,A,,280506*67 +$PMGNTRK,4008.968,N,10708.334,W,02979,M,181352.27,A,,280506*66 +$PMGNTRK,4008.961,N,10708.321,W,02978,M,181441.27,A,,280506*6F +$PMGNTRK,4008.952,N,10708.305,W,02975,M,181516.27,A,,280506*67 +$PMGNTRK,4008.930,N,10708.300,W,02974,M,181558.28,A,,280506*62 +$PMGNTRK,4008.928,N,10708.280,W,02973,M,181638.27,A,,280506*6F +$PMGNTRK,4008.925,N,10708.269,W,02974,M,181708.28,A,,280506*6F +$PMGNTRK,4008.926,N,10708.256,W,02975,M,181744.27,A,,280506*66 +$PMGNTRK,4008.932,N,10708.213,W,02976,M,181833.27,A,,280506*6E +$PMGNTRK,4008.935,N,10708.199,W,02977,M,181852.28,A,,280506*61 +$PMGNTRK,4008.939,N,10708.173,W,02976,M,181922.27,A,,280506*61 +$PMGNTRK,4008.942,N,10708.156,W,02977,M,181943.29,A,,280506*62 +$PMGNTRK,4008.932,N,10708.139,W,02978,M,182008.29,A,,280506*66 +$PMGNTRK,4008.925,N,10708.129,W,02979,M,182025.27,A,,280506*61 +$PMGNTRK,4008.918,N,10708.121,W,02981,M,182046.29,A,,280506*6B +$PMGNTRK,4008.927,N,10708.118,W,02978,M,182104.28,A,,280506*6D +$PMGNTRK,4008.939,N,10708.121,W,02981,M,182129.28,A,,280506*61 +$PMGNTRK,4008.951,N,10708.125,W,02982,M,182155.28,A,,280506*63 +$PMGNTRK,4008.963,N,10708.112,W,02985,M,182226.28,A,,280506*66 +$PMGNTRK,4008.966,N,10708.097,W,02985,M,182250.29,A,,280506*6F +$PMGNTRK,4008.965,N,10708.081,W,02989,M,182311.29,A,,280506*63 +$PMGNTRK,4008.968,N,10708.063,W,02992,M,182348.28,A,,280506*65 +$PMGNTRK,4008.978,N,10708.047,W,02991,M,182417.29,A,,280506*6D +$PMGNTRK,4008.983,N,10708.032,W,02993,M,182446.28,A,,280506*6C +$PMGNTRK,4008.983,N,10708.020,W,02992,M,182515.29,A,,280506*68 +$PMGNTRK,4008.982,N,10708.000,W,02991,M,182543.29,A,,280506*6B +$PMGNTRK,4008.990,N,10707.990,W,02993,M,182610.28,A,,280506*61 +$PMGNTRK,4008.983,N,10707.977,W,02997,M,182642.29,A,,280506*68 +$PMGNTRK,4008.976,N,10707.970,W,02997,M,182702.30,A,,280506*68 +$PMGNTRK,4008.977,N,10707.952,W,03000,M,182723.29,A,,280506*64 +$PMGNTRK,4008.974,N,10707.937,W,03006,M,182806.30,A,,280506*62 +$PMGNTRK,4008.967,N,10707.926,W,03012,M,182925.29,A,,280506*6D +$PMGNTRK,4008.958,N,10707.917,W,03012,M,182957.30,A,,280506*6E +$PMGNTRK,4008.948,N,10707.896,W,03011,M,183030.31,A,,280506*6C +$PMGNTRK,4008.947,N,10707.878,W,03006,M,183102.30,A,,280506*64 +$PMGNTRK,4008.939,N,10707.878,W,03003,M,183128.30,A,,280506*60 +$PMGNTRK,4008.928,N,10707.878,W,03003,M,183147.30,A,,280506*69 +$PMGNTRK,4008.920,N,10707.871,W,03004,M,183217.30,A,,280506*69 +$PMGNTRK,4008.910,N,10707.870,W,03000,M,183309.31,A,,280506*60 +$PMGNTRK,4008.901,N,10707.869,W,03003,M,183358.30,A,,280506*6E +$PMGNTRK,4008.893,N,10707.862,W,03010,M,183753.32,A,,280506*60 +$PMGNTRK,4008.901,N,10707.865,W,03008,M,184254.31,A,,280506*62 +$PMGNTRK,4008.910,N,10707.863,W,03007,M,184328.31,A,,280506*61 +$PMGNTRK,4008.918,N,10707.868,W,03008,M,184409.31,A,,280506*69 +$PMGNTRK,4008.928,N,10707.871,W,03008,M,184441.32,A,,280506*6D +$PMGNTRK,4008.937,N,10707.871,W,03021,M,184504.32,A,,280506*68 +$PMGNTRK,4008.941,N,10707.887,W,03020,M,184534.34,A,,280506*64 +$PMGNTRK,4008.948,N,10707.902,W,03020,M,184601.33,A,,280506*63 +$PMGNTRK,4008.956,N,10707.908,W,03019,M,184628.34,A,,280506*60 +$PMGNTRK,4008.971,N,10707.917,W,03021,M,184647.88,A,,280506*6E +$PMGNTRK,4008.973,N,10707.935,W,03014,M,184737.33,A,,280506*6C +$PMGNTRK,4008.976,N,10707.950,W,03014,M,184759.34,A,,280506*65 +$PMGNTRK,4008.983,N,10707.957,W,03014,M,184835.32,A,,280506*6B +$PMGNTRK,4008.985,N,10707.968,W,03002,M,184906.33,A,,280506*66 +$PMGNTRK,4008.983,N,10707.985,W,02999,M,184927.33,A,,280506*6A +$PMGNTRK,4008.982,N,10707.996,W,02997,M,184945.33,A,,280506*63 +$PMGNTRK,4008.986,N,10708.009,W,03008,M,185009.32,A,,280506*68 +$PMGNTRK,4008.982,N,10708.025,W,03003,M,185030.32,A,,280506*63 +$PMGNTRK,4008.974,N,10708.045,W,02998,M,185102.32,A,,280506*66 +$PMGNTRK,4008.968,N,10708.080,W,02991,M,185135.32,A,,280506*6F +$PMGNTRK,4008.970,N,10708.094,W,02992,M,185210.32,A,,280506*64 +$PMGNTRK,4008.962,N,10708.120,W,02989,M,185454.32,A,,280506*65 +$PMGNTRK,4008.948,N,10708.141,W,02983,M,185535.33,A,,280506*67 +$PMGNTRK,4008.939,N,10708.159,W,02981,M,185600.33,A,,280506*6F +$PMGNTRK,4008.934,N,10708.187,W,02981,M,185633.33,A,,280506*61 +$PMGNTRK,4008.925,N,10708.266,W,02980,M,185806.33,A,,280506*64 +$PMGNTRK,4008.928,N,10708.279,W,02977,M,185824.34,A,,280506*68 +$PMGNTRK,4008.926,N,10708.294,W,02975,M,185847.33,A,,280506*65 +$PMGNTRK,4008.918,N,10708.302,W,02977,M,185919.33,A,,280506*6E +$PMGNTRK,4008.913,N,10708.312,W,02979,M,185942.33,A,,280506*64 +$PMGNTRK,4008.908,N,10708.327,W,02982,M,190007.34,A,,280506*67 +$PMGNTRK,4008.906,N,10708.345,W,02986,M,190041.33,A,,280506*6C +$PMGNTRK,4008.890,N,10708.349,W,02990,M,190105.33,A,,280506*68 +$PMGNTRK,4008.878,N,10708.349,W,02991,M,190122.34,A,,280506*6D +$PMGNTRK,4008.864,N,10708.351,W,02993,M,190143.34,A,,280506*6C +$PMGNTRK,4008.850,N,10708.353,W,02994,M,190204.34,A,,280506*6E +$PMGNTRK,4008.834,N,10708.354,W,02997,M,190228.33,A,,280506*61 +$PMGNTRK,4008.821,N,10708.356,W,02999,M,190248.34,A,,280506*68 +$PMGNTRK,4008.803,N,10708.358,W,02998,M,190322.33,A,,280506*6D +$PMGNTRK,4008.791,N,10708.360,W,02997,M,190340.34,A,,280506*6E +$PMGNTRK,4008.778,N,10708.358,W,02996,M,190357.33,A,,280506*62 +$PMGNTRK,4008.756,N,10708.354,W,02995,M,190427.34,A,,280506*66 +$PMGNTRK,4008.737,N,10708.353,W,02995,M,190451.33,A,,280506*60 +$PMGNTRK,4008.722,N,10708.352,W,02994,M,190511.34,A,,280506*66 +$PMGNTRK,4008.708,N,10708.349,W,02993,M,190532.34,A,,280506*62 +$PMGNTRK,4008.694,N,10708.347,W,02989,M,190554.34,A,,280506*63 +$PMGNTRK,4008.678,N,10708.345,W,02986,M,190619.34,A,,280506*66 +$PMGNTRK,4008.671,N,10708.347,W,02989,M,190655.33,A,,280506*6D +$PMGNTRK,4008.661,N,10708.341,W,02990,M,191233.37,A,,280506*63 +$PMGNTRK,4008.653,N,10708.338,W,02992,M,191309.34,A,,280506*65 +$PMGNTRK,4008.646,N,10708.327,W,02989,M,191331.36,A,,280506*6C +$PMGNTRK,4008.632,N,10708.321,W,02984,M,191400.34,A,,280506*63 +$PMGNTRK,4008.620,N,10708.316,W,02983,M,191420.36,A,,280506*63 +$PMGNTRK,4008.612,N,10708.315,W,02983,M,191441.35,A,,280506*65 +$PMGNTRK,4008.614,N,10708.327,W,02985,M,191553.35,A,,280506*66 +$PMGNTRK,4008.625,N,10708.332,W,02987,M,191618.37,A,,280506*6C +$PMGNTRK,4008.636,N,10708.334,W,02991,M,191704.36,A,,280506*62 +$PMGNTRK,4008.649,N,10708.337,W,02994,M,191733.35,A,,280506*6B +$PMGNTRK,4008.666,N,10708.341,W,02994,M,191757.35,A,,280506*65 +$PMGNTRK,4008.687,N,10708.344,W,02993,M,191824.36,A,,280506*60 +$PMGNTRK,4008.699,N,10708.345,W,02994,M,191840.37,A,,280506*6A +$PMGNTRK,4008.718,N,10708.351,W,02996,M,191907.37,A,,280506*67 +$PMGNTRK,4008.740,N,10708.352,W,02999,M,191937.37,A,,280506*65 +$PMGNTRK,4008.755,N,10708.354,W,03000,M,191958.36,A,,280506*67 +$PMGNTRK,4008.769,N,10708.358,W,03004,M,192017.37,A,,280506*60 +$PMGNTRK,4008.800,N,10708.360,W,03006,M,192124.37,A,,280506*68 +$PMGNTRK,4008.814,N,10708.356,W,03006,M,192141.36,A,,280506*6A +$PMGNTRK,4008.831,N,10708.354,W,03005,M,192204.37,A,,280506*6F +$PMGNTRK,4008.846,N,10708.353,W,03004,M,192225.36,A,,280506*6B +$PMGNTRK,4008.858,N,10708.351,W,03005,M,192241.36,A,,280506*65 +$PMGNTRK,4008.874,N,10708.349,W,03005,M,192304.38,A,,280506*6C +$PMGNTRK,4008.887,N,10708.348,W,03003,M,192320.37,A,,280506*6E +$PMGNTRK,4008.902,N,10708.349,W,03003,M,192339.36,A,,280506*6A +$PMGNTRK,4008.917,N,10708.347,W,03002,M,192357.37,A,,280506*68 +$PMGNTRK,4008.932,N,10708.345,W,03002,M,192417.36,A,,280506*6F +$PMGNTRK,4008.950,N,10708.339,W,03001,M,192441.36,A,,280506*60 +$PMGNTRK,4008.968,N,10708.332,W,02999,M,192506.36,A,,280506*6B +$PMGNTRK,4008.978,N,10708.327,W,02995,M,192531.37,A,,280506*67 +$PMGNTRK,4008.984,N,10708.321,W,02990,M,192647.36,A,,280506*64 +$PMGNTRK,4010.089,N,10705.151,W,02982,M,200016.76,A,,280506*6C +$PMGNTRK,4010.093,N,10705.143,W,02982,M,200033.77,A,,280506*62 +$PMGNTRK,4010.101,N,10705.133,W,02982,M,200057.29,A,,280506*66 +$PMGNTRK,4010.102,N,10705.122,W,02982,M,200114.78,A,,280506*67 +$PMGNTRK,4010.103,N,10705.108,W,02982,M,200133.29,A,,280506*6F +$PMGNTRK,4010.111,N,10705.102,W,02973,M,200157.77,A,,280506*61 +$PMGNTRK,4010.127,N,10705.100,W,02971,M,200209.77,A,,280506*6C +$PMGNTRK,4010.154,N,10705.099,W,02927,M,200227.33,A,,280506*66 +$PMGNTRK,4010.159,N,10705.089,W,02928,M,200246.76,A,,280506*63 +$PMGNTRK,4010.162,N,10705.079,W,02931,M,200330.76,A,,280506*6C +$PMGNTRK,4010.155,N,10705.080,W,02930,M,200440.77,A,,280506*6E +$PMGNTRK,4010.147,N,10705.086,W,02929,M,200502.77,A,,280506*64 +$PMGNTRK,4010.148,N,10705.101,W,02933,M,200539.76,A,,280506*67 +$PMGNTRK,4010.140,N,10705.122,W,02936,M,200611.78,A,,280506*6C +$PMGNTRK,4010.131,N,10705.119,W,02935,M,200636.77,A,,280506*6B +$PMGNTRK,4010.120,N,10705.114,W,02933,M,200658.78,A,,280506*67 +$PMGNTRK,4010.113,N,10705.104,W,02938,M,200712.78,A,,280506*62 +$PMGNTRK,4010.111,N,10705.091,W,02934,M,200731.78,A,,280506*60 +$PMGNTRK,4010.111,N,10705.073,W,02925,M,200756.78,A,,280506*6D +$PMGNTRK,4010.114,N,10705.059,W,02913,M,200816.78,A,,280506*6E +$PMGNTRK,4010.112,N,10705.047,W,02910,M,200834.77,A,,280506*6B +$PMGNTRK,4010.111,N,10705.033,W,02906,M,200854.77,A,,280506*6A +$PMGNTRK,4010.115,N,10705.016,W,02901,M,200931.77,A,,280506*6C +$PMGNTRK,4010.120,N,10705.008,W,02904,M,201011.77,A,,280506*6A +$PMGNTRK,4010.129,N,10704.997,W,02901,M,201041.78,A,,280506*62 +$PMGNTRK,4010.139,N,10704.991,W,02902,M,201113.78,A,,280506*60 +$PMGNTRK,4010.149,N,10704.978,W,02893,M,201141.77,A,,280506*61 +$PMGNTRK,4010.155,N,10704.970,W,02891,M,201201.77,A,,280506*61 +$PMGNTRK,4010.166,N,10704.961,W,02888,M,201227.77,A,,280506*6D +$PMGNTRK,4010.176,N,10704.954,W,02891,M,201249.78,A,,280506*65 +$PMGNTRK,4010.184,N,10704.950,W,02893,M,201323.77,A,,280506*6C +$PMGNTRK,4010.194,N,10704.954,W,02885,M,201417.79,A,,280506*60 +$PMGNTRK,4010.206,N,10704.948,W,02883,M,201503.78,A,,280506*66 +$PMGNTRK,4010.212,N,10704.935,W,02875,M,201525.78,A,,280506*64 +$PMGNTRK,4010.220,N,10704.928,W,02871,M,201601.79,A,,280506*69 +$PMGNTRK,4010.227,N,10704.907,W,02866,M,201642.77,A,,280506*6C +$PMGNTRK,4010.243,N,10704.898,W,02866,M,201707.79,A,,280506*67 +$PMGNTRK,4010.255,N,10704.892,W,02864,M,201734.78,A,,280506*69 +$PMGNTRK,4010.269,N,10704.885,W,02858,M,201804.77,A,,280506*6C +$PMGNTRK,4010.275,N,10704.875,W,02852,M,201821.78,A,,280506*6C +$PMGNTRK,4010.283,N,10704.862,W,02847,M,201844.78,A,,280506*64 +$PMGNTRK,4010.294,N,10704.854,W,02846,M,201901.78,A,,280506*66 +$PMGNTRK,4010.312,N,10704.838,W,02846,M,201944.78,A,,280506*62 +$PMGNTRK,4010.320,N,10704.831,W,02844,M,202003.77,A,,280506*6E +$PMGNTRK,4010.338,N,10704.819,W,02842,M,202040.78,A,,280506*63 +$PMGNTRK,4010.347,N,10704.813,W,02840,M,202058.78,A,,280506*6A +$PMGNTRK,4010.356,N,10704.813,W,02837,M,202128.78,A,,280506*6C +$PMGNTRK,4010.364,N,10704.812,W,02842,M,202316.78,A,,280506*61 +$PMGNTRK,4010.377,N,10704.806,W,02843,M,202337.78,A,,280506*64 +$PMGNTRK,4010.391,N,10704.800,W,02842,M,202404.80,A,,280506*6B +$PMGNTRK,4010.401,N,10704.795,W,02840,M,202422.79,A,,280506*66 +$PMGNTRK,4010.412,N,10704.785,W,02836,M,202446.79,A,,280506*66 +$PMGNTRK,4010.427,N,10704.782,W,02838,M,202514.79,A,,280506*6F +$PMGNTRK,4010.439,N,10704.782,W,02836,M,202535.79,A,,280506*6D +$PMGNTRK,4010.456,N,10704.780,W,02836,M,202602.78,A,,280506*60 +$PMGNTRK,4010.474,N,10704.799,W,02832,M,202640.78,A,,280506*6A +$PMGNTRK,4010.484,N,10704.811,W,02833,M,202700.79,A,,280506*6F +$PMGNTRK,4010.489,N,10704.822,W,02829,M,202715.80,A,,280506*6B +$PMGNTRK,4010.498,N,10704.844,W,02823,M,202747.79,A,,280506*60 +$PMGNTRK,4010.518,N,10704.868,W,02827,M,202823.79,A,,280506*6E +$PMGNTRK,4010.524,N,10704.879,W,02831,M,202838.80,A,,280506*6A +$PMGNTRK,4010.527,N,10704.910,W,02845,M,202913.80,A,,280506*6C +$PMGNTRK,4010.523,N,10704.921,W,02850,M,202938.78,A,,280506*60 +$PMGNTRK,4010.519,N,10704.936,W,02852,M,203012.80,A,,280506*6A +$PMGNTRK,4010.517,N,10704.948,W,02856,M,203027.79,A,,280506*69 +$PMGNTRK,4010.518,N,10704.967,W,02858,M,203058.81,A,,280506*6A +$PMGNTRK,4010.522,N,10704.978,W,02861,M,203114.79,A,,280506*69 +$PMGNTRK,4010.533,N,10704.997,W,02870,M,203308.80,A,,280506*61 +$PMGNTRK,4010.540,N,10705.008,W,02869,M,203323.81,A,,280506*6B +$PMGNTRK,4010.554,N,10705.022,W,02871,M,203347.79,A,,280506*6A +$PMGNTRK,4010.567,N,10705.026,W,02871,M,203404.81,A,,280506*69 +$PMGNTRK,4010.581,N,10705.028,W,02874,M,203421.80,A,,280506*6C +$PMGNTRK,4010.591,N,10705.035,W,02876,M,203439.80,A,,280506*6A +$PMGNTRK,4010.608,N,10705.049,W,02880,M,203507.80,A,,280506*67 +$PMGNTRK,4010.617,N,10705.053,W,02878,M,203521.80,A,,280506*61 +$PMGNTRK,4010.634,N,10705.064,W,02875,M,203549.80,A,,280506*67 +$PMGNTRK,4010.640,N,10705.070,W,02875,M,203633.80,A,,280506*6F +$PMGNTRK,4010.648,N,10705.065,W,02873,M,203701.79,A,,280506*63 +$PMGNTRK,4010.636,N,10705.065,W,02874,M,203723.81,A,,280506*6A +$PMGNTRK,4010.628,N,10705.056,W,02875,M,203740.80,A,,280506*60 +$PMGNTRK,4010.627,N,10705.038,W,02876,M,203756.80,A,,280506*63 +$PMGNTRK,4010.634,N,10705.022,W,02878,M,203815.80,A,,280506*6C +$PMGNTRK,4010.644,N,10705.005,W,02878,M,203835.80,A,,280506*6C +$PMGNTRK,4010.654,N,10704.981,W,02883,M,203901.80,A,,280506*6B +$PMGNTRK,4010.664,N,10704.959,W,02889,M,203949.81,A,,280506*6A +$PMGNTRK,4010.672,N,10704.950,W,02885,M,204010.79,A,,280506*6D +$PMGNTRK,4010.686,N,10704.932,W,02885,M,204040.81,A,,280506*60 +$PMGNTRK,4010.699,N,10704.921,W,02887,M,204102.81,A,,280506*69 +$PMGNTRK,4010.706,N,10704.905,W,02889,M,204123.81,A,,280506*65 +$PMGNTRK,4010.707,N,10704.893,W,02887,M,204137.79,A,,280506*66 +$PMGNTRK,4010.701,N,10704.881,W,02886,M,204203.81,A,,280506*61 +$PMGNTRK,4010.693,N,10704.876,W,02886,M,204228.80,A,,280506*6B +$PMGNTRK,4010.686,N,10704.869,W,02873,M,204303.81,A,,280506*62 +$PMGNTRK,4010.679,N,10704.857,W,02868,M,204348.81,A,,280506*6A +$PMGNTRK,4010.669,N,10704.848,W,02867,M,204416.81,A,,280506*66 +$PMGNTRK,4010.664,N,10704.840,W,02871,M,204431.80,A,,280506*60 +$PMGNTRK,4010.659,N,10704.831,W,02861,M,204452.82,A,,280506*6E +$PMGNTRK,4010.651,N,10704.813,W,02863,M,204519.82,A,,280506*6A +$PMGNTRK,4010.636,N,10704.815,W,02853,M,204612.81,A,,280506*65 +$PMGNTRK,4010.623,N,10704.817,W,02848,M,204636.40,A,,280506*62 +$PMGNTRK,4010.608,N,10704.817,W,02846,M,204659.83,A,,280506*63 +$PMGNTRK,4010.599,N,10704.822,W,02845,M,204715.82,A,,280506*65 +$PMGNTRK,4010.587,N,10704.831,W,02844,M,204741.81,A,,280506*6B +$PMGNTRK,4010.577,N,10704.847,W,02848,M,204811.82,A,,280506*60 +$PMGNTRK,4010.570,N,10704.858,W,02849,M,204830.81,A,,280506*68 +$PMGNTRK,4010.559,N,10704.871,W,02851,M,204857.82,A,,280506*63 +$PMGNTRK,4010.552,N,10704.881,W,02850,M,204914.82,A,,280506*60 +$PMGNTRK,4010.542,N,10704.885,W,02853,M,204932.82,A,,280506*62 +$PMGNTRK,4010.535,N,10704.875,W,02855,M,205010.82,A,,280506*63 +$PMGNTRK,4010.527,N,10704.865,W,02854,M,205027.82,A,,280506*64 +$PMGNTRK,4010.509,N,10704.869,W,02853,M,205036.80,A,,280506*61 +$PMGNTRK,4010.507,N,10704.852,W,02852,M,205135.82,A,,280506*66 +$PMGNTRK,4010.503,N,10704.835,W,02855,M,205155.82,A,,280506*62 +$PMGNTRK,4010.494,N,10704.815,W,02859,M,205222.81,A,,280506*63 +$PMGNTRK,4010.482,N,10704.794,W,02848,M,205251.82,A,,280506*65 +$PMGNTRK,4010.476,N,10704.785,W,02849,M,205305.82,A,,280506*6F +$PMGNTRK,4010.465,N,10704.778,W,02846,M,205322.82,A,,280506*65 +$PMGNTRK,4010.454,N,10704.778,W,02845,M,205346.82,A,,280506*66 +$PMGNTRK,4010.441,N,10704.778,W,02845,M,205412.81,A,,280506*67 +$PMGNTRK,4010.427,N,10704.780,W,02846,M,205431.81,A,,280506*62 +$PMGNTRK,4010.415,N,10704.789,W,02847,M,205453.82,A,,280506*6C +$PMGNTRK,4010.399,N,10704.801,W,02842,M,205528.82,A,,280506*68 +$PMGNTRK,4010.390,N,10704.804,W,02842,M,205542.82,A,,280506*68 +$PMGNTRK,4010.379,N,10704.811,W,02837,M,205601.83,A,,280506*6C +$PMGNTRK,4010.370,N,10704.809,W,02838,M,205631.82,A,,280506*61 +$PMGNTRK,4010.358,N,10704.809,W,02842,M,205737.83,A,,280506*60 +$PMGNTRK,4010.342,N,10704.817,W,02845,M,205806.83,A,,280506*6E +$PMGNTRK,4010.329,N,10704.825,W,02847,M,205829.37,A,,280506*62 +$PMGNTRK,4010.318,N,10704.832,W,02847,M,205854.84,A,,280506*64 +$PMGNTRK,4010.307,N,10704.842,W,02849,M,205915.84,A,,280506*67 +$PMGNTRK,4010.297,N,10704.850,W,02850,M,205934.83,A,,280506*60 +$PMGNTRK,4010.288,N,10704.860,W,02854,M,205954.83,A,,280506*6F +$PMGNTRK,4010.276,N,10704.879,W,02860,M,210026.83,A,,280506*69 +$PMGNTRK,4010.264,N,10704.888,W,02864,M,210049.83,A,,280506*69 +$PMGNTRK,4010.248,N,10704.892,W,02869,M,210115.83,A,,280506*69 +$PMGNTRK,4010.237,N,10704.899,W,02873,M,210137.83,A,,280506*61 +$PMGNTRK,4010.226,N,10704.912,W,02875,M,210203.82,A,,280506*60 +$PMGNTRK,4010.220,N,10704.923,W,02876,M,210220.82,A,,280506*66 +$PMGNTRK,4010.213,N,10704.930,W,02879,M,210238.83,A,,280506*63 +$PMGNTRK,4010.197,N,10704.935,W,02883,M,210309.82,A,,280506*6E +$PMGNTRK,4010.189,N,10704.941,W,02879,M,210334.38,A,,280506*68 +$PMGNTRK,4010.178,N,10704.944,W,02890,M,210404.83,A,,280506*60 +$PMGNTRK,4010.162,N,10704.951,W,02890,M,210417.82,A,,280506*6C +$PMGNTRK,4010.160,N,10704.969,W,02935,M,210458.83,A,,280506*61 +$PMGNTRK,4010.153,N,10704.975,W,02908,M,210518.40,A,,280506*68 +$PMGNTRK,4010.146,N,10704.972,W,02909,M,210603.82,A,,280506*6D +$PMGNTRK,4010.134,N,10704.982,W,02922,M,210612.83,A,,280506*6F +$PMGNTRK,4010.120,N,10704.990,W,02927,M,210631.36,A,,280506*63 +$PMGNTRK,4010.114,N,10705.002,W,02926,M,210657.83,A,,280506*68 +$PMGNTRK,4010.118,N,10705.015,W,02926,M,210713.82,A,,280506*62 +$PMGNTRK,4010.112,N,10705.038,W,02922,M,210737.36,A,,280506*6A +$PMGNTRK,4010.114,N,10705.050,W,02916,M,210752.84,A,,280506*6F +$PMGNTRK,4010.118,N,10705.078,W,02922,M,210828.83,A,,280506*6B +$PMGNTRK,4010.111,N,10705.093,W,02926,M,210852.84,A,,280506*69 +$PMGNTRK,4010.115,N,10705.105,W,02926,M,210909.84,A,,280506*6C +$PMGNTRK,4010.113,N,10705.115,W,02927,M,210925.84,A,,280506*64 +$PMGNTRK,4010.111,N,10705.126,W,02933,M,210941.84,A,,280506*61 +$PMGNTRK,4010.114,N,10705.138,W,02943,M,211005.84,A,,280506*64 +$PMGNTRK,4010.118,N,10705.148,W,02939,M,211023.85,A,,280506*67 +$PMGNTRK,4010.119,N,10705.164,W,02940,M,211049.84,A,,280506*6B +$PMGNTRK,4010.109,N,10705.183,W,02946,M,211132.84,A,,280506*68 +$PMGNTRK,4010.098,N,10705.183,W,02946,M,211141.37,A,,280506*6D +$PMGNTRK,4010.088,N,10705.190,W,02943,M,211201.85,A,,280506*65 +$PMGNTRK,4010.085,N,10705.203,W,02944,M,211220.85,A,,280506*65 +$PMGNTRK,4010.079,N,10705.213,W,02944,M,211236.46,A,,280506*6F +$PMGNTRK,4010.089,N,10705.232,W,02974,M,211251.84,A,,280506*6F +$PMGNTRK,4010.095,N,10705.248,W,02974,M,211308.84,A,,280506*62 +$PMGNTRK,4010.102,N,10705.260,W,02976,M,211335.37,A,,280506*63 +$PMGNTRK,4010.109,N,10705.271,W,02976,M,211346.85,A,,280506*65 +$PMGNTRK,4010.099,N,10705.278,W,02979,M,211410.86,A,,280506*6C +$PMGNTRK,4010.094,N,10705.283,W,02984,M,211552.85,A,,280506*63 +$PMGNCMD,END*3D diff --git a/tests/gpx_files/one_segment.gpx b/tests/gpx_files/one_segment.gpx new file mode 100644 index 0000000..e164eee --- /dev/null +++ b/tests/gpx_files/one_segment.gpx @@ -0,0 +1,770 @@ + + + + + + + + + + + + + + 1334.447 + + + + 1338.292 + + + + 1344.061 + + + + 1364.729 + + + + 1371.938 + + + + 1375.303 + + + + 1389.723 + + + + 1396.452 + + + + 1400.778 + + + + 1404.143 + + + + 1410.391 + + + + 1413.756 + + + + 1414.237 + + + + 1415.679 + + + + 1415.679 + + + + 1415.679 + + + + 1415.198 + + + + 1414.717 + + + + 1415.679 + + + + 1415.679 + + + + 1415.679 + + + + 1415.198 + + + + 1415.679 + + + + 1415.198 + + + + 1416.159 + + + + 1417.601 + + + + 1417.601 + + + + 1417.601 + + + + 1417.601 + + + + 1415.679 + + + + 1416.159 + + + + 1415.679 + + + + 1413.756 + + + + 1414.237 + + + + 1414.237 + + + + 1414.717 + + + + 1415.198 + + + + 1414.237 + + + + 1418.082 + + + + 1418.562 + + + + 1417.121 + + + + 1416.64 + + + + 1419.043 + + + + 1419.043 + + + + 1416.159 + + + + 1415.198 + + + + 1414.717 + + + + 1411.353 + + + + 1409.911 + + + + 1409.43 + + + + 1408.469 + + + + 1407.027 + + + + 1403.662 + + + + 1402.701 + + + + 1406.065 + + + + 1407.508 + + + + 1408.469 + + + + 1408.469 + + + + 1409.43 + + + + 1409.43 + + + + 1411.353 + + + + 1411.833 + + + + 1412.314 + + + + 1411.833 + + + + 1412.314 + + + + 1409.911 + + + + 1410.391 + + + + 1409.911 + + + + 1408.469 + + + + 1407.027 + + + + 1408.469 + + + + 1411.353 + + + + 1411.353 + + + + 1414.237 + + + + 1416.159 + + + + 1416.64 + + + + 1419.524 + + + + 1420.485 + + + + 1421.447 + + + + 1424.331 + + + + 1425.772 + + + + 1427.695 + + + + 1428.656 + + + + 1430.579 + + + + 1429.137 + + + + 1429.618 + + + + 1428.176 + + + + 1433.463 + + + + 1433.944 + + + + 1438.27 + + + + 1445.479 + + + + 1451.728 + + + + 1454.132 + + + + 1455.573 + + + + 1457.015 + + + + 1458.457 + + + + 1461.341 + + + + 1463.264 + + + + 1467.109 + + + + 1469.032 + + + + 1469.032 + + + + 1469.993 + + + + 1471.916 + + + + 1469.993 + + + + 1468.551 + + + + 1469.032 + + + + 1474.319 + + + + 1476.242 + + + + 1480.087 + + + + 1478.645 + + + + 1478.164 + + + + 1474.319 + + + + 1472.396 + + + + 1471.435 + + + + 1471.916 + + + + 1473.358 + + + + 1470.955 + + + + 1469.993 + + + + 1468.071 + + + + 1468.551 + + + + 1471.435 + + + + 1471.435 + + + + 1468.071 + + + + 1472.877 + + + + 1469.513 + + + + 1470.474 + + + + 1471.435 + + + + 1471.435 + + + + 1468.551 + + + + 1465.667 + + + + 1462.302 + + + + 1461.341 + + + + 1459.899 + + + + 1457.977 + + + + 1456.054 + + + + 1455.093 + + + + 1453.651 + + + + 1449.805 + + + + 1446.921 + + + + 1437.308 + + + + 1435.385 + + + + 1434.424 + + + + 1432.021 + + + + 1430.579 + + + + 1429.618 + + + + 1427.215 + + + + 1425.772 + + + + 1425.772 + + + + 1423.369 + + + + 1422.889 + + + + 1420.966 + + + + 1420.005 + + + + 1416.64 + + + + 1417.121 + + + + 1416.64 + + + + 1413.275 + + + + 1413.275 + + + + 1409.43 + + + + 1408.469 + + + + 1409.43 + + + + 1409.43 + + + + 1409.43 + + + + 1412.314 + + + + 1412.314 + + + + 1411.353 + + + + 1411.833 + + + + 1412.795 + + + + 1413.756 + + + + 1414.717 + + + + 1418.562 + + + + 1420.005 + + + + 1420.966 + + + + 1420.005 + + + + 1420.005 + + + + 1421.447 + + + + 1420.966 + + + + 1421.447 + + + + 1424.811 + + + + 1426.253 + + + + 1428.176 + + + + 1424.811 + + + + 1423.85 + + + + 1420.485 + + + + 1418.562 + + + + 1416.64 + + + + 1414.717 + + + + 1414.717 + + + + 1415.198 + + + + 1413.756 + + + + diff --git a/tests/gpx_files/one_track.gpx b/tests/gpx_files/one_track.gpx new file mode 100644 index 0000000..8cc6fb3 --- /dev/null +++ b/tests/gpx_files/one_track.gpx @@ -0,0 +1,756 @@ + + + + + + + + + + + + + + 1564.683 + + + + 1563.721 + + + + 1565.164 + + + + 1565.645 + + + + 1565.164 + + + + 1566.125 + + + + 1566.125 + + + + 1565.645 + + + + 1566.606 + + + + 1566.606 + + + + 1566.606 + + + + 1566.606 + + + + 1567.086 + + + + 1566.606 + + + + 1567.086 + + + + 1566.606 + + + + 1566.606 + + + + 1567.086 + + + + 1568.528 + + + + + + 1570.451 + + + + 1570.451 + + + + 1572.374 + + + + 1572.374 + + + + 1573.335 + + + + 1573.335 + + + + 1575.257 + + + + 1575.257 + + + + 1575.257 + + + + 1575.257 + + + + 1574.777 + + + + 1572.854 + + + + 1569.97 + + + + 1569.49 + + + + 1565.645 + + + + 1563.721 + + + + 1562.76 + + + + 1559.876 + + + + 1560.357 + + + + 1560.357 + + + + 1560.838 + + + + 1557.954 + + + + 1562.28 + + + + 1561.799 + + + + 1560.838 + + + + 1560.357 + + + + 1561.799 + + + + 1559.876 + + + + 1561.799 + + + + 1561.799 + + + + 1561.799 + + + + 1561.318 + + + + 1560.838 + + + + 1561.318 + + + + 1563.721 + + + + 1564.202 + + + + 1564.202 + + + + 1564.202 + + + + 1562.28 + + + + 1563.241 + + + + 1563.721 + + + + 1564.683 + + + + 1564.202 + + + + 1565.645 + + + + 1565.645 + + + + 1565.645 + + + + 1565.645 + + + + 1569.97 + + + + 1571.412 + + + + 1572.374 + + + + 1569.97 + + + + 1570.451 + + + + 1569.49 + + + + 1569.009 + + + + 1570.932 + + + + 1571.893 + + + + 1572.374 + + + + 1572.374 + + + + 1572.374 + + + + 1570.451 + + + + 1567.086 + + + + 1566.606 + + + + 1565.645 + + + + 1566.606 + + + + 1565.164 + + + + 1566.125 + + + + 1567.086 + + + + 1570.451 + + + + 1573.335 + + + + 1577.18 + + + + 1576.699 + + + + 1577.661 + + + + 1577.661 + + + + 1577.661 + + + + 1578.622 + + + + 1579.103 + + + + 1576.219 + + + + 1576.219 + + + + 1576.219 + + + + 1575.738 + + + + 1576.219 + + + + 1577.661 + + + + 1578.622 + + + + 1579.103 + + + + 1580.064 + + + + 1581.026 + + + + 1581.506 + + + + 1581.987 + + + + 1581.026 + + + + 1581.506 + + + + + + 1582.948 + + + + + + 1582.468 + + + + 1584.871 + + + + 1587.755 + + + + 1586.313 + + + + 1592.081 + + + + 1592.561 + + + + 1594.003 + + + + 1592.561 + + + + 1588.716 + + + + 1585.832 + + + + 1582.468 + + + + 1583.429 + + + + 1581.026 + + + + 1580.064 + + + + 1580.545 + + + + 1580.545 + + + + 1582.468 + + + + 1585.351 + + + + 1586.313 + + + + 1587.274 + + + + 1585.832 + + + + 1588.235 + + + + 1588.716 + + + + 1589.197 + + + + 1588.235 + + + + 1588.235 + + + + 1585.351 + + + + 1585.351 + + + + 1584.39 + + + + 1584.39 + + + + 1584.871 + + + + 1584.871 + + + + 1584.39 + + + + 1584.871 + + + + 1584.871 + + + + 1586.313 + + + + 1587.274 + + + + 1586.793 + + + + 1588.235 + + + + 1588.235 + + + + 1587.755 + + + + 1589.197 + + + + 1589.677 + + + + 1589.197 + + + + 1588.716 + + + + 1590.158 + + + + 1589.677 + + + + 1591.6 + + + + 1589.677 + + + + 1589.677 + + + + 1593.522 + + + + 1592.081 + + + + 1592.081 + + + + 1590.638 + + + + 1589.197 + + + + 1591.119 + + + + 1590.638 + + + + 1590.158 + + + + 1587.755 + + + + 1589.197 + + + + 1589.197 + + + + + + 1590.158 + + + + 1590.158 + + + + + + 1584.39 + + + + 1585.351 + + + + 1586.313 + + + + 1588.716 + + + + 1589.677 + + + + 1586.313 + + + + + + 1575.738 + + + + 1572.854 + + + + + + 1267.155 + + + + diff --git a/tests/gpx_files/routes.gpx b/tests/gpx_files/routes.gpx new file mode 100644 index 0000000..d2dd500 --- /dev/null +++ b/tests/gpx_files/routes.gpx @@ -0,0 +1 @@ +Waypoint1766.535Waypoint1854.735Waypoint2163.556Waypoint1612.965 \ No newline at end of file diff --git a/tests/gpx_files/tracks.gpx b/tests/gpx_files/tracks.gpx new file mode 100644 index 0000000..10ad04b --- /dev/null +++ b/tests/gpx_files/tracks.gpx @@ -0,0 +1,6304 @@ + + + + + + + + + + + + + + 1737.24 + + + + 1738.682 + + + + 1738.682 + + + + 1737.24 + + + + 1735.798 + + + + 1735.798 + + + + 1736.278 + + + + 1739.643 + + + + 1732.433 + + + + 1726.665 + + + + 1720.417 + + + + 1713.687 + + + + 1707.919 + + + + 1704.555 + + + + 1701.19 + + + + 1694.461 + + + + 1689.174 + + + + 1681.483 + + + + 1677.157 + + + + 1673.312 + + + + 1669.467 + + + + 1663.699 + + + + 1660.334 + + + + 1656.489 + + + + 1653.605 + + + + 1650.24 + + + + 1645.434 + + + + 1639.666 + + + + 1635.821 + + + + 1632.456 + + + + 1627.649 + + + + 1624.285 + + + + 1622.362 + + + + 1618.036 + + + + 1616.114 + + + + 1614.191 + + + + 1610.826 + + + + 1608.904 + + + + 1606.02 + + + + 1609.384 + + + + 1608.423 + + + + 1606.02 + + + + 1606.02 + + + + 1606.981 + + + + 1609.865 + + + + 1609.384 + + + + 1616.114 + + + + 1617.555 + + + + 1619.959 + + + + 1621.401 + + + + 1620.439 + + + + 1623.323 + + + + 1622.362 + + + + 1618.036 + + + + 1627.649 + + + + 1619.478 + + + + 1613.23 + + + + 1613.23 + + + + 1605.539 + + + + 1607.462 + + + + 1606.02 + + + + 1607.942 + + + + 1606.02 + + + + 1606.02 + + + + 1599.771 + + + + 1599.291 + + + + 1596.407 + + + + 1598.81 + + + + 1593.522 + + + + 1592.081 + + + + 1594.964 + + + + 1586.793 + + + + 1581.026 + + + + 1583.909 + + + + 1586.313 + + + + 1588.235 + + + + 1592.081 + + + + 1589.677 + + + + 1599.771 + + + + 1593.042 + + + + 1599.771 + + + + 1601.694 + + + + 1596.407 + + + + 1593.042 + + + + 1602.655 + + + + 1600.252 + + + + 1606.02 + + + + 1598.81 + + + + 1604.097 + + + + 1604.097 + + + + 1607.942 + + + + 1605.539 + + + + 1611.788 + + + + 1610.826 + + + + 1610.345 + + + + 1614.672 + + + + 1608.904 + + + + 1608.423 + + + + 1608.423 + + + + 1608.423 + + + + 1608.904 + + + + 1608.904 + + + + 1609.384 + + + + 1607.942 + + + + 1608.904 + + + + 1609.384 + + + + 1609.384 + + + + 1610.826 + + + + 1609.865 + + + + 1611.307 + + + + 1609.865 + + + + 1611.788 + + + + 1610.826 + + + + 1610.345 + + + + 1604.097 + + + + 1607.942 + + + + 1606.981 + + + + 1608.423 + + + + 1608.904 + + + + 1610.345 + + + + 1618.036 + + + + 1623.323 + + + + 1621.881 + + + + 1626.688 + + + + 1626.207 + + + + 1626.207 + + + + 1628.13 + + + + 1629.091 + + + + 1625.246 + + + + 1617.075 + + + + 1612.749 + + + + 1609.865 + + + + 1609.865 + + + + 1609.865 + + + + 1611.307 + + + + 1610.826 + + + + 1609.865 + + + + 1610.826 + + + + 1610.345 + + + + 1610.826 + + + + 1609.865 + + + + 1609.865 + + + + 1613.71 + + + + 1608.904 + + + + 1609.384 + + + + 1608.904 + + + + 1608.904 + + + + 1608.904 + + + + 1608.904 + + + + 1609.865 + + + + 1609.384 + + + + 1609.384 + + + + 1612.268 + + + + 1609.865 + + + + 1610.345 + + + + 1608.904 + + + + 1611.307 + + + + 1612.268 + + + + 1611.788 + + + + 1611.307 + + + + 1613.23 + + + + 1611.307 + + + + 1611.788 + + + + 1610.345 + + + + 1610.345 + + + + 1609.865 + + + + 1609.865 + + + + 1613.71 + + + + 1610.345 + + + + 1613.71 + + + + 1610.345 + + + + 1623.804 + + + + 1626.207 + + + + 1629.572 + + + + 1633.417 + + + + 1635.821 + + + + 1637.262 + + + + 1643.511 + + + + 1643.992 + + + + 1644.472 + + + + 1643.992 + + + + 1643.511 + + + + 1644.472 + + + + 1644.953 + + + + 1645.434 + + + + 1640.627 + + + + 1646.395 + + + + 1639.185 + + + + 1645.915 + + + + 1639.666 + + + + 1645.915 + + + + 1642.069 + + + + 1645.915 + + + + 1642.069 + + + + 1645.434 + + + + 1642.069 + + + + 1644.953 + + + + 1643.03 + + + + 1646.395 + + + + 1645.434 + + + + 1644.472 + + + + 1643.992 + + + + 1678.599 + + + + 1680.522 + + + + 1681.483 + + + + 1676.677 + + + + 1681.003 + + + + 1681.964 + + + + 1675.715 + + + + 1680.522 + + + + 1684.367 + + + + 1680.041 + + + + 1678.599 + + + + 1677.638 + + + + 1675.715 + + + + 1681.003 + + + + 1677.638 + + + + 1676.196 + + + + 1674.754 + + + + 1674.273 + + + + 1678.119 + + + + 1674.273 + + + + 1672.831 + + + + 1677.638 + + + + 1672.831 + + + + 1677.638 + + + + 1673.792 + + + + 1677.638 + + + + 1671.389 + + + + 1676.196 + + + + 1676.677 + + + + 1678.119 + + + + 1678.599 + + + + 1680.522 + + + + 1681.964 + + + + 1678.599 + + + + 1676.196 + + + + 1673.312 + + + + 1677.157 + + + + 1673.312 + + + + 1677.638 + + + + 1831.449 + + + + 1833.852 + + + + 1829.526 + + + + 1825.2 + + + + 1821.836 + + + + 1818.471 + + + + 1815.106 + + + + 1799.725 + + + + 1801.167 + + + + 1795.399 + + + + 1801.648 + + + + 1812.222 + + + + 1815.106 + + + + 1818.471 + + + + 1838.659 + + + + 1847.311 + + + + 1849.233 + + + + 1845.388 + + + + 1853.559 + + + + 1852.598 + + + + 1849.714 + + + + 1843.946 + + + + 1845.388 + + + + 1861.25 + + + + 1873.266 + + + + 1874.708 + + + + 1879.995 + + + + 1882.399 + + + + 1904.99 + + + + 1915.564 + + + + 1916.526 + + + + 1919.89 + + + + 1923.735 + + + + 1926.619 + + + + 1932.868 + + + + 1942.962 + + + + 1945.846 + + + + 1955.939 + + + + 1961.708 + + + + 1970.359 + + + + 1984.779 + + + + 1987.663 + + + + 1993.431 + + + + 2009.773 + + + + 2018.425 + + + + 2025.154 + + + + 2045.342 + + + + 2044.862 + + + + 2055.436 + + + + 2058.801 + + + + 2061.685 + + + + 2074.182 + + + + 2082.833 + + + + 2089.563 + + + + 2093.889 + + + + 2106.866 + + + + 2110.231 + + + + 2113.596 + + + + 2114.557 + + + + 2117.922 + + + + 2114.076 + + + + 2118.402 + + + + 2126.574 + + + + 2129.938 + + + + 2141.955 + + + + 2144.839 + + + + 2150.606 + + + + 2147.242 + + + + 2150.606 + + + + 2143.877 + + + + 2147.242 + + + + 2150.126 + + + + 2152.529 + + + + 2155.894 + + + + 2162.142 + + + + 2158.778 + + + + 2158.297 + + + + 2161.662 + + + + 2157.336 + + + + 2160.7 + + + + 2161.662 + + + + 2160.7 + + + + 2153.49 + + + + 2147.723 + + + + 2125.132 + + + + 2116.96 + + + + 2107.347 + + + + 2101.099 + + + + 2097.734 + + + + 2093.889 + + + + 2090.524 + + + + 2089.563 + + + + 2085.718 + + + + 2082.833 + + + + 2073.22 + + + + 2066.972 + + + + 2062.646 + + + + 2062.165 + + + + 2058.32 + + + + 2053.994 + + + + 2050.149 + + + + 2050.149 + + + + 2046.784 + + + + 2036.69 + + + + 2030.923 + + + + 2027.558 + + + + 2030.923 + + + + 2030.923 + + + + 2027.558 + + + + 2024.674 + + + + 2022.271 + + + + 2019.387 + + + + 2016.503 + + + + 2013.138 + + + + 2004.486 + + + + 2002.563 + + + + 1966.995 + + + + 1957.382 + + + + 1954.978 + + + + 1950.652 + + + + 1938.155 + + + + 1938.155 + + + + 1928.542 + + + + 1925.177 + + + + 1922.293 + + + + 1891.531 + + + + 1892.973 + + + + 1892.493 + + + + 1889.128 + + + + 1857.405 + + + + 1854.04 + + + + 1845.869 + + + + 1844.907 + + + + 1832.891 + + + + 1829.526 + + + + 1825.2 + + + + 1799.725 + + + + 1792.516 + + + + 1781.941 + + + + 1780.98 + + + + 1774.731 + + + + 1769.444 + + + + 1764.156 + + + + 1768.002 + + + + 1759.831 + + + + 1764.637 + + + + 1758.869 + + + + 1759.831 + + + + + + + + + 55.8938 + + + + 55.8938 + + + + 34.26416 + + + + 107.3243 + + + + 107.3243 + + + + 104.4404 + + + + 100.5951 + + + + 101.0757 + + + + 99.63379 + + + + 99.63379 + + + + 98.19189 + + + + 99.15308 + + + + 1737.24 + + + + 1738.682 + + + + 1738.682 + + + + 1738.201 + + + + 1737.72 + + + + 1737.24 + + + + 1736.278 + + + + 1735.798 + + + + 1735.798 + + + + 1736.278 + + + + 1739.643 + + + + 1732.433 + + + + 1726.665 + + + + 1720.417 + + + + 1713.687 + + + + 1707.919 + + + + 1704.555 + + + + 1701.19 + + + + 1694.461 + + + + 1689.174 + + + + 1681.483 + + + + 1677.157 + + + + 1673.312 + + + + 1669.467 + + + + 1663.699 + + + + 1660.334 + + + + 1656.489 + + + + 1653.605 + + + + 1650.24 + + + + 1645.434 + + + + 1639.666 + + + + 1635.821 + + + + 1632.456 + + + + 1627.649 + + + + 1624.285 + + + + 1622.362 + + + + 1618.036 + + + + 1616.114 + + + + 1614.191 + + + + 1610.826 + + + + 1608.904 + + + + 1606.02 + + + + 1609.384 + + + + 1608.423 + + + + 1605.539 + + + + 1606.02 + + + + 1606.02 + + + + 1606.981 + + + + 1607.942 + + + + 1608.423 + + + + 1609.865 + + + + 1609.384 + + + + 1612.749 + + + + 1613.23 + + + + 1615.152 + + + + 1616.114 + + + + 1614.672 + + + + 1615.152 + + + + 1617.555 + + + + 1620.439 + + + + 1619.478 + + + + 1619.959 + + + + 1621.401 + + + + 1620.439 + + + + 1621.881 + + + + 1623.323 + + + + 1624.765 + + + + 1622.362 + + + + 1620.92 + + + + 1618.036 + + + + 1620.439 + + + + 1620.439 + + + + 1626.688 + + + + 1624.765 + + + + 1627.649 + + + + 1619.478 + + + + 1613.23 + + + + 1612.268 + + + + 1615.152 + + + + 1613.71 + + + + 1615.152 + + + + 1611.788 + + + + 1613.23 + + + + 1610.826 + + + + 1607.462 + + + + 1605.539 + + + + 1605.539 + + + + 1606.02 + + + + 1607.462 + + + + 1606.5 + + + + 1606.02 + + + + 1607.942 + + + + 1606.02 + + + + 1606.02 + + + + 1599.291 + + + + 1600.252 + + + + 1599.771 + + + + 1600.732 + + + + 1601.694 + + + + 1600.732 + + + + 1599.291 + + + + 1601.694 + + + + 1596.407 + + + + 1596.887 + + + + 1598.81 + + + + 1594.964 + + + + 1593.522 + + + + 1592.081 + + + + 1594.964 + + + + 1591.6 + + + + 1590.158 + + + + 1586.793 + + + + 1581.987 + + + + 1581.026 + + + + 1583.909 + + + + 1586.313 + + + + 1588.235 + + + + 1592.081 + + + + 1589.677 + + + + 1594.003 + + + + 1599.771 + + + + 1592.081 + + + + 1593.042 + + + + 1596.407 + + + + 1599.771 + + + + 1601.694 + + + + 1596.407 + + + + 1592.561 + + + + 1593.042 + + + + 1595.445 + + + + 1602.655 + + + + 1599.291 + + + + 1600.252 + + + + 1606.02 + + + + 1600.732 + + + + 1597.849 + + + + 1598.81 + + + + 1602.174 + + + + 1604.097 + + + + 1604.097 + + + + 1604.097 + + + + 1607.942 + + + + 1605.539 + + + + 1611.788 + + + + 1610.826 + + + + 1610.826 + + + + 1610.345 + + + + 1614.672 + + + + 1610.826 + + + + 1608.904 + + + + 1608.423 + + + + 1608.423 + + + + 1608.423 + + + + 1608.423 + + + + 1608.904 + + + + 1610.345 + + + + 1609.384 + + + + 1608.904 + + + + 1609.384 + + + + 1608.423 + + + + 1607.942 + + + + 1608.904 + + + + 1609.384 + + + + 1608.904 + + + + 1609.384 + + + + 1609.384 + + + + 1608.423 + + + + 1609.865 + + + + 1609.384 + + + + 1610.826 + + + + 1609.865 + + + + 1611.307 + + + + 1611.307 + + + + 1609.865 + + + + 1611.307 + + + + 1609.865 + + + + 1611.307 + + + + 1611.788 + + + + 1609.384 + + + + 1610.826 + + + + 1610.345 + + + + 1607.942 + + + + 1604.097 + + + + 1606.981 + + + + 1605.058 + + + + 1607.942 + + + + 1606.981 + + + + 1607.462 + + + + 1608.423 + + + + 1608.904 + + + + 1610.826 + + + + 1609.865 + + + + 1609.384 + + + + 1609.865 + + + + 1610.345 + + + + 1610.345 + + + + 1610.345 + + + + 1610.345 + + + + 1615.152 + + + + 1618.036 + + + + 1620.92 + + + + 1623.323 + + + + 1621.881 + + + + 1622.843 + + + + 1624.765 + + + + 1626.688 + + + + 1626.207 + + + + 1626.207 + + + + 1628.13 + + + + 1629.091 + + + + 1628.13 + + + + 1625.246 + + + + 1617.075 + + + + 1617.555 + + + + 1617.075 + + + + 1612.749 + + + + 1609.865 + + + + 1609.865 + + + + 1609.865 + + + + 1611.307 + + + + 1610.826 + + + + 1609.865 + + + + 1610.345 + + + + 1610.826 + + + + 1610.345 + + + + 1610.826 + + + + 1609.865 + + + + 1609.865 + + + + 1609.865 + + + + 1613.71 + + + + 1610.826 + + + + 1613.23 + + + + 1612.268 + + + + 1608.904 + + + + 1609.384 + + + + 1608.904 + + + + 1608.904 + + + + 1608.904 + + + + 1608.904 + + + + 1609.865 + + + + 1609.384 + + + + 1609.384 + + + + 1609.865 + + + + 1609.384 + + + + 1612.268 + + + + 1609.384 + + + + 1609.865 + + + + 1608.904 + + + + 1609.865 + + + + 1610.345 + + + + 1610.826 + + + + 1608.904 + + + + 1610.345 + + + + 1610.826 + + + + 1611.307 + + + + 1612.268 + + + + 1611.788 + + + + 1611.307 + + + + 1612.268 + + + + 1613.23 + + + + 1611.307 + + + + 1611.788 + + + + 1610.826 + + + + 1610.345 + + + + 1610.345 + + + + 1609.865 + + + + 1609.865 + + + + 1609.865 + + + + 1613.71 + + + + 1610.345 + + + + 1613.71 + + + + 1610.345 + + + + 1623.804 + + + + 1625.246 + + + + 1629.572 + + + + 1631.014 + + + + 1633.417 + + + + 1635.821 + + + + 1640.146 + + + + 1643.511 + + + + 1643.992 + + + + 1644.472 + + + + 1643.992 + + + + 1643.511 + + + + 1644.472 + + + + 1644.472 + + + + 1644.953 + + + + + + + + + 55.8938 + + + + 55.8938 + + + + + + 34.74487 + + + + 34.26416 + + + + + + 107.3243 + + + + 107.3243 + + + + 104.4404 + + + + 101.0757 + + + + 100.5951 + + + + + + 101.0757 + + + + 101.0757 + + + + 99.63379 + + + + 99.63379 + + + + + + 99.63379 + + + + 98.19189 + + + + + + 99.15308 + + + + + + 1737.24 + + + + 1738.201 + + + + 1738.682 + + + + 1738.682 + + + + 1738.201 + + + + 1737.72 + + + + 1737.24 + + + + 1736.278 + + + + 1735.798 + + + + 1735.798 + + + + 1736.278 + + + + 1739.643 + + + + 1739.162 + + + + 1732.433 + + + + 1726.665 + + + + 1720.417 + + + + 1713.687 + + + + 1707.919 + + + + 1704.555 + + + + 1701.19 + + + + 1694.461 + + + + 1689.174 + + + + 1681.483 + + + + 1677.157 + + + + 1673.312 + + + + 1669.467 + + + + 1663.699 + + + + 1660.334 + + + + 1656.489 + + + + 1653.605 + + + + 1650.24 + + + + 1645.434 + + + + 1639.666 + + + + 1635.821 + + + + 1632.456 + + + + 1627.649 + + + + 1629.091 + + + + 1624.285 + + + + 1622.362 + + + + 1618.036 + + + + 1619.478 + + + + 1616.114 + + + + 1614.191 + + + + 1610.826 + + + + 1608.904 + + + + 1606.02 + + + + 1609.384 + + + + 1608.904 + + + + 1608.423 + + + + 1605.539 + + + + 1606.02 + + + + 1606.02 + + + + 1606.981 + + + + 1608.423 + + + + 1607.942 + + + + 1608.423 + + + + 1608.423 + + + + 1609.865 + + + + 1609.384 + + + + 1612.749 + + + + 1612.268 + + + + 1613.23 + + + + 1615.152 + + + + 1616.114 + + + + 1614.672 + + + + 1615.152 + + + + 1617.555 + + + + 1620.439 + + + + 1620.92 + + + + 1619.478 + + + + 1619.959 + + + + 1621.401 + + + + 1620.439 + + + + 1621.881 + + + + 1623.323 + + + + 1624.765 + + + + 1622.362 + + + + 1620.92 + + + + 1618.036 + + + + 1620.439 + + + + 1620.439 + + + + 1623.323 + + + + 1626.688 + + + + 1624.765 + + + + 1627.649 + + + + 1627.649 + + + + 1623.323 + + + + 1619.478 + + + + 1616.594 + + + + 1616.594 + + + + 1613.23 + + + + 1612.268 + + + + 1615.633 + + + + 1615.152 + + + + 1613.71 + + + + 1615.152 + + + + 1611.788 + + + + 1613.23 + + + + 1610.826 + + + + 1607.462 + + + + 1605.539 + + + + 1605.539 + + + + 1606.02 + + + + 1607.462 + + + + 1606.5 + + + + 1606.02 + + + + 1607.942 + + + + 1606.02 + + + + 1606.02 + + + + 1600.732 + + + + 1599.291 + + + + 1600.252 + + + + 1599.771 + + + + 1600.732 + + + + 1601.694 + + + + 1600.732 + + + + 1599.291 + + + + 1601.694 + + + + 1596.887 + + + + 1596.407 + + + + 1596.887 + + + + 1598.81 + + + + 1594.964 + + + + 1594.964 + + + + 1593.522 + + + + 1592.081 + + + + 1594.964 + + + + 1591.6 + + + + 1590.158 + + + + 1586.793 + + + + 1583.909 + + + + 1581.987 + + + + 1581.026 + + + + 1583.909 + + + + 1586.313 + + + + 1588.235 + + + + 1590.158 + + + + 1592.081 + + + + 1589.677 + + + + 1590.158 + + + + 1594.003 + + + + 1597.368 + + + + 1599.771 + + + + 1597.368 + + + + 1596.407 + + + + 1594.484 + + + + 1593.042 + + + + 1593.522 + + + + 1592.081 + + + + 1593.042 + + + + 1596.407 + + + + 1599.771 + + + + 1601.694 + + + + 1597.368 + + + + 1596.407 + + + + 1593.042 + + + + 1593.042 + + + + 1592.561 + + + + 1593.042 + + + + 1595.445 + + + + 1598.329 + + + + 1602.655 + + + + 1602.174 + + + + 1599.291 + + + + 1600.252 + + + + 1602.655 + + + + 1606.02 + + + + 1606.02 + + + + 1604.578 + + + + 1600.732 + + + + 1597.849 + + + + 1598.81 + + + + 1601.694 + + + + 1602.174 + + + + 1604.097 + + + + 1604.097 + + + + 1604.097 + + + + 1607.942 + + + + 1606.02 + + + + 1605.539 + + + + 1608.423 + + + + 1612.268 + + + + 1611.788 + + + + 1610.826 + + + + 1610.826 + + + + 1610.345 + + + + 1611.307 + + + + 1614.672 + + + + 1610.826 + + + + 1608.904 + + + + 1608.423 + + + + 1608.423 + + + + 1608.423 + + + + 1608.423 + + + + 1608.423 + + + + 1608.904 + + + + 1610.345 + + + + + + 1609.384 + + + + 1608.904 + + + + 1608.904 + + + + 1609.384 + + + + 1608.904 + + + + 1608.423 + + + + 1608.423 + + + + 1607.942 + + + + 1608.904 + + + + 1608.904 + + + + 1609.384 + + + + 1608.904 + + + + 1609.384 + + + + 1609.384 + + + + 1608.904 + + + + 1608.423 + + + + 1608.904 + + + + 1609.865 + + + + 1609.865 + + + + 1609.384 + + + + 1610.826 + + + + 1610.345 + + + + 1609.865 + + + + 1610.826 + + + + 1611.307 + + + + 1611.307 + + + + 1609.865 + + + + 1611.307 + + + + 1609.865 + + + + 1611.307 + + + + 1611.788 + + + + + + 1609.384 + + + + 1610.826 + + + + 1610.345 + + + + 1607.942 + + + + 1608.904 + + + + 1608.423 + + + + 1604.097 + + + + 1606.981 + + + + 1605.058 + + + + 1607.942 + + + + 1606.981 + + + + 1606.981 + + + + 1607.462 + + + + 1608.423 + + + + 1608.904 + + + + 1609.865 + + + + 1610.826 + + + + 1609.865 + + + + 1609.384 + + + + 1609.865 + + + + 1610.345 + + + + 1610.345 + + + + 1610.345 + + + + 1610.345 + + + + 1611.307 + + + + 1613.71 + + + + 1615.152 + + + + 1618.036 + + + + 1620.92 + + + + 1623.323 + + + + 1622.843 + + + + 1621.881 + + + + 1622.843 + + + + 1624.765 + + + + 1626.688 + + + + 1626.207 + + + + 1626.207 + + + + 1628.13 + + + + 1629.091 + + + + 1628.13 + + + + 1625.246 + + + + 1625.246 + + + + 1621.401 + + + + 1618.998 + + + + 1617.075 + + + + 1617.555 + + + + 1617.555 + + + + 1617.075 + + + + 1612.749 + + + + 1610.345 + + + + 1609.865 + + + + 1609.865 + + + + 1609.865 + + + + 1611.307 + + + + + + 1610.826 + + + + + + 1609.865 + + + + 1610.345 + + + + 1610.826 + + + + 1610.345 + + + + 1610.826 + + + + 1610.826 + + + + 1610.345 + + + + 1609.865 + + + + 1609.865 + + + + 1609.865 + + + + 1609.865 + + + + 1613.71 + + + + 1610.826 + + + + 1613.23 + + + + 1612.268 + + + + + + 1608.904 + + + + 1609.384 + + + + 1609.384 + + + + 1608.904 + + + + 1608.904 + + + + 1608.904 + + + + 1608.904 + + + + 1608.904 + + + + 1609.865 + + + + 1609.384 + + + + 1609.384 + + + + 1609.384 + + + + 1609.384 + + + + 1609.865 + + + + 1609.384 + + + + 1612.268 + + + + 1611.788 + + + + 1609.384 + + + + 1609.865 + + + + 1608.904 + + + + 1609.865 + + + + 1610.345 + + + + 1610.826 + + + + 1609.384 + + + + 1608.904 + + + + 1608.904 + + + + 1610.345 + + + + 1610.826 + + + + 1610.826 + + + + 1611.307 + + + + 1610.826 + + + + 1612.268 + + + + 1611.788 + + + + 1611.788 + + + + 1611.307 + + + + 1612.749 + + + + 1612.268 + + + + 1613.23 + + + + 1611.788 + + + + 1611.307 + + + + 1611.788 + + + + 1611.788 + + + + 1610.826 + + + + 1610.345 + + + + 1610.345 + + + + 1609.865 + + + + 1609.865 + + + + 1609.865 + + + + 1609.865 + + + + 1613.71 + + + + 1613.23 + + + + 1610.345 + + + + 1613.71 + + + + 1610.345 + + + + + + 1623.804 + + + + 1625.246 + + + + 1626.207 + + + + 1629.572 + + + + 1631.014 + + + + 1633.417 + + + + 1635.821 + + + + 1637.262 + + + + 1640.146 + + + + 1643.511 + + + + 1643.992 + + + + 1643.511 + + + + 1644.472 + + + + 1643.511 + + + + 1643.992 + + + + 1643.511 + + + + 1644.472 + + + + 1644.472 + + + + 1644.953 + + + + + + 1645.434 + + + + 1640.627 + + + + 1646.395 + + + + 1639.185 + + + + 1645.915 + + + + 1639.666 + + + + 1645.915 + + + + 1642.069 + + + + 1645.915 + + + + 1642.069 + + + + 1645.434 + + + + 1642.069 + + + + 1643.03 + + + + 1644.953 + + + + 1643.03 + + + + 1643.03 + + + + 1646.395 + + + + 1645.915 + + + + 1645.434 + + + + 1645.434 + + + + 1644.472 + + + + 1644.953 + + + + 1643.992 + + + + 1644.472 + + + + 1644.472 + + + + 1643.992 + + + + + + 1678.599 + + + + 1680.522 + + + + 1679.08 + + + + 1677.638 + + + + 1679.561 + + + + 1681.483 + + + + 1676.677 + + + + 1681.003 + + + + 1681.964 + + + + 1680.041 + + + + 1675.715 + + + + 1678.119 + + + + 1678.119 + + + + 1680.522 + + + + 1684.367 + + + + 1680.041 + + + + 1681.964 + + + + 1680.522 + + + + 1678.599 + + + + 1681.003 + + + + 1679.561 + + + + 1677.638 + + + + 1680.041 + + + + 1675.715 + + + + 1681.003 + + + + 1679.561 + + + + 1677.638 + + + + 1679.561 + + + + 1678.119 + + + + 1676.196 + + + + 1678.119 + + + + 1676.196 + + + + 1674.754 + + + + 1677.638 + + + + 1676.196 + + + + 1674.273 + + + + 1678.119 + + + + 1676.196 + + + + 1678.119 + + + + 1676.196 + + + + 1678.119 + + + + 1676.196 + + + + 1677.638 + + + + 1676.196 + + + + 1678.119 + + + + 1676.196 + + + + 1674.273 + + + + 1674.754 + + + + 1672.831 + + + + 1675.234 + + + + 1677.638 + + + + 1677.157 + + + + 1672.831 + + + + 1674.754 + + + + 1677.638 + + + + 1677.157 + + + + 1675.715 + + + + 1673.792 + + + + 1675.715 + + + + 1677.638 + + + + 1675.234 + + + + 1671.389 + + + + 1673.792 + + + + 1676.196 + + + + 1674.754 + + + + 1676.677 + + + + 1678.119 + + + + 1676.677 + + + + 1676.677 + + + + 1678.119 + + + + 1676.677 + + + + 1678.599 + + + + 1680.522 + + + + 1678.599 + + + + 1678.119 + + + + 1680.041 + + + + 1678.119 + + + + 1680.041 + + + + 1678.599 + + + + 1680.041 + + + + 1678.119 + + + + 1680.041 + + + + 1678.119 + + + + 1680.041 + + + + 1681.964 + + + + 1680.041 + + + + 1681.483 + + + + 1680.041 + + + + 1678.599 + + + + 1680.522 + + + + 1678.599 + + + + 1680.041 + + + + 1678.599 + + + + 1678.599 + + + + 1676.196 + + + + 1677.638 + + + + 1676.196 + + + + 1677.638 + + + + 1676.196 + + + + 1673.312 + + + + 1677.157 + + + + 1675.234 + + + + 1673.312 + + + + 1675.715 + + + + 1677.638 + + + + 1676.196 + + + + 1674.754 + + + + 1676.196 + + + + 1675.234 + + + + + + 1831.449 + + + + 1833.852 + + + + 1829.526 + + + + 1825.2 + + + + 1821.836 + + + + 1818.471 + + + + 1815.106 + + + + 1812.222 + + + + 1809.339 + + + + 1805.974 + + + + 1803.09 + + + + 1799.725 + + + + 1801.167 + + + + 1795.399 + + + + 1795.88 + + + + 1798.283 + + + + 1801.648 + + + + 1805.013 + + + + + + 1812.222 + + + + 1815.106 + + + + 1818.471 + + + + 1821.836 + + + + 1825.2 + + + + 1826.642 + + + + 1828.565 + + + + 1831.449 + + + + 1834.813 + + + + + + 1838.659 + + + + 1842.023 + + + + + + 1847.311 + + + + 1849.233 + + + + 1849.233 + + + + 1845.388 + + + + 1847.311 + + + + 1850.675 + + + + 1853.559 + + + + 1853.559 + + + + + + 1852.598 + + + + 1849.714 + + + + 1846.349 + + + + + + 1843.946 + + + + 1845.388 + + + + 1847.791 + + + + 1850.675 + + + + 1854.04 + + + + 1855.482 + + + + 1858.366 + + + + 1861.25 + + + + 1865.095 + + + + 1867.979 + + + + + + 1873.266 + + + + 1873.747 + + + + 1874.708 + + + + 1878.073 + + + + 1879.995 + + + + 1883.36 + + + + 1882.399 + + + + 1885.763 + + + + 1889.128 + + + + 1892.012 + + + + 1895.376 + + + + 1898.741 + + + + 1901.625 + + + + 1904.99 + + + + 1908.835 + + + + 1910.277 + + + + 1913.161 + + + + 1916.045 + + + + 1915.564 + + + + 1914.603 + + + + 1916.526 + + + + 1919.89 + + + + 1922.774 + + + + + + 1923.735 + + + + 1926.619 + + + + 1929.503 + + + + 1932.868 + + + + 1936.713 + + + + 1940.078 + + + + 1942 + + + + 1942.962 + + + + 1942.962 + + + + 1945.846 + + + + 1948.73 + + + + 1952.575 + + + + 1955.459 + + + + 1955.939 + + + + 1958.343 + + + + 1961.708 + + + + 1964.111 + + + + 1967.475 + + + + 1970.359 + + + + 1970.84 + + + + 1973.724 + + + + + + 1984.779 + + + + 1987.663 + + + + 1991.028 + + + + 1991.989 + + + + 1993.431 + + + + 1993.431 + + + + 1996.796 + + + + 2000.16 + + + + 2003.525 + + + + 2006.889 + + + + 2009.773 + + + + + + 2015.061 + + + + 2018.425 + + + + 2021.79 + + + + 2021.79 + + + + 2025.154 + + + + 2028.039 + + + + 2031.403 + + + + 2034.768 + + + + 2038.132 + + + + 2041.497 + + + + 2045.342 + + + + 2046.304 + + + + 2044.862 + + + + 2048.226 + + + + 2048.707 + + + + 2052.071 + + + + 2055.436 + + + + 2058.801 + + + + 2061.685 + + + + 2065.049 + + + + 2067.933 + + + + 2071.298 + + + + 2074.182 + + + + 2077.546 + + + + 2080.43 + + + + + + 2081.392 + + + + 2082.833 + + + + 2086.198 + + + + 2089.563 + + + + 2092.927 + + + + 2093.889 + + + + 2097.253 + + + + 2100.618 + + + + 2103.983 + + + + 2106.866 + + + + 2110.231 + + + + 2113.596 + + + + 2113.115 + + + + 2115.038 + + + + 2114.557 + + + + 2117.922 + + + + 2114.076 + + + + 2118.402 + + + + 2120.806 + + + + 2123.689 + + + + 2126.574 + + + + 2129.938 + + + + 2130.419 + + + + 2133.303 + + + + 2135.706 + + + + 2138.59 + + + + 2141.955 + + + + 2141.474 + + + + 2143.397 + + + + 2144.839 + + + + 2143.877 + + + + 2147.242 + + + + + + 2148.684 + + + + 2148.203 + + + + 2149.645 + + + + 2150.606 + + + + 2147.242 + + + + 2150.606 + + + + 2149.165 + + + + 2147.723 + + + + 2143.877 + + + + 2145.8 + + + + 2147.242 + + + + 2150.126 + + + + 2152.529 + + + + 2155.894 + + + + 2156.375 + + + + 2158.778 + + + + 2162.142 + + + + 2161.181 + + + + 2160.22 + + + + 2160.7 + + + + 2160.7 + + + + 2157.817 + + + + 2158.778 + + + + 2158.297 + + + + 2161.662 + + + + 2158.297 + + + + 2161.181 + + + + 2157.336 + + + + 2160.7 + + + + 2161.662 + + + + 2160.7 + + + + 2157.336 + + + + 2156.855 + + + + 2153.49 + + + + 2151.087 + + + + 2147.723 + + + + 2146.761 + + + + 2143.877 + + + + 2140.513 + + + + 2140.032 + + + + 2136.667 + + + + 2135.225 + + + + 2133.783 + + + + 2130.9 + + + + 2127.535 + + + + 2125.132 + + + + 2122.728 + + + + 2119.844 + + + + 2116.96 + + + + 2113.596 + + + + 2110.231 + + + + 2107.347 + + + + 2103.983 + + + + 2101.099 + + + + 2097.734 + + + + 2096.773 + + + + 2093.889 + + + + 2090.524 + + + + 2089.563 + + + + 2085.718 + + + + 2082.833 + + + + 2079.469 + + + + 2076.104 + + + + 2073.22 + + + + 2069.856 + + + + 2066.972 + + + + + + 2062.646 + + + + 2062.165 + + + + 2058.32 + + + + 2057.839 + + + + 2053.994 + + + + 2050.629 + + + + 2050.149 + + + + 2050.149 + + + + 2046.784 + + + + 2046.784 + + + + 2043.419 + + + + 2039.574 + + + + 2036.69 + + + + 2034.287 + + + + 2030.923 + + + + 2027.558 + + + + 2030.923 + + + + 2030.923 + + + + 2027.558 + + + + 2027.558 + + + + 2024.674 + + + + + + 2022.271 + + + + 2019.387 + + + + + + 2016.503 + + + + 2013.138 + + + + 2011.696 + + + + + + 2004.486 + + + + 2002.563 + + + + 2000.16 + + + + + + 1969.398 + + + + 1966.995 + + + + 1964.111 + + + + 1960.746 + + + + + + 1957.382 + + + + 1954.978 + + + + 1951.614 + + + + 1950.652 + + + + 1948.73 + + + + 1944.884 + + + + + + 1938.155 + + + + 1938.155 + + + + 1934.791 + + + + 1931.426 + + + + + + 1928.542 + + + + 1925.177 + + + + 1922.293 + + + + 1918.929 + + + + + + 1891.531 + + + + 1892.973 + + + + 1892.493 + + + + 1889.128 + + + + + + 1857.405 + + + + 1854.04 + + + + 1851.156 + + + + + + 1845.869 + + + + 1844.907 + + + + 1841.543 + + + + + + 1832.891 + + + + 1829.526 + + + + 1826.642 + + + + 1825.2 + + + + 1822.316 + + + + 1821.836 + + + + + + 1799.725 + + + + 1796.361 + + + + 1792.996 + + + + 1792.516 + + + + 1789.632 + + + + 1788.67 + + + + + + 1781.941 + + + + 1780.98 + + + + 1778.096 + + + + 1774.731 + + + + 1772.809 + + + + 1769.444 + + + + 1769.444 + + + + 1768.002 + + + + 1764.156 + + + + 1768.002 + + + + 1765.599 + + + + 1762.234 + + + + 1765.599 + + + + 1764.156 + + + + 1759.831 + + + + 1761.273 + + + + 1763.195 + + + + 1764.637 + + + + 1758.869 + + + + 1759.831 + + + + 1762.715 + + + + 1759.35 + + + + 1758.389 + + + + 1761.753 + + + + 1757.427 + + + + 1760.792 + + + + 1756.947 + + + + + + 1682.445 + + + + 1681.964 + + + + 1683.406 + + + + 1682.445 + + + + 1681.003 + + + + 1682.925 + + + + 1681.003 + + + + 1682.925 + + + + 1681.483 + + + + 1679.08 + + + + 1682.925 + + + + 1681.003 + + + + 1680.041 + + + + + + 1680.041 + + + + 1680.041 + + + + diff --git a/tests/gpx_files/waypoints.gpx b/tests/gpx_files/waypoints.gpx new file mode 100644 index 0000000..60486c9 --- /dev/null +++ b/tests/gpx_files/waypoints.gpx @@ -0,0 +1 @@ +Waypoint1639.161Waypoint1955.192Waypoint2129.91Waypoint2136.399Waypoint2174.612Waypoint2156.106Waypoint2155.145Waypoint2152.021Waypoint1854.735Waypoint325.0491Waypoint1766.535Waypoint35.93469Waypoint361.0981Waypoint38.09766Waypoint1612.965Waypoint2163.556Waypoint1535.34 \ No newline at end of file diff --git a/tests/magellan_test.rb b/tests/magellan_test.rb new file mode 100644 index 0000000..ecac714 --- /dev/null +++ b/tests/magellan_test.rb @@ -0,0 +1,18 @@ +require 'test/unit' +require File.dirname(__FILE__) + '/../lib/gpx' + +class TestMagellanTrackLog < Test::Unit::TestCase + MAGELLAN_TRACK_LOG = File.join(File.dirname(__FILE__), "gpx_files/magellan_track.log") + GPX_FILE = File.join(File.dirname(__FILE__), "gpx_files/one_segment.gpx") + + def test_convert + GPX::MagellanTrackLog.convert_to_gpx(MAGELLAN_TRACK_LOG, "/tmp/gpx_from_magellan.gpx") + @gpx_file = GPX::GPXFile.new(:gpx_file => "/tmp/gpx_from_magellan.gpx") + end + + def test_file_type + assert(GPX::MagellanTrackLog::is_magellan_file?(MAGELLAN_TRACK_LOG)) + assert(!GPX::MagellanTrackLog::is_magellan_file?(GPX_FILE)) + end + +end diff --git a/tests/segment_test.rb b/tests/segment_test.rb new file mode 100644 index 0000000..e892832 --- /dev/null +++ b/tests/segment_test.rb @@ -0,0 +1,57 @@ +require 'test/unit' +require File.dirname(__FILE__) + '/../lib/gpx' + +class TestSegment < Test::Unit::TestCase + ONE_SEGMENT = File.join(File.dirname(__FILE__), "gpx_files/one_segment.gpx") + + def setup + @gpx_file = GPX::GPXFile.new(:gpx_file => ONE_SEGMENT) + @segment = @gpx_file.tracks.first.segments.first + end + + def test_segment_read + assert_equal(189, @segment.points.size) + assert_equal("Fri Apr 07 18:12:05 UTC 2006", @segment.earliest_point.time.to_s) + assert_equal("Fri Apr 07 19:26:31 UTC 2006", @segment.latest_point.time.to_s) + assert_equal(1334.447, @segment.lowest_point.elevation) + assert_equal(1480.087, @segment.highest_point.elevation) + assert_equal("6.98803359528853", @segment.distance.to_s) + end + + def test_segment_crop + crop_rectangle = GPX::Bounds.new( :min_lat=> 39.173000, + :min_lon=> -109.010000, + :max_lat=> 39.188000, + :max_lon=> -108.999000) + @segment.crop(crop_rectangle) + + assert_equal(106, @segment.points.size) + assert_equal("4.11422061733046", @segment.distance.to_s) + assert_equal("Fri Apr 07 18:37:21 UTC 2006", @segment.earliest_point.time.to_s) + assert_equal("Fri Apr 07 19:22:32 UTC 2006", @segment.latest_point.time.to_s) + assert_equal(1407.027, @segment.lowest_point.elevation) + assert_equal(1480.087, @segment.highest_point.elevation) + assert_equal(39.173834, @segment.bounds.min_lat) + assert_equal(-109.009995, @segment.bounds.min_lon) + assert_equal(39.187868, @segment.bounds.max_lat) + assert_equal(-108.999546, @segment.bounds.max_lon) + end + + def test_segment_delete + delete_rectangle = GPX::Bounds.new( :min_lat=> 39.173000, + :min_lon=> -109.010000, + :max_lat=> 39.188000, + :max_lon=> -108.999000) + @segment.delete_area(delete_rectangle) + assert_equal(83, @segment.points.size) + assert_equal("3.35967118153605", @segment.distance.to_s) + assert_equal("Fri Apr 07 18:12:05 UTC 2006", @segment.earliest_point.time.to_s) + assert_equal("Fri Apr 07 19:26:31 UTC 2006", @segment.latest_point.time.to_s) + assert_equal(1334.447, @segment.lowest_point.elevation) + assert_equal(1428.176, @segment.highest_point.elevation) + assert_equal(39.180572, @segment.bounds.min_lat) + assert_equal(-109.016604, @segment.bounds.min_lon) + assert_equal(39.188747, @segment.bounds.max_lat) + assert_equal(-109.007978, @segment.bounds.max_lon) + end +end diff --git a/tests/track_file_test.rb b/tests/track_file_test.rb new file mode 100644 index 0000000..f5650ab --- /dev/null +++ b/tests/track_file_test.rb @@ -0,0 +1,75 @@ +require 'test/unit' +require File.dirname(__FILE__) + '/../lib/gpx' + +class TestTrackFile < Test::Unit::TestCase + TRACK_FILE = File.join(File.dirname(__FILE__), "gpx_files/tracks.gpx") + OTHER_TRACK_FILE = File.join(File.dirname(__FILE__), "gpx_files/arches.gpx") + + def setup + @track_file = GPX::GPXFile.new(:gpx_file => TRACK_FILE) + @other_track_file = GPX::GPXFile.new(:gpx_file => OTHER_TRACK_FILE) + end + + def test_track_read + assert_equal(3, @track_file.tracks.size) + assert_equal("First Track", @track_file.tracks[0].name) + assert_equal("Second Track", @track_file.tracks[1].name) + assert_equal("Third Track", @track_file.tracks[2].name) + end + + def test_track_segment_and_point_counts + # One segment with 398 points... + assert_equal(1, @track_file.tracks[0].segments.size) + assert_equal(389, @track_file.tracks[0].segments.first.points.size) + + # One segment with 299 points... + assert_equal(1, @track_file.tracks[1].segments.size) + assert_equal(299, @track_file.tracks[1].segments.first.points.size) + + # Many segments of many different sizes + segment_sizes = %w{ 2 2 5 4 2 1 197 31 54 1 15 54 19 26 109 18 9 2 8 3 10 23 21 11 25 32 66 21 2 3 3 4 6 4 4 4 3 3 6 6 27 13 2 } + assert_equal(43, @track_file.tracks[2].segments.size) + @track_file.tracks[2].segments.each_with_index do |seg, i| + assert_equal(segment_sizes[i].to_i, seg.points.size) + end + last_segment = @track_file.tracks[2].segments.last + assert_equal(1680.041, last_segment.points.last.elevation) + + + second_to_last_segment = @track_file.tracks[2].segments[-2] + assert_equal("2006-01-02T00:00:51Z", second_to_last_segment.points.last.time.strftime("%Y-%m-%dT%H:%M:%SZ")) + assert_equal(39.998045, second_to_last_segment.points.last.lat) + assert_equal(-105.292368, second_to_last_segment.points.last.lon) + end + + def test_find_nearest_point_by_time + time = Time.parse("2005-12-31T22:02:01Z") + pt = @track_file.tracks[0].closest_point(time) + #puts "pt: #{pt.lat_lon}" + + end + + def test_find_distance + #puts "Distance: #{@other_track_file.distance(:units => 'miles')} miles" + #puts "Distance: #{@track_file.distance(:units => 'miles')} miles" + end + def test_high_low_elevation + #puts "Lowest: #{@track_file.lowest_point.elevation} m" + #puts "Highest: #{@track_file.highest_point.elevation} m" + end + + def test_duration + #puts "Duration 1: #{@other_track_file.duration} " + #puts "Duration 2: #{@track_file.duration} " + end + + def test_average_speed + #puts "Speed 1: #{@other_track_file.average_speed} " + #puts "Speed 2: #{@track_file.average_speed} " + end + + def test_write + @other_track_file.write("myoutput.gpx") + end + +end diff --git a/tests/track_test.rb b/tests/track_test.rb new file mode 100644 index 0000000..0c9941f --- /dev/null +++ b/tests/track_test.rb @@ -0,0 +1,65 @@ +require 'test/unit' +require File.dirname(__FILE__) + '/../lib/gpx' + +class TestTrack < Test::Unit::TestCase + ONE_TRACK = File.join(File.dirname(__FILE__), "gpx_files/one_track.gpx") + + def setup + @gpx_file = GPX::GPXFile.new(:gpx_file => ONE_TRACK) + @track = @gpx_file.tracks.first + end + + def test_track_read + assert_equal("ACTIVE LOG", @track.name) + assert_equal( 364, @track.points.size) + assert_equal(8, @track.segments.size) + assert_equal("3.07249668492626", @track.distance.to_s) + assert_equal(1267.155, @track.lowest_point.elevation) + assert_equal(1594.003, @track.highest_point.elevation) + assert_equal(38.681488, @track.bounds.min_lat) + assert_equal(-109.606948, @track.bounds.min_lon) + assert_equal(38.791759, @track.bounds.max_lat) + assert_equal(-109.447045, @track.bounds.max_lon) + end + + def test_track_crop + area = GPX::Bounds.new( + :min_lat => 38.710000, + :min_lon => -109.600000, + :max_lat => 38.791759, + :max_lon => -109.450000) + @track.crop(area) + assert_equal("ACTIVE LOG", @track.name) + assert_equal( 111, @track.points.size) + assert_equal(4, @track.segments.size) + assert_equal("1.62136024923607", @track.distance.to_s) + assert_equal(1557.954, @track.lowest_point.elevation) + assert_equal(1582.468, @track.highest_point.elevation) + assert_equal(38.782511, @track.bounds.min_lat) + assert_equal(-109.599781, @track.bounds.min_lon) + assert_equal(38.789527, @track.bounds.max_lat) + assert_equal(-109.594996, @track.bounds.max_lon) + end + + def test_track_delete + area = GPX::Bounds.new( + :min_lat => 38.710000, + :min_lon => -109.600000, + :max_lat => 38.791759, + :max_lon => -109.450000) + @track.delete_area(area) + + #puts @track + #assert_equal("ACTIVE LOG", @track.name) + #assert_equal( 111, @track.points.size) + #assert_equal(4, @track.segments.size) + #assert_equal("1.62136024923607", @track.distance.to_s) + #assert_equal(1557.954, @track.lowest_point.elevation) + #assert_equal(1582.468, @track.highest_point.elevation) + #assert_equal(38.782511, @track.bounds.min_lat) + #assert_equal(-109.599781, @track.bounds.min_lon) + #assert_equal(38.789527, @track.bounds.max_lat) + #assert_equal(-109.594996, @track.bounds.max_lon) + end + +end