Compare commits
No commits in common. "release-0.1" and "master" have entirely different histories.
release-0.
...
master
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
tests/output/*
|
||||
.*.swp
|
||||
Gemfile.lock
|
60
ChangeLog
Normal file
60
ChangeLog
Normal file
@ -0,0 +1,60 @@
|
||||
2010-02-27 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Putting the gem building stuff into a gemspec.
|
||||
* Fixing some tests since git does not believe in empty directories.
|
||||
* Fixing README formatting.
|
||||
|
||||
2010-02-27 Doug Fales <doug@falesafeconsulting.com>
|
||||
* README edits.
|
||||
* More rdoc tweaks.
|
||||
* Changing README to rdoc ext for github.
|
||||
|
||||
2009-10-13 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Adding the ability to write GPX to a string in addition to a file. Thanks to Douglas Robertson for the patch.
|
||||
|
||||
2009-09-27 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Adding a patch from Douglas Robertson that allows using version 1.0 of the schema for output.
|
||||
|
||||
2009-07-07 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Adding changelog.
|
||||
* Revving to version 0.5.
|
||||
* Changing my contact email address.
|
||||
* Patches from Tom Verbeure (mtbguru.com) to work with libxml-ruby 1.x.
|
||||
|
||||
2009-06-17 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Patch from Kang-min Liu to support speed element.
|
||||
|
||||
2008-02-19 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Revving to 0.4.
|
||||
* Adding some new unit tests and fixing several file export bugs reported by Jochen Topf. New unit tests also uncovered a bug where the number of trackpoints reported in a file was twice the actual number.
|
||||
|
||||
2008-02-11 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Going to version 0.3.
|
||||
* Updating unit tests in light of recent fixes to routes and waypoints code.
|
||||
|
||||
2008-02-08 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Thanks to Mike Gauland for discovering some route- and waypoint-related bugs. I've fixed them and also added #to_s on Waypoint so it's easier to debug.
|
||||
|
||||
2007-12-04 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Thanks to Christian Koerner for finding and fixing these bugs in the waypoint code.
|
||||
* Another patch from Gaku Ueda. This one allows you to pass in a string of GPX data using the :gpx_date => option. Thanks Gaku!
|
||||
|
||||
2007-11-30 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Updating the version #.
|
||||
* Updates courtesy of Gaku Ueda: * Adding support for GPX 1.0 as well as 1.1 (since libxml namespace parsing was hard-coded to 1.1. previously). * Adding a GPX 1.0 unit test file. * Miscellaneous updates to make it work with Ruby 1.8.6.
|
||||
|
||||
2006-12-04 Doug Fales <doug@falesafeconsulting.com>
|
||||
* First stab at using libxml-ruby instead of REXML. I'm seeing the unit tests finish in under 14 seconds. That is compared to 2 minutes using REXML.
|
||||
|
||||
2006-12-03 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Fixing more nil time exceptions.
|
||||
* Fixing an exception in contains_time?.
|
||||
|
||||
2006-11-28 Doug Fales <doug@falesafeconsulting.com>
|
||||
* A couple of fixes to make the library comply with the different attribute names possible on the bounds element.
|
||||
|
||||
2006-10-28 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Fixing nil time bug.
|
||||
|
||||
2006-10-14 Doug Fales <doug@falesafeconsulting.com>
|
||||
* Initial import of gpx gem.
|
||||
|
4
Gemfile
Normal file
4
Gemfile
Normal file
@ -0,0 +1,4 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in seryz.gemspec
|
||||
gemspec
|
@ -1,8 +1,9 @@
|
||||
= GPX Gem
|
||||
Copyright (C) 2006 Doug Fales
|
||||
Doug Fales mailto:doug.fales@gmail.com
|
||||
|
||||
Copyright (C) 2006 Doug Fales mailto:doug@falesafeconsulting.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.
|
||||
@ -14,6 +15,7 @@ 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
|
||||
@ -27,13 +29,14 @@ Converting a Magellan track log to GPX:
|
||||
|
||||
|
||||
== 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
|
||||
and my website, WalkingBoss.org (RIP). 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
|
||||
Since this code uses XML 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.
|
||||
|
72
Rakefile
72
Rakefile
@ -1,20 +1,6 @@
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'bundler/gem_tasks'
|
||||
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/**/*"
|
||||
]
|
||||
require 'rdoc/task'
|
||||
|
||||
desc "Default Task"
|
||||
task :default => [ :test ]
|
||||
@ -27,12 +13,6 @@ Rake::TestTask.new("test") { |t|
|
||||
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|
|
||||
@ -41,51 +21,3 @@ Rake::RDocTask.new("doc") { |rdoc|
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
}
|
||||
|
||||
# Genereate the package
|
||||
spec = Gem::Specification.new do |s|
|
||||
|
||||
#### Basic information.
|
||||
|
||||
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
|
||||
|
||||
#### Which files are to be included in this gem? Everything! (Except CVS directories.)
|
||||
|
||||
s.files = PKG_FILES
|
||||
|
||||
#### Load-time details: library and application (you will need one or both).
|
||||
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'gpx'
|
||||
|
||||
#### Documentation and testing.
|
||||
|
||||
s.has_rdoc = true
|
||||
|
||||
#### Author and project details.
|
||||
|
||||
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
|
||||
|
22
gpx.gemspec
Normal file
22
gpx.gemspec
Normal file
@ -0,0 +1,22 @@
|
||||
# coding: utf-8
|
||||
lib = File.expand_path('../lib', __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require 'gpx/version'
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'gpx'
|
||||
s.version = GPX::VERSION
|
||||
s.authors = ["Guillaume Dott", "Doug Fales"]
|
||||
s.email = ["guillaume+github@dott.fr", "doug.fales@gmail.com"]
|
||||
s.summary = %q{A basic API for reading and writing GPX files.}
|
||||
s.description = %q{A basic API for reading and writing GPX files.}
|
||||
|
||||
s.files = `git ls-files`.split($/)
|
||||
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
||||
s.require_paths = ["lib"]
|
||||
s.has_rdoc = true
|
||||
|
||||
s.homepage = "http://dougfales.github.com/gpx/"
|
||||
s.rubyforge_project = "gpx"
|
||||
s.add_dependency 'nokogiri'
|
||||
end
|
11
lib/gpx.rb
11
lib/gpx.rb
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -20,11 +20,12 @@
|
||||
# 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 'nokogiri'
|
||||
|
||||
require 'gpx/version'
|
||||
|
||||
require 'gpx/gpx'
|
||||
require 'gpx/gpx_file'
|
||||
require 'gpx/bounds'
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -21,63 +21,54 @@
|
||||
# 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
|
||||
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
|
||||
# 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
|
||||
|
||||
# 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 middle latitude.
|
||||
def center_lat
|
||||
distance = (max_lat - min_lat)/2.0
|
||||
(min_lat + distance)
|
||||
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
|
||||
|
||||
# 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
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -21,33 +21,26 @@
|
||||
# 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
|
||||
|
||||
# A common base class which provides a useful initializer method to many
|
||||
# class in the GPX library.
|
||||
class Base
|
||||
# This initializer can take an XML::Node 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|
|
||||
child_xpath = "#{el}"
|
||||
unless parent.at(child_xpath).nil?
|
||||
val = parent.at(child_xpath).inner_text
|
||||
self.send("#{el}=", val)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -21,202 +21,268 @@
|
||||
# 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
|
||||
class GPXFile < Base
|
||||
attr_accessor :tracks, :routes, :waypoints, :bounds, :lowest_point, :highest_point, :duration, :ns, :time, :name
|
||||
|
||||
# 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 read a GPX file from a string, use :gpx_data.
|
||||
# gpx_file = GPXFile.new(:gpx_data => '<xml ...><gpx>...</gpx>)
|
||||
# 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] or opts[:gpx_data])
|
||||
if opts[:gpx_file]
|
||||
gpx_file = opts[:gpx_file]
|
||||
gpx_file = File.open(gpx_file) unless gpx_file.is_a?(File)
|
||||
@xml = Nokogiri::XML(gpx_file)
|
||||
else
|
||||
@xml = Nokogiri::XML(opts[:gpx_data])
|
||||
end
|
||||
|
||||
# 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)
|
||||
reset_meta_data
|
||||
bounds_element = (@xml.at("metadata/bounds") rescue nil)
|
||||
if bounds_element
|
||||
@bounds.min_lat = get_bounds_attr_value(bounds_element, %w{ min_lat minlat minLat })
|
||||
@bounds.min_lon = get_bounds_attr_value(bounds_element, %w{ min_lon minlon minLon})
|
||||
@bounds.max_lat = get_bounds_attr_value(bounds_element, %w{ max_lat maxlat maxLat})
|
||||
@bounds.max_lon = get_bounds_attr_value(bounds_element, %w{ max_lon maxlon maxLon})
|
||||
else
|
||||
get_bounds = true
|
||||
end
|
||||
|
||||
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
|
||||
@time = Time.parse(@xml.at("metadata/time").inner_text) rescue nil
|
||||
@name = @xml.at("metadata/name").inner_text rescue nil
|
||||
@tracks = []
|
||||
@xml.search("trk").each do |trk|
|
||||
trk = Track.new(:element => trk, :gpx_file => self)
|
||||
update_meta_data(trk, get_bounds)
|
||||
@tracks << trk
|
||||
end
|
||||
@waypoints = []
|
||||
@xml.search("wpt").each { |wpt| @waypoints << Waypoint.new(:element => wpt, :gpx_file => self) }
|
||||
@routes = []
|
||||
@xml.search("rte").each { |rte| @routes << Route.new(:element => rte, :gpx_file => self) }
|
||||
@tracks.delete_if { |t| t.empty? }
|
||||
|
||||
@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) }
|
||||
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
|
||||
@tracks ||= []
|
||||
@routes ||= []
|
||||
@waypoints ||= []
|
||||
end
|
||||
|
||||
@tracks.delete_if { |t| t.empty? }
|
||||
def get_bounds_attr_value(el, possible_names)
|
||||
result = nil
|
||||
possible_names.each do |name|
|
||||
result = el[name]
|
||||
break unless result.nil?
|
||||
end
|
||||
return (result.to_f rescue nil)
|
||||
end
|
||||
|
||||
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
|
||||
# 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.nil? and trk.lowest_point.elevation < @lowest_point.elevation))
|
||||
@highest_point = trk.highest_point if(@highest_point.nil? or (!trk.highest_point.nil? and 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 <filename>.
|
||||
# If the file does not exist, it is created. If it does exist, it is overwritten.
|
||||
def write(filename, update_time = true)
|
||||
@time = Time.now if(@time.nil? or update_time)
|
||||
@name ||= File.basename(filename)
|
||||
doc = generate_xml_doc
|
||||
File.open(filename, 'w') { |f| f.write(doc.to_xml) }
|
||||
end
|
||||
|
||||
def to_s(update_time = true)
|
||||
@time = Time.now if(@time.nil? or update_time)
|
||||
doc = generate_xml_doc
|
||||
doc.to_xml
|
||||
end
|
||||
|
||||
def inspect
|
||||
"<#{self.class.name}:...>"
|
||||
end
|
||||
|
||||
private
|
||||
def generate_xml_doc
|
||||
version = '1.1'
|
||||
version_dir = version.gsub('.','/')
|
||||
|
||||
doc = Nokogiri::XML::Builder.new do |xml|
|
||||
xml.gpx(
|
||||
'xsi' => "http://www.w3.org/2001/XMLSchema-instance",
|
||||
'version' => version.to_s,
|
||||
'creator' => "GPX RubyGem #{GPX::VERSION}",
|
||||
'xsi:schemaLocation' => "http://www.topografix.com/GPX/#{version_dir} http://www.topografix.com/GPX/#{version_dir}/gpx.xsd") \
|
||||
{
|
||||
xml.metadata {
|
||||
xml.name @name
|
||||
xml.time @time.xmlschema
|
||||
xml.bound(
|
||||
minlat: bounds.min_lat,
|
||||
minlon: bounds.min_lon,
|
||||
maxlat: bounds.max_lat,
|
||||
maxlon: bounds.max_lon,
|
||||
)
|
||||
}
|
||||
|
||||
tracks.each do |t|
|
||||
xml.trk {
|
||||
xml.name t.name
|
||||
|
||||
t.segments.each do |seg|
|
||||
xml.trkseg {
|
||||
seg.points.each do |p|
|
||||
xml.trkpt(lat: p.lat, lon: p.lon) {
|
||||
xml.time p.time.xmlschema unless p.time.nil?
|
||||
xml.ele p.elevation unless p.elevation.nil?
|
||||
}
|
||||
end
|
||||
}
|
||||
end
|
||||
}
|
||||
end unless tracks.nil?
|
||||
|
||||
waypoints.each do |w|
|
||||
xml.wpt(lat: w.lat, lon: w.lon) {
|
||||
Waypoint::SUB_ELEMENTS.each do |sub_elem|
|
||||
xml.send(sub_elem, w.send(sub_elem)) if w.respond_to?(sub_elem) && !w.send(sub_elem).nil?
|
||||
end
|
||||
}
|
||||
end unless waypoints.nil?
|
||||
|
||||
routes.each do |r|
|
||||
xml.rte {
|
||||
xml.name r.name
|
||||
|
||||
r.points.each do |p|
|
||||
xml.rtept(lat: p.lat, lon: p.lon) {
|
||||
xml.time p.time.xmlschema unless p.time.nil?
|
||||
xml.ele p.elevation unless p.elevation.nil?
|
||||
}
|
||||
end
|
||||
}
|
||||
end unless routes.nil?
|
||||
}
|
||||
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
|
||||
return doc
|
||||
end
|
||||
|
||||
# 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
|
||||
|
||||
# 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 <filename>.
|
||||
# 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
|
||||
@duration = (@tracks[-1].segments[-1].points[-1].time - @tracks.first.segments.first.points.first.time)
|
||||
rescue
|
||||
@duration = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -21,114 +21,112 @@
|
||||
# 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.
|
||||
|
||||
# This class will parse the lat/lon and time data from a Magellan track log,
|
||||
# which is a NMEA formatted CSV list of points.
|
||||
#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.
|
||||
|
||||
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.
|
||||
# $PMGNTRK,llll.ll,a,yyyyy.yy,a,xxxxx,a,hhmmss.ss,A,c----c,ddmmyy*hh<CR><LF>
|
||||
require 'csv'
|
||||
|
||||
#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.
|
||||
LAT = 1
|
||||
LAT_HEMI = 2
|
||||
LON = 3
|
||||
LON_HEMI = 4
|
||||
ELE = 5
|
||||
ELE_UNITS = 6
|
||||
TIME = 7
|
||||
INVALID_FLAG = 8
|
||||
DATE = 10
|
||||
|
||||
# $PMGNTRK,llll.ll,a,yyyyy.yy,a,xxxxx,a,hhmmss.ss,A,c----c,ddmmyy*hh<CR><LF>
|
||||
require 'csv'
|
||||
FEET_TO_METERS = 0.3048
|
||||
|
||||
LAT = 1
|
||||
LAT_HEMI = 2
|
||||
LON = 3
|
||||
LON_HEMI = 4
|
||||
ELE = 5
|
||||
ELE_UNITS = 6
|
||||
TIME = 7
|
||||
INVALID_FLAG = 8
|
||||
DATE = 10
|
||||
class << self
|
||||
|
||||
FEET_TO_METERS = 0.3048
|
||||
# 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)
|
||||
|
||||
class << self
|
||||
segment = Segment.new
|
||||
|
||||
# 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)
|
||||
CSV.open(magellan_filename, "r").each do |row|
|
||||
next if(row.size < 10 or row[INVALID_FLAG] == 'V')
|
||||
|
||||
segment = Segment.new
|
||||
lat_deg = row[LAT][0..1]
|
||||
lat_min = row[LAT][2...-1]
|
||||
lat_hemi = row[LAT_HEMI]
|
||||
|
||||
CSV.open(magellan_filename, "r") do |row|
|
||||
next if(row.size < 10 or row[INVALID_FLAG] == 'V')
|
||||
lat = lat_deg.to_f + (lat_min.to_f / 60.0)
|
||||
lat = (-lat) if(lat_hemi == 'S')
|
||||
|
||||
lat_deg = row[LAT][0..1]
|
||||
lat_min = row[LAT][2...-1]
|
||||
lat_hemi = row[LAT_HEMI]
|
||||
lon_deg = row[LON][0..2]
|
||||
lon_min = row[LON][3..-1]
|
||||
lon_hemi = row[LON_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')
|
||||
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
|
||||
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
|
||||
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)
|
||||
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)
|
||||
#must create point
|
||||
pt = TrackPoint.new(:lat => lat, :lon => lon, :time => time, :elevation => ele)
|
||||
segment.append_point(pt)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
trk = Track.new
|
||||
trk.append_segment(segment)
|
||||
gpx_file = GPXFile.new(:tracks => [trk])
|
||||
gpx_file.write(gpx_filename)
|
||||
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
|
||||
# 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
|
||||
|
127
lib/gpx/point.rb
127
lib/gpx/point.rb
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -20,84 +20,71 @@
|
||||
# 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
|
||||
# The base class for all points. Trackpoint and Waypoint both descend from this base class.
|
||||
class Point < Base
|
||||
D_TO_R = Math::PI/180.0;
|
||||
attr_accessor :lat, :lon, :time, :elevation, :gpx_file, :speed
|
||||
|
||||
# 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 an XML 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 } )
|
||||
@gpx_file = opts[:gpx_file]
|
||||
if (opts[:element])
|
||||
elem = opts[:element]
|
||||
@lat, @lon = elem["lat"].to_f, elem["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.at("time").inner_text) rescue nil)
|
||||
@elevation = elem.at("ele").inner_text.to_f unless elem.at("ele").nil?
|
||||
@speed = elem.at("speed").inner_text.to_f unless elem.at("speed").nil?
|
||||
else
|
||||
@lat = opts[:lat]
|
||||
@lon = opts[:lon]
|
||||
@elevation = opts[:elevation]
|
||||
@time = opts[:time]
|
||||
@speed = opts[:speed]
|
||||
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
|
||||
# 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
|
||||
|
||||
# Latitude in radians.
|
||||
def latr
|
||||
@latr ||= (@lat * D_TO_R)
|
||||
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
|
||||
|
||||
# Longitude in radians.
|
||||
def lonr
|
||||
@lonr ||= (@lon * D_TO_R)
|
||||
end
|
||||
# Latitude in radians.
|
||||
def latr
|
||||
@latr ||= (@lat * D_TO_R)
|
||||
end
|
||||
|
||||
# Set the latitude (in degrees).
|
||||
def lat=(latitude)
|
||||
@latr = (latitude * D_TO_R)
|
||||
@lat = latitude
|
||||
end
|
||||
# Longitude in radians.
|
||||
def lonr
|
||||
@lonr ||= (@lon * D_TO_R)
|
||||
end
|
||||
|
||||
# Set the longitude (in degrees).
|
||||
def lon=(longitude)
|
||||
@lonr = (longitude * D_TO_R)
|
||||
@lon = longitude
|
||||
end
|
||||
# Set the latitude (in degrees).
|
||||
def lat=(latitude)
|
||||
@latr = (latitude * D_TO_R)
|
||||
@lat = latitude
|
||||
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
|
||||
# Set the longitude (in degrees).
|
||||
def lon=(longitude)
|
||||
@lonr = (longitude * D_TO_R)
|
||||
@lon = longitude
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -21,45 +21,38 @@
|
||||
# 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
|
||||
|
||||
# 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
|
||||
attr_accessor :points, :name, :gpx_file
|
||||
|
||||
# Initialize a Route from a XML::Node.
|
||||
def initialize(opts = {})
|
||||
if(opts[:gpx_file] and opts[:element])
|
||||
rte_element = opts[:element]
|
||||
@gpx_file = opts[:gpx_file]
|
||||
@name = rte_element.at("name").inner_text
|
||||
@points = []
|
||||
rte_element.search("rtept").each do |point|
|
||||
@points << Point.new(:element => point, :gpx_file => @gpx_file)
|
||||
end
|
||||
else
|
||||
@points = (opts[:points] or [])
|
||||
@name = (opts[:name])
|
||||
end
|
||||
|
||||
# Delete points outside of a given area.
|
||||
def crop(area)
|
||||
points.delete_if{ |pt| not area.contains? pt }
|
||||
end
|
||||
end
|
||||
|
||||
# Delete points within the given area.
|
||||
def delete_area(area)
|
||||
points.delete_if{ |pt| area.contains? pt }
|
||||
end
|
||||
# Delete points outside of a given area.
|
||||
def crop(area)
|
||||
points.delete_if{ |pt| not 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
|
||||
# Delete points within the given area.
|
||||
def delete_area(area)
|
||||
points.delete_if{ |pt| area.contains? pt }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -21,197 +21,194 @@
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
# If a XML::Node object is passed-in, this will initialize a new
|
||||
# Segment based on its contents. Otherwise, a blank Segment is created.
|
||||
def initialize(opts = {})
|
||||
@gpx_file = opts[:gpx_file]
|
||||
@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
|
||||
if segment_element.is_a?(Nokogiri::XML::Node)
|
||||
segment_element.search("trkpt").each do |trkpt|
|
||||
pt = TrackPoint.new(:element => trkpt, :segment => self, :gpx_file => @gpx_file)
|
||||
unless pt.time.nil?
|
||||
@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)
|
||||
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
|
||||
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
|
||||
end
|
||||
@points = keep_points
|
||||
@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
|
||||
|
||||
# Returns true if this Segment has no points.
|
||||
def empty?
|
||||
(points.nil? or (points.size == 0))
|
||||
# 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) rescue false
|
||||
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
|
||||
|
||||
# Converts this Segment to a REXML::Element object.
|
||||
def to_xml
|
||||
seg = Element.new('trkseg')
|
||||
points.each { |pt| seg.elements << pt.to_xml }
|
||||
seg
|
||||
# Returns true if this Segment has no points.
|
||||
def empty?
|
||||
(points.nil? or (points.size == 0))
|
||||
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
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
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
|
||||
RADIUS = 6371; # earth's mean radius in km
|
||||
|
||||
return pts[midpoint]
|
||||
# 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
|
||||
|
||||
elsif(time <= pts[midpoint].time)
|
||||
return find_closest(pts[0..midpoint], time)
|
||||
else
|
||||
return find_closest(pts[(midpoint+1)..-1], time)
|
||||
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)
|
||||
unless pt.time.nil?
|
||||
@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)
|
||||
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;
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
end
|
||||
|
211
lib/gpx/track.rb
211
lib/gpx/track.rb
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -21,129 +21,116 @@
|
||||
# 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
|
||||
|
||||
# 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
|
||||
# Initialize a track from a XML::Node, 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.at("name").inner_text rescue "")
|
||||
trk_element.search("trkseg").each do |seg_element|
|
||||
seg = Segment.new(:element => seg_element, :track => self, :gpx_file => @gpx_file)
|
||||
update_meta_data(seg)
|
||||
@segments << seg
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
# 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.select { |s| s.contains_time?(time) }
|
||||
segment.first
|
||||
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
|
||||
|
||||
# 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)
|
||||
# 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
|
||||
|
||||
# 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
|
||||
# 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
|
||||
|
||||
# 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
|
||||
# Prints out a friendly summary of this track (sans points). Useful for
|
||||
# debugging and sanity checks.
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
# 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
|
||||
protected
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
protected
|
||||
def reset_meta_data
|
||||
@bounds = Bounds.new
|
||||
@highest_point = nil
|
||||
@lowest_point = nil
|
||||
@distance = 0.0
|
||||
@points = []
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -21,15 +21,15 @@
|
||||
# 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
|
||||
# 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
|
||||
|
3
lib/gpx/version.rb
Normal file
3
lib/gpx/version.rb
Normal file
@ -0,0 +1,3 @@
|
||||
module GPX
|
||||
VERSION = "0.7"
|
||||
end
|
@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2006 Doug Fales
|
||||
# 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
|
||||
@ -20,54 +20,55 @@
|
||||
# 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
|
||||
|
||||
# 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 = %w{ magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions ele}
|
||||
|
||||
SUB_ELEMENTS = %q{ magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions }
|
||||
attr_reader :gpx_file
|
||||
SUB_ELEMENTS.each { |sub_el| attr_accessor sub_el.to_sym }
|
||||
|
||||
attr_reader :gpx_file
|
||||
# Not implemented
|
||||
def crop(area)
|
||||
end
|
||||
|
||||
# Not implemented
|
||||
def crop(area)
|
||||
# Not implemented
|
||||
def delete_area(area)
|
||||
end
|
||||
|
||||
# Initializes a waypoint from a XML::Node.
|
||||
def initialize(opts = {})
|
||||
if(opts[:element] and opts[:gpx_file])
|
||||
wpt_elem = opts[:element]
|
||||
@gpx_file = opts[:gpx_file]
|
||||
super(:element => wpt_elem, :gpx_file => @gpx_file)
|
||||
instantiate_with_text_elements(wpt_elem, SUB_ELEMENTS)
|
||||
else
|
||||
opts.each do |key, value|
|
||||
assignment_method = "#{key}="
|
||||
if self.respond_to?(assignment_method)
|
||||
self.send(assignment_method, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Not implemented
|
||||
def delete_area(area)
|
||||
# Prints out a friendly summary of this track (sans points). Useful for
|
||||
# debugging and sanity checks.
|
||||
def to_s
|
||||
result = "Waypoint \n"
|
||||
result << "\tName: #{name}\n"
|
||||
result << "\tLatitude: #{lat} \n"
|
||||
result << "\tLongitude: #{lon} \n"
|
||||
result << "\tElevation: #{elevation}\n "
|
||||
result << "\tTime: #{time}\n"
|
||||
SUB_ELEMENTS.each do |sub_element_attribute|
|
||||
val = self.send(sub_element_attribute)
|
||||
result << "\t#{sub_element_attribute}: #{val}\n" unless val.nil?
|
||||
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
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
12
tests/gpx10_test.rb
Normal file
12
tests/gpx10_test.rb
Normal file
@ -0,0 +1,12 @@
|
||||
require 'test/unit'
|
||||
require 'gpx'
|
||||
|
||||
class GPX10Test < Test::Unit::TestCase
|
||||
GPX_FILE = File.join(File.dirname(__FILE__), "gpx_files/gpx10.gpx")
|
||||
|
||||
def test_read
|
||||
# make sure we can read a GPX 1.0 file
|
||||
@gpx_file = GPX::GPXFile.new(:gpx_file => GPX_FILE)
|
||||
end
|
||||
|
||||
end
|
48
tests/gpx_file_test.rb
Normal file
48
tests/gpx_file_test.rb
Normal file
@ -0,0 +1,48 @@
|
||||
require 'test/unit'
|
||||
require 'gpx'
|
||||
|
||||
class GPXFileTest < Test::Unit::TestCase
|
||||
|
||||
ONE_TRACK_FILE = File.join(File.dirname(__FILE__), "gpx_files/one_track.gpx")
|
||||
WITH_OR_WITHOUT_ELEV_FILE = File.join(File.dirname(__FILE__), "gpx_files/with_or_without_elev.gpx")
|
||||
BIG_FILE = File.join(File.dirname(__FILE__), "gpx_files/big.gpx")
|
||||
|
||||
def test_load_data_from_string
|
||||
gpx_file = GPX::GPXFile.new(:gpx_data => open(ONE_TRACK_FILE).read)
|
||||
assert_equal(1, gpx_file.tracks.size)
|
||||
assert_equal(8, gpx_file.tracks.first.segments.size)
|
||||
assert_equal("ACTIVE LOG", gpx_file.tracks.first.name)
|
||||
assert_equal("active_log.gpx", gpx_file.name)
|
||||
assert_equal("2006-04-08T16:44:28Z", gpx_file.time.xmlschema)
|
||||
assert_equal(38.681488, gpx_file.bounds.min_lat)
|
||||
assert_equal(-109.606948, gpx_file.bounds.min_lon)
|
||||
assert_equal(38.791759, gpx_file.bounds.max_lat)
|
||||
assert_equal(-109.447045, gpx_file.bounds.max_lon)
|
||||
end
|
||||
|
||||
def test_load_data_from_file
|
||||
gpx_file = GPX::GPXFile.new(:gpx_file => ONE_TRACK_FILE)
|
||||
assert_equal(1, gpx_file.tracks.size)
|
||||
assert_equal(8, gpx_file.tracks.first.segments.size)
|
||||
assert_equal("ACTIVE LOG", gpx_file.tracks.first.name)
|
||||
assert_equal("active_log.gpx", gpx_file.name)
|
||||
assert_equal("2006-04-08T16:44:28Z", gpx_file.time.xmlschema)
|
||||
assert_equal(38.681488, gpx_file.bounds.min_lat)
|
||||
assert_equal(-109.606948, gpx_file.bounds.min_lon)
|
||||
assert_equal(38.791759, gpx_file.bounds.max_lat)
|
||||
assert_equal(-109.447045, gpx_file.bounds.max_lon)
|
||||
end
|
||||
|
||||
def test_big_file
|
||||
gpx_file = GPX::GPXFile.new(:gpx_file => BIG_FILE)
|
||||
assert_equal(1, gpx_file.tracks.size)
|
||||
assert_equal(7968, gpx_file.tracks.first.points.size)
|
||||
end
|
||||
|
||||
def test_with_or_with_elev
|
||||
gpx_file = GPX::GPXFile.new(:gpx_file => WITH_OR_WITHOUT_ELEV_FILE)
|
||||
assert_equal(2, gpx_file.tracks.size)
|
||||
#assert_equal(7968, gpx_file.tracks.first.points.size)
|
||||
end
|
||||
|
||||
end
|
File diff suppressed because one or more lines are too long
1
tests/gpx_files/big.gpx
Normal file
1
tests/gpx_files/big.gpx
Normal file
File diff suppressed because one or more lines are too long
17
tests/gpx_files/gpx10.gpx
Normal file
17
tests/gpx_files/gpx10.gpx
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.0"
|
||||
creator="GPSBabel - http://www.gpsbabel.org"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.topografix.com/GPX/1/0"
|
||||
xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">
|
||||
<trk>
|
||||
<name>ACTIVE LOG #74</name>
|
||||
<number>82</number>
|
||||
<trkseg>
|
||||
<trkpt lat="37.378388923" lon="-122.063654875">
|
||||
<ele>35.706055</ele>
|
||||
<time>2007-11-23T23:49:43Z</time>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
@ -1 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="Link2GPS - 2.0.2 - http://www.hiketech.com" xsi:schemaLocation="ttp://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"><metadata><name><![CDATA[routes.gpx]]></name><time>2006-01-02T08:55:34Z</time><bounds min_lat="39.989739" min_lon="-105.295285" max_lat="39.999840" max_lon="-105.214696"/></metadata><extensions/><rte><name><![CDATA[GRG-CA-TO]]></name><rtept lat="39.997298" lon="-105.292674"><name><![CDATA[GRG-CA]]></name><sym>Waypoint</sym><ele>1766.535</ele></rtept><rtept lat="39.995700" lon="-105.292805"><name><![CDATA[AMPTHT]]></name><sym>Waypoint</sym><ele>1854.735</ele></rtept><rtept lat="39.989739" lon="-105.295285"><name><![CDATA[TO]]></name><sym>Waypoint</sym><ele>2163.556</ele></rtept></rte><rte><name><![CDATA[SBDR-SBDR]]></name><rtept lat="39.999840" lon="-105.214696"><name><![CDATA[SBDR]]></name><sym>Waypoint</sym><ele>1612.965</ele></rtept></rte></gpx>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="Link2GPS - 2.0.2 - http://www.hiketech.com" xsi:schemaLocation="ttp://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"><metadata><name><![CDATA[routes.gpx]]></name><time>2006-01-02T08:55:34Z</time><bounds min_lat="39.989739" min_lon="-105.295285" max_lat="39.999840" max_lon="-105.214696"/></metadata><extensions/>
|
||||
<rte><name><![CDATA[GRG-CA-TO]]></name>
|
||||
<rtept lat="39.997298" lon="-105.292674"><name><![CDATA[GRG-CA]]></name><sym>Waypoint</sym><ele>1766.535</ele></rtept>
|
||||
<rtept lat="39.995700" lon="-105.292805"><name><![CDATA[AMPTHT]]></name><sym>Waypoint</sym><ele>1854.735</ele></rtept>
|
||||
<rtept lat="39.989739" lon="-105.295285"><name><![CDATA[TO]]></name><sym>Waypoint</sym><ele>2163.556</ele></rtept></rte>
|
||||
<rte><name><![CDATA[SBDR-SBDR]]></name>
|
||||
<rtept lat="39.999840" lon="-105.214696"><name><![CDATA[SBDR]]></name><sym>Waypoint</sym><ele>1612.965</ele></rtept></rte>
|
||||
</gpx>
|
@ -1 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="Link2GPS - 2.0.2 - http://www.hiketech.com" xsi:schemaLocation="ttp://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"><metadata><name><![CDATA[waypoints.gpx]]></name><time>2006-01-02T08:55:21Z</time><bounds min_lat="25.061783" min_lon="-111.946110" max_lat="50.982883" max_lon="121.640267"/></metadata><extensions/><wpt lat="40.035557" lon="-105.248268"><name><![CDATA[001]]></name><sym>Waypoint</sym><ele>1639.161</ele></wpt><wpt lat="39.993070" lon="-105.296588"><name><![CDATA[002]]></name><sym>Waypoint</sym><ele>1955.192</ele></wpt><wpt lat="39.990151" lon="-105.295680"><name><![CDATA[003]]></name><sym>Waypoint</sym><ele>2129.91</ele></wpt><wpt lat="39.990157" lon="-105.295686"><name><![CDATA[004]]></name><sym>Waypoint</sym><ele>2136.399</ele></wpt><wpt lat="39.990134" lon="-105.295251"><name><![CDATA[005]]></name><sym>Waypoint</sym><ele>2174.612</ele></wpt><wpt lat="39.990116" lon="-105.295147"><name><![CDATA[006]]></name><sym>Waypoint</sym><ele>2156.106</ele></wpt><wpt lat="39.990099" lon="-105.295207"><name><![CDATA[007]]></name><sym>Waypoint</sym><ele>2155.145</ele></wpt><wpt lat="39.990067" lon="-105.295185"><name><![CDATA[008]]></name><sym>Waypoint</sym><ele>2152.021</ele></wpt><wpt lat="39.995700" lon="-105.292805"><name><![CDATA[AMPTHT]]></name><sym>Waypoint</sym><ele>1854.735</ele></wpt><wpt lat="38.855550" lon="-94.799017"><name><![CDATA[GARMIN]]></name><sym>Waypoint</sym><ele>325.0491</ele></wpt><wpt lat="39.997298" lon="-105.292674"><name><![CDATA[GRG-CA]]></name><sym>Waypoint</sym><ele>1766.535</ele></wpt><wpt lat="50.982883" lon="-1.463900"><name><![CDATA[GRMEUR]]></name><sym>Waypoint</sym><ele>35.93469</ele></wpt><wpt lat="33.330190" lon="-111.946110"><name><![CDATA[GRMPHX]]></name><sym>Waypoint</sym><ele>361.0981</ele></wpt><wpt lat="25.061783" lon="121.640267"><name><![CDATA[GRMTWN]]></name><sym>Waypoint</sym><ele>38.09766</ele></wpt><wpt lat="39.999840" lon="-105.214696"><name><![CDATA[SBDR]]></name><sym>Waypoint</sym><ele>1612.965</ele></wpt><wpt lat="39.989739" lon="-105.295285"><name><![CDATA[TO]]></name><sym>Waypoint</sym><ele>2163.556</ele></wpt><wpt lat="40.035301" lon="-105.254443"><name><![CDATA[VICS]]></name><sym>Waypoint</sym><ele>1535.34</ele></wpt></gpx>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="Link2GPS - 2.0.2 - http://www.hiketech.com" xsi:schemaLocation="ttp://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"><metadata><name><![CDATA[waypoints.gpx]]></name><time>2006-01-02T08:55:21Z</time><bounds min_lat="25.061783" min_lon="-111.946110" max_lat="50.982883" max_lon="121.640267"/></metadata><extensions/>
|
||||
<wpt lat="40.035557" lon="-105.248268"><name><![CDATA[001]]></name><sym>Waypoint</sym><ele>1639.161</ele><cmt><![CDATA[001]]></cmt><desc><![CDATA[Just some waypoint...]]></desc></wpt>
|
||||
<wpt lat="39.993070" lon="-105.296588"><name><![CDATA[002]]></name><sym>Waypoint</sym><ele>1955.192</ele></wpt>
|
||||
<wpt lat="39.990151" lon="-105.295680"><name><![CDATA[003]]></name><sym>Waypoint</sym><ele>2129.91</ele></wpt>
|
||||
<wpt lat="39.990157" lon="-105.295686"><name><![CDATA[004]]></name><sym>Waypoint</sym><ele>2136.399</ele></wpt>
|
||||
<wpt lat="39.990134" lon="-105.295251"><name><![CDATA[005]]></name><sym>Waypoint</sym><ele>2174.612</ele></wpt>
|
||||
<wpt lat="39.990116" lon="-105.295147"><name><![CDATA[006]]></name><sym>Waypoint</sym><ele>2156.106</ele></wpt>
|
||||
<wpt lat="39.990099" lon="-105.295207"><name><![CDATA[007]]></name><sym>Waypoint</sym><ele>2155.145</ele></wpt>
|
||||
<wpt lat="39.990067" lon="-105.295185"><name><![CDATA[008]]></name><sym>Waypoint</sym><ele>2152.021</ele></wpt>
|
||||
<wpt lat="39.995700" lon="-105.292805"><name><![CDATA[AMPTHT]]></name><sym>Waypoint</sym><ele>1854.735</ele></wpt>
|
||||
<wpt lat="38.855550" lon="-94.799017"><name><![CDATA[GARMIN]]></name><sym>Waypoint</sym><ele>325.0491</ele></wpt>
|
||||
<wpt lat="39.997298" lon="-105.292674"><name><![CDATA[GRG-CA]]></name><sym>Waypoint</sym><ele>1766.535</ele></wpt>
|
||||
<wpt lat="50.982883" lon="-1.463900"><name><![CDATA[GRMEUR]]></name><sym>Waypoint</sym><ele>35.93469</ele></wpt>
|
||||
<wpt lat="33.330190" lon="-111.946110"><name><![CDATA[GRMPHX]]></name><sym>Waypoint</sym><ele>361.0981</ele></wpt>
|
||||
<wpt lat="25.061783" lon="121.640267"><name><![CDATA[GRMTWN]]></name><sym>Waypoint</sym><ele>38.09766</ele></wpt>
|
||||
<wpt lat="39.999840" lon="-105.214696"><name><![CDATA[SBDR]]></name><sym>Waypoint</sym><ele>1612.965</ele></wpt>
|
||||
<wpt lat="39.989739" lon="-105.295285"><name><![CDATA[TO]]></name><sym>Waypoint</sym><ele>2163.556</ele></wpt>
|
||||
<wpt lat="40.035301" lon="-105.254443"><name><![CDATA[VICS]]></name><sym>Waypoint</sym><ele>1535.34</ele></wpt>
|
||||
</gpx>
|
29
tests/gpx_files/with_or_without_elev.gpx
Normal file
29
tests/gpx_files/with_or_without_elev.gpx
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<gpx xmlns="http://www.topografix.com/GPX/1/1" creator="MapSource 6.5" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||
|
||||
<metadata>
|
||||
<link href="http://www.garmin.com">
|
||||
<text>Garmin International</text>
|
||||
</link>
|
||||
<time>2007-04-16T18:11:47Z</time>
|
||||
<bounds maxlat="40.207429" maxlon="116.670578" minlat="39.876895" minlon="101.636217"/>
|
||||
</metadata>
|
||||
|
||||
<trk>
|
||||
<name>ACTIVE LOG</name>
|
||||
<trkseg>
|
||||
<trkpt lat="40.079434" lon="116.295948">
|
||||
<ele>50.606567</ele>
|
||||
<time>2007-03-25T05:17:34Z</time>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
|
||||
<trk>
|
||||
<name>LINE-13</name>
|
||||
<trkseg>
|
||||
<trkpt lat="39.949744" lon="116.427398"/>
|
||||
</trkseg>
|
||||
</trk>
|
||||
|
||||
</gpx>
|
@ -1,7 +1,7 @@
|
||||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/gpx'
|
||||
require 'gpx'
|
||||
|
||||
class TestMagellanTrackLog < Test::Unit::TestCase
|
||||
class MagellanTest < 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")
|
||||
|
||||
|
115
tests/output_test.rb
Normal file
115
tests/output_test.rb
Normal file
@ -0,0 +1,115 @@
|
||||
require 'test/unit'
|
||||
require 'fileutils'
|
||||
require 'gpx'
|
||||
|
||||
class OutputTest < Test::Unit::TestCase
|
||||
|
||||
include GPX
|
||||
|
||||
def setup
|
||||
FileUtils.mkdir_p(File.join(File.dirname(__FILE__), "output"))
|
||||
end
|
||||
|
||||
def test_new_gpx_file_from_scratch
|
||||
gpx_file = GPXFile.new
|
||||
|
||||
track = Track.new(:name => "My First Track")
|
||||
segment = Segment.new
|
||||
|
||||
track_point_data = [
|
||||
{:lat => 40.036926, :lon =>-105.253487, :time => Time.parse('2005-12-31T22:01:24Z'), :elevation => 1737.24},
|
||||
{:lat => 40.036604, :lon =>-105.253487, :time => Time.parse("2005-12-31T22:02:01Z"), :elevation => 1738.682},
|
||||
{:lat => 40.036347, :lon =>-105.253830, :time => Time.parse("2005-12-31T22:02:08Z"), :elevation => 1738.682},
|
||||
{:lat => 40.035574, :lon =>-105.254045, :time => Time.parse("2005-12-31T22:02:20Z"), :elevation => 1737.24},
|
||||
{:lat => 40.035467, :lon =>-105.254366, :time => Time.parse("2005-12-31T22:02:29Z"), :elevation => 1735.798},
|
||||
{:lat => 40.035317, :lon =>-105.254388, :time => Time.parse("2005-12-31T22:02:33Z"), :elevation => 1735.798},
|
||||
{:lat => 40.035274, :lon =>-105.254431, :time => Time.parse("2005-12-31T22:02:49Z"), :elevation => 1736.278},
|
||||
{:lat => 40.035274, :lon =>-105.254431, :time => Time.parse("2005-12-31T22:02:54Z"), :elevation => 1739.643},
|
||||
{:lat => 40.035317, :lon =>-105.254431, :time => Time.parse("2005-12-31T22:05:08Z"), :elevation => 1732.433},
|
||||
{:lat => 40.035317, :lon =>-105.254431, :time => Time.parse("2005-12-31T22:05:09Z"), :elevation => 1726.665}]
|
||||
|
||||
track_point_data.each do |trk_pt_hash|
|
||||
segment.points << TrackPoint.new(trk_pt_hash)
|
||||
end
|
||||
|
||||
track.segments << segment
|
||||
gpx_file.tracks << track
|
||||
|
||||
waypoint_data = [
|
||||
{:lat => 39.997298, :lon => -105.292674, :name => 'GRG-CA', :sym => 'Waypoint', :ele => '1766.535'},
|
||||
{:lat => 33.330190, :lon => -111.946110, :name => 'GRMPHX', :sym => 'Waypoint', :ele => '361.0981',
|
||||
:cmt => "Hey here's a comment.", :desc => "Somewhere in my backyard.", :fix => '3d', :sat => "8", :hdop => "50.5", :vdop => "6.8", :pdop => "7.6"},
|
||||
{:lat => 25.061783, :lon => 121.640267, :name => 'GRMTWN', :sym => 'Waypoint', :ele => '38.09766'},
|
||||
{:lat => 39.999840, :lon => -105.214696, :name => 'SBDR', :sym => 'Waypoint', :ele => '1612.965'},
|
||||
{:lat => 39.989739, :lon => -105.295285, :name => 'TO', :sym => 'Waypoint', :ele => '2163.556'},
|
||||
{:lat => 40.035301, :lon => -105.254443, :name => 'VICS', :sym => 'Waypoint', :ele => '1535.34'}
|
||||
]
|
||||
|
||||
waypoint_data.each do |wpt_hash|
|
||||
gpx_file.waypoints << Waypoint.new(wpt_hash)
|
||||
end
|
||||
|
||||
route_point_data = [
|
||||
{:lat => 40.035467, :lon =>-105.254366, :time => Time.parse("2005-12-31T22:02:29Z"), :elevation => 1735.798},
|
||||
{:lat => 40.035317, :lon =>-105.254388, :time => Time.parse("2005-12-31T22:02:33Z"), :elevation => 1735.798},
|
||||
{:lat => 40.035274, :lon =>-105.254431, :time => Time.parse("2005-12-31T22:02:49Z"), :elevation => 1736.278} ]
|
||||
|
||||
route = Route.new()
|
||||
route_point_data.each do |rte_pt_hash|
|
||||
route.points << Point.new(rte_pt_hash)
|
||||
end
|
||||
|
||||
gpx_file.routes << route
|
||||
|
||||
gpx_file.write(output_file(name_of_test))
|
||||
|
||||
|
||||
written_gpx_file = GPXFile.new(:gpx_file => output_file(name_of_test))
|
||||
|
||||
assert_equal(File.basename(output_file(name_of_test)), written_gpx_file.name)
|
||||
assert_equal(1, written_gpx_file.tracks.size)
|
||||
assert_equal(1, written_gpx_file.tracks[0].segments.size)
|
||||
assert_equal(track_point_data.size, written_gpx_file.tracks[0].segments[0].points.size)
|
||||
assert_equal(track_point_data.size, written_gpx_file.tracks[0].points.size)
|
||||
|
||||
# Make sure each point in the segment has the attributes it was initialized with
|
||||
written_segment = written_gpx_file.tracks[0].segments[0]
|
||||
track_point_data.each_with_index do |trk_pt_hash, index|
|
||||
trk_pt_hash.each do |key, value|
|
||||
assert_equal(value, written_segment.points[index].send(key))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Make sure the one route has the attributes we initialized it with
|
||||
assert_equal(1, written_gpx_file.routes.size)
|
||||
written_route = written_gpx_file.routes[0]
|
||||
assert_equal(route_point_data.size, written_route.points.size)
|
||||
route_point_data.each_with_index do |rte_pt_hash, index|
|
||||
rte_pt_hash.each do |key, value|
|
||||
assert_equal(value, written_route.points[index].send(key))
|
||||
end
|
||||
end
|
||||
|
||||
# Make sure the waypoints have all of the attributes we initialized them with
|
||||
written_waypoints = written_gpx_file.waypoints
|
||||
assert_equal(waypoint_data.size, written_waypoints.size)
|
||||
waypoint_data.each_with_index do |wpt_hash, index|
|
||||
wpt_hash.each do |key, value|
|
||||
assert_equal(value, written_waypoints[index].send(key.to_s), key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def name_of_test
|
||||
caller[0] =~ /`test_([^']*)'/ and $1
|
||||
end
|
||||
|
||||
def output_file(test_name)
|
||||
File.join(File.dirname(__FILE__), "output/#{test_name}.gpx")
|
||||
end
|
||||
|
||||
THE_WORKS = "<?xml version=\"1.0\"?>\n<gpx xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.topografix.com/GPX/1/1\" version=\"1.1\" creator=\"GPX RubyGem #{GPX::VERSION} Copyright 2006-2009 Doug Fales -- http://gpx.rubyforge.org/\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n <metadata>\n <name>new_gpx_file_from_scratch.gpx</name>\n <time>%s</time>\n <bounds minlat=\"90.0\" minlon=\"180.0\" maxlat=\"-90.0\" maxlon=\"-180.0\"/>\n </metadata>\n <trk>\n <name/>\n <trkseg>\n <trkpt lat=\"40.036926\" lon=\"-105.253487\">\n <time>2005-12-31T22:01:24Z</time>\n <ele>1737.24</ele>\n </trkpt>\n <trkpt lat=\"40.036604\" lon=\"-105.253487\">\n <time>2005-12-31T22:02:01Z</time>\n <ele>1738.682</ele>\n </trkpt>\n <trkpt lat=\"40.036347\" lon=\"-105.25383\">\n <time>2005-12-31T22:02:08Z</time>\n <ele>1738.682</ele>\n </trkpt>\n <trkpt lat=\"40.035574\" lon=\"-105.254045\">\n <time>2005-12-31T22:02:20Z</time>\n <ele>1737.24</ele>\n </trkpt>\n <trkpt lat=\"40.035467\" lon=\"-105.254366\">\n <time>2005-12-31T22:02:29Z</time>\n <ele>1735.798</ele>\n </trkpt>\n <trkpt lat=\"40.035317\" lon=\"-105.254388\">\n <time>2005-12-31T22:02:33Z</time>\n <ele>1735.798</ele>\n </trkpt>\n <trkpt lat=\"40.035274\" lon=\"-105.254431\">\n <time>2005-12-31T22:02:49Z</time>\n <ele>1736.278</ele>\n </trkpt>\n <trkpt lat=\"40.035274\" lon=\"-105.254431\">\n <time>2005-12-31T22:02:54Z</time>\n <ele>1739.643</ele>\n </trkpt>\n <trkpt lat=\"40.035317\" lon=\"-105.254431\">\n <time>2005-12-31T22:05:08Z</time>\n <ele>1732.433</ele>\n </trkpt>\n <trkpt lat=\"40.035317\" lon=\"-105.254431\">\n <time>2005-12-31T22:05:09Z</time>\n <ele>1726.665</ele>\n </trkpt>\n </trkseg>\n </trk>\n <wpt lat=\"39.997298\" lon=\"-105.292674\">\n <name>GRG-CA</name>\n <sym>Waypoint</sym>\n <ele>1766.535</ele>\n </wpt>\n <wpt lat=\"33.33019\" lon=\"-111.94611\">\n <name>GRMPHX</name>\n <cmt>Hey here's a comment.</cmt>\n <desc>Somewhere in my backyard.</desc>\n <sym>Waypoint</sym>\n <fix>3d</fix>\n <sat>8</sat>\n <hdop>50.5</hdop>\n <vdop>6.8</vdop>\n <pdop>7.6</pdop>\n <ele>361.0981</ele>\n </wpt>\n <wpt lat=\"25.061783\" lon=\"121.640267\">\n <name>GRMTWN</name>\n <sym>Waypoint</sym>\n <ele>38.09766</ele>\n </wpt>\n <wpt lat=\"39.99984\" lon=\"-105.214696\">\n <name>SBDR</name>\n <sym>Waypoint</sym>\n <ele>1612.965</ele>\n </wpt>\n <wpt lat=\"39.989739\" lon=\"-105.295285\">\n <name>TO</name>\n <sym>Waypoint</sym>\n <ele>2163.556</ele>\n </wpt>\n <wpt lat=\"40.035301\" lon=\"-105.254443\">\n <name>VICS</name>\n <sym>Waypoint</sym>\n <ele>1535.34</ele>\n </wpt>\n <rte>\n <name/>\n <rtept lat=\"40.035467\" lon=\"-105.254366\">\n <time>2005-12-31T22:02:29Z</time>\n <ele>1735.798</ele>\n </rtept>\n <rtept lat=\"40.035317\" lon=\"-105.254388\">\n <time>2005-12-31T22:02:33Z</time>\n <ele>1735.798</ele>\n </rtept>\n <rtept lat=\"40.035274\" lon=\"-105.254431\">\n <time>2005-12-31T22:02:49Z</time>\n <ele>1736.278</ele>\n </rtept>\n </rte>\n</gpx>\n"
|
||||
|
||||
end
|
63
tests/route_test.rb
Normal file
63
tests/route_test.rb
Normal file
@ -0,0 +1,63 @@
|
||||
require 'test/unit'
|
||||
require 'gpx'
|
||||
|
||||
class RouteTest < Test::Unit::TestCase
|
||||
|
||||
def test_read_routes
|
||||
gpx = GPX::GPXFile.new(:gpx_file => File.join(File.dirname(__FILE__), "gpx_files/routes.gpx"))
|
||||
assert_equal(2, gpx.routes.size)
|
||||
first_route = gpx.routes.first
|
||||
assert_equal(3, first_route.points.size)
|
||||
assert_equal('GRG-CA-TO', first_route.name)
|
||||
|
||||
|
||||
# Route 1, First Point
|
||||
# <rtept lat="39.997298" lon="-105.292674">
|
||||
# <name><![CDATA[GRG-CA]]></name>
|
||||
# <sym>Waypoint</sym>
|
||||
# <ele>1766.535</ele>
|
||||
# </rtept>
|
||||
assert_equal(39.997298, first_route.points[0].lat)
|
||||
assert_equal(-105.292674, first_route.points[0].lon)
|
||||
assert_equal(1766.535, first_route.points[0].elevation)
|
||||
|
||||
|
||||
# Route 1, Second Point
|
||||
# <rtept lat="39.995700" lon="-105.292805">
|
||||
# <name><![CDATA[AMPTHT]]></name>
|
||||
# <sym>Waypoint</sym>
|
||||
# <ele>1854.735</ele>
|
||||
# </rtept>
|
||||
assert_equal(39.995700, first_route.points[1].lat)
|
||||
assert_equal(-105.292805, first_route.points[1].lon)
|
||||
assert_equal(1854.735, first_route.points[1].elevation)
|
||||
|
||||
# Route 1, Third Point
|
||||
# <rtept lat="39.989739" lon="-105.295285">
|
||||
# <name><![CDATA[TO]]></name>
|
||||
# <sym>Waypoint</sym>
|
||||
# <ele>2163.556</ele>
|
||||
# </rtept>
|
||||
assert_equal(39.989739, first_route.points[2].lat)
|
||||
assert_equal(-105.295285, first_route.points[2].lon)
|
||||
assert_equal(2163.556, first_route.points[2].elevation)
|
||||
|
||||
|
||||
second_route = gpx.routes[1]
|
||||
assert_equal(1, second_route.points.size)
|
||||
assert_equal('SBDR-SBDR', second_route.name)
|
||||
|
||||
# Route 2, Only Point
|
||||
# <rtept lat="39.999840" lon="-105.214696">
|
||||
# <name><![CDATA[SBDR]]></name>
|
||||
# <sym>Waypoint</sym>
|
||||
# <ele>1612.965</ele>
|
||||
# </rtept>
|
||||
assert_equal(39.999840, second_route.points[0].lat)
|
||||
assert_equal(-105.214696, second_route.points[0].lon)
|
||||
assert_equal(1612.965, second_route.points[0].elevation)
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
@ -1,7 +1,8 @@
|
||||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/gpx'
|
||||
require 'yaml'
|
||||
require 'gpx'
|
||||
|
||||
class TestSegment < Test::Unit::TestCase
|
||||
class SegmentTest < Test::Unit::TestCase
|
||||
ONE_SEGMENT = File.join(File.dirname(__FILE__), "gpx_files/one_segment.gpx")
|
||||
|
||||
def setup
|
||||
@ -11,11 +12,11 @@ class TestSegment < Test::Unit::TestCase
|
||||
|
||||
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(1144433525, @segment.earliest_point.time.to_i)
|
||||
assert_equal(1144437991, @segment.latest_point.time.to_i)
|
||||
assert_equal(1334.447, @segment.lowest_point.elevation)
|
||||
assert_equal(1480.087, @segment.highest_point.elevation)
|
||||
assert_equal("6.98803359528853", @segment.distance.to_s)
|
||||
assert_in_delta(6.98803359528853, @segment.distance, 0.001)
|
||||
end
|
||||
|
||||
def test_segment_crop
|
||||
@ -25,10 +26,10 @@ class TestSegment < Test::Unit::TestCase
|
||||
: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(106, @segment.points.size)
|
||||
assert_in_delta(4.11422061733046, @segment.distance, 0.001)
|
||||
assert_equal(1144435041, @segment.earliest_point.time.to_i)
|
||||
assert_equal(1144437752, @segment.latest_point.time.to_i)
|
||||
assert_equal(1407.027, @segment.lowest_point.elevation)
|
||||
assert_equal(1480.087, @segment.highest_point.elevation)
|
||||
assert_equal(39.173834, @segment.bounds.min_lat)
|
||||
@ -43,10 +44,10 @@ class TestSegment < Test::Unit::TestCase
|
||||
: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(83, @segment.points.size)
|
||||
assert_in_delta(3.35967118153605, @segment.distance, 0.001)
|
||||
assert_equal(1144433525, @segment.earliest_point.time.to_i)
|
||||
assert_equal(1144437991, @segment.latest_point.time.to_i)
|
||||
assert_equal(1334.447, @segment.lowest_point.elevation)
|
||||
assert_equal(1428.176, @segment.highest_point.elevation)
|
||||
assert_equal(39.180572, @segment.bounds.min_lat)
|
||||
|
@ -1,7 +1,7 @@
|
||||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/gpx'
|
||||
require 'gpx'
|
||||
|
||||
class TestTrackFile < Test::Unit::TestCase
|
||||
class TrackFileTest < 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")
|
||||
|
||||
@ -69,7 +69,7 @@ class TestTrackFile < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_write
|
||||
@other_track_file.write("myoutput.gpx")
|
||||
@other_track_file.write("tests/output/myoutput.gpx")
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/gpx'
|
||||
require 'gpx'
|
||||
|
||||
class TestTrack < Test::Unit::TestCase
|
||||
class TrackTest < Test::Unit::TestCase
|
||||
ONE_TRACK = File.join(File.dirname(__FILE__), "gpx_files/one_track.gpx")
|
||||
|
||||
def setup
|
||||
@ -11,38 +11,38 @@ class TestTrack < Test::Unit::TestCase
|
||||
|
||||
def test_track_read
|
||||
assert_equal("ACTIVE LOG", @track.name)
|
||||
assert_equal( 364, @track.points.size)
|
||||
assert_equal( 182, @track.points.size)
|
||||
assert_equal(8, @track.segments.size)
|
||||
assert_equal("3.07249668492626", @track.distance.to_s)
|
||||
assert_in_delta(3.07249668492626, @track.distance, 0.001)
|
||||
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(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(
|
||||
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( 111, @track.points.size)
|
||||
assert_equal(4, @track.segments.size)
|
||||
assert_equal("1.62136024923607", @track.distance.to_s)
|
||||
assert_in_delta(1.62136024923607, @track.distance, 0.001)
|
||||
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(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(
|
||||
area = GPX::Bounds.new(
|
||||
:min_lat => 38.710000,
|
||||
:min_lon => -109.600000,
|
||||
:max_lat => 38.791759,
|
||||
@ -51,14 +51,14 @@ class TestTrack < Test::Unit::TestCase
|
||||
|
||||
#puts @track
|
||||
#assert_equal("ACTIVE LOG", @track.name)
|
||||
#assert_equal( 111, @track.points.size)
|
||||
#assert_equal( 111, @track.points.size)
|
||||
#assert_equal(4, @track.segments.size)
|
||||
#assert_equal("1.62136024923607", @track.distance.to_s)
|
||||
#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(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
|
||||
|
||||
|
44
tests/waypoint_test.rb
Executable file
44
tests/waypoint_test.rb
Executable file
@ -0,0 +1,44 @@
|
||||
require 'test/unit'
|
||||
require 'gpx'
|
||||
|
||||
class WaypointTest < Test::Unit::TestCase
|
||||
|
||||
def test_read_waypoints
|
||||
|
||||
gpx = GPX::GPXFile.new(:gpx_file => File.join(File.dirname(__FILE__), "gpx_files/waypoints.gpx"))
|
||||
assert_equal(17, gpx.waypoints.size)
|
||||
|
||||
# First Waypoint
|
||||
# <wpt lat="40.035557" lon="-105.248268">
|
||||
# <name><![CDATA[001]]></name>
|
||||
# <sym>Waypoint</sym>
|
||||
# <ele>1639.161</ele>
|
||||
# <cmt><![CDATA[001]]></cmt>
|
||||
# <desc><![CDATA[Just some waypoint...]]></desc>
|
||||
# </wpt>
|
||||
|
||||
first_wpt = gpx.waypoints[0]
|
||||
assert_equal(40.035557, first_wpt.lat)
|
||||
assert_equal(-105.248268, first_wpt.lon)
|
||||
assert_equal('001', first_wpt.name)
|
||||
assert_equal('001', first_wpt.cmt)
|
||||
assert_equal('Just some waypoint...', first_wpt.desc)
|
||||
assert_equal('Waypoint', first_wpt.sym)
|
||||
assert_equal(1639.161, first_wpt.elevation)
|
||||
|
||||
# Second Waypoint
|
||||
# <wpt lat="39.993070" lon="-105.296588">
|
||||
# <name><![CDATA[002]]></name>
|
||||
# <sym>Waypoint</sym>
|
||||
# <ele>1955.192</ele>
|
||||
# </wpt>
|
||||
second_wpt = gpx.waypoints[1]
|
||||
assert_equal(39.993070, second_wpt.lat)
|
||||
assert_equal(-105.296588, second_wpt.lon)
|
||||
assert_equal('002', second_wpt.name)
|
||||
assert_equal('Waypoint', second_wpt.sym)
|
||||
assert_equal(1955.192, second_wpt.elevation)
|
||||
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user