Skip to content

Commit

Permalink
Initial import of gpx gem.
Browse files Browse the repository at this point in the history
  • Loading branch information
dougfales committed Oct 14, 2006
0 parents commit c2fb347
Show file tree
Hide file tree
Showing 24 changed files with 9,659 additions and 0 deletions.
43 changes: 43 additions & 0 deletions README
@@ -0,0 +1,43 @@
= GPX Gem
Copyright (C) 2006 Doug Fales
Doug Fales mailto:doug.fales@gmail.com

== What It Does
This library reads GPX files and provides an API for reading and manipulating
the data as objects. For more info on the GPX format, see
http://www.topografix.com/gpx.asp.

In addition to parsing GPX files, this library is capable of converting
Magellan NMEA files to GPX, and writing new GPX files. It can crop and delete
rectangular areas within a file, and it also calculates some meta-data about
the tracks and points in a file (such as distance, duration, average speed,
etc).

== Examples
Reading a GPX file, and cropping its contents to a given area:
gpx = GPX::GPXFile.new(:gpx_file => filename) # Read GPX file
bounds = GPX::Bounds.new(params) # Create a rectangular area to crop
gpx.crop(bounds) # Crop it
gpx.write(filename) # Save it

Converting a Magellan track log to GPX:
if GPX::MagellanTrackLog::is_magellan_file?(filename)
GPX::MagellanTrackLog::convert_to_gpx(filename, "#{filename}.gpx")
end


== Notes
This library was written to bridge the gap between my Garmin Geko
and my website, WalkingBoss.org. For that reason, it has always been more of a
work-in-progress than an attempt at full GPX compliance. The track side of the
library has seen much more use than the route/waypoint side, so if you're doing
something with routes or waypoints, you may need to tweak some things.

Since this code uses REXML to read an entire GPX file into memory, it is not
the fastest possible solution for working with GPX data, especially if you are
working with tracks from several days or weeks.

Finally, it should be noted that none of the distance/speed calculation or
crop/delete code has been tested under International Date Line-crossing
conditions. That particular part of the code will likely be unreliable if
you're zig-zagging across 180 degrees longitude routinely.
91 changes: 91 additions & 0 deletions Rakefile
@@ -0,0 +1,91 @@
require 'rubygems'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/gempackagetask'
require File.dirname(__FILE__) + '/lib/gpx'

PKG_VERSION = GPX::VERSION
PKG_NAME = "gpx"
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
RUBY_FORGE_PROJECT = "gpx"
RUBY_FORGE_USER = ENV['RUBY_FORGE_USER'] || "dougfales"
RELEASE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"

PKG_FILES = FileList[
"lib/**/*", "bin/*", "tests/**/*", "[A-Z]*", "Rakefile", "doc/**/*"
]

desc "Default Task"
task :default => [ :test ]

# Run the unit tests
desc "Run all unit tests"
Rake::TestTask.new("test") { |t|
t.libs << "lib"
t.pattern = 'tests/*_test.rb'
t.verbose = true
}

# Make a console, useful when working on tests
desc "Generate a test console"
task :console do
verbose( false ) { sh "irb -I lib/ -r 'gpx'" }
end

# Genereate the RDoc documentation
desc "Create documentation"
Rake::RDocTask.new("doc") { |rdoc|
rdoc.title = "Ruby GPX API"
rdoc.rdoc_dir = 'html'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
}

# Genereate the package
spec = Gem::Specification.new do |s|

#### 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
37 changes: 37 additions & 0 deletions lib/gpx.rb
@@ -0,0 +1,37 @@
#--
# Copyright (c) 2006 Doug Fales
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
$:.unshift(File.dirname(__FILE__))
require 'rexml/document'
require 'date'
require 'time'
require 'csv'
require 'gpx/gpx'
require 'gpx/gpx_file'
require 'gpx/bounds'
require 'gpx/track'
require 'gpx/route'
require 'gpx/segment'
require 'gpx/point'
require 'gpx/trackpoint'
require 'gpx/waypoint'
require 'gpx/magellan_track_log'
83 changes: 83 additions & 0 deletions lib/gpx/bounds.rb
@@ -0,0 +1,83 @@
#--
# Copyright (c) 2006 Doug Fales
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
module GPX
class Bounds < Base
attr_accessor :min_lat, :max_lat, :max_lon, :min_lon, :center_lat, :center_lon

# Creates a new bounds object with the passed-in min and max longitudes
# and latitudes.
def initialize(opts = { :min_lat => 90.0, :max_lat => -90.0, :min_lon => 180.0, :max_lon => -180.0})
@min_lat, @max_lat = opts[:min_lat].to_f, opts[:max_lat].to_f
@min_lon, @max_lon = opts[:min_lon].to_f, opts[:max_lon].to_f
end

# Returns the middle latitude.
def center_lat
distance = (max_lat - min_lat)/2.0
(min_lat + distance)
end

# Returns the middle longitude.
def center_lon
distance = (max_lon - min_lon)/2.0
(min_lon + distance)
end

def to_xml
bnd = REXML::Element.new('bounds')
bnd.attributes['min_lat'] = min_lat
bnd.attributes['min_lon'] = min_lon
bnd.attributes['max_lat'] = max_lat
bnd.attributes['max_lon'] = max_lon
bnd
end

# Returns true if the pt is within these bounds.
def contains?(pt)
(pt.lat >= min_lat and pt.lat <= max_lat and pt.lon >= min_lon and pt.lon <= max_lon)
end

# Adds an item to itself, expanding its min/max lat/lon as needed to
# contain the given item. The item can be either another instance of
# Bounds or a Point.
def add(item)
if(item.respond_to?(:lat) and item.respond_to?(:lon))
@min_lat = item.lat if item.lat < @min_lat
@min_lon = item.lon if item.lon < @min_lon
@max_lat = item.lat if item.lat > @max_lat
@max_lon = item.lon if item.lon > @max_lon
else
@min_lat = item.min_lat if item.min_lat < @min_lat
@min_lon = item.min_lon if item.min_lon < @min_lon
@max_lat = item.max_lat if item.max_lat > @max_lat
@max_lon = item.max_lon if item.max_lon > @max_lon
end
end

# Returns the min_lat, min_lon, max_lat, and max_lon in a labeled string.
def to_s
"min_lat: #{min_lat} min_lon: #{min_lon} max_lat: #{max_lat} max_lon: #{max_lon}"
end

end
end
53 changes: 53 additions & 0 deletions lib/gpx/gpx.rb
@@ -0,0 +1,53 @@
#--
# Copyright (c) 2006 Doug Fales
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
module GPX
VERSION = "0.1"

# A common base class which provides a useful initializer method to many
# class in the GPX library.
class Base
include REXML

# This initializer can take a REXML::Element and scrape out any text
# elements with the names given in the "text_elements" array. Each
# element found underneath "parent" with a name in "text_elements" causes
# an attribute to be initialized on the instance. This means you don't
# have to pick out individual text elements in each initializer of each
# class (Route, TrackPoint, Track, etc). Just pass an array of possible
# attributes to this method.
def instantiate_with_text_elements(parent, text_elements)
text_elements.each do |el|
unless parent.elements[el].nil?
val = parent.elements[el].text
code = <<-code
attr_accessor #{ el }
#{el}=#{val}
code
class_eval code
end
end

end

end
end

0 comments on commit c2fb347

Please sign in to comment.