gpx 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +43 -0
- data/Rakefile +81 -0
- data/lib/gpx.rb +37 -0
- data/lib/gpx/bounds.rb +83 -0
- data/lib/gpx/gpx.rb +53 -0
- data/lib/gpx/gpx_file.rb +222 -0
- data/lib/gpx/magellan_track_log.rb +134 -0
- data/lib/gpx/point.rb +103 -0
- data/lib/gpx/route.rb +65 -0
- data/lib/gpx/segment.rb +217 -0
- data/lib/gpx/track.rb +149 -0
- data/lib/gpx/trackpoint.rb +35 -0
- data/lib/gpx/waypoint.rb +73 -0
- data/tests/gpx_files/arches.gpx +1 -0
- data/tests/gpx_files/magellan_track.log +306 -0
- data/tests/gpx_files/one_segment.gpx +770 -0
- data/tests/gpx_files/one_track.gpx +756 -0
- data/tests/gpx_files/routes.gpx +1 -0
- data/tests/gpx_files/tracks.gpx +6304 -0
- data/tests/gpx_files/waypoints.gpx +1 -0
- data/tests/magellan_test.rb +18 -0
- data/tests/segment_test.rb +57 -0
- data/tests/track_file_test.rb +75 -0
- data/tests/track_test.rb +65 -0
- metadata +70 -0
data/README
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
= GPX Gem
|
2
|
+
Copyright (C) 2006 Doug Fales
|
3
|
+
Doug Fales mailto:doug.fales@gmail.com
|
4
|
+
|
5
|
+
== What It Does
|
6
|
+
This library reads GPX files and provides an API for reading and manipulating
|
7
|
+
the data as objects. For more info on the GPX format, see
|
8
|
+
http://www.topografix.com/gpx.asp.
|
9
|
+
|
10
|
+
In addition to parsing GPX files, this library is capable of converting
|
11
|
+
Magellan NMEA files to GPX, and writing new GPX files. It can crop and delete
|
12
|
+
rectangular areas within a file, and it also calculates some meta-data about
|
13
|
+
the tracks and points in a file (such as distance, duration, average speed,
|
14
|
+
etc).
|
15
|
+
|
16
|
+
== Examples
|
17
|
+
Reading a GPX file, and cropping its contents to a given area:
|
18
|
+
gpx = GPX::GPXFile.new(:gpx_file => filename) # Read GPX file
|
19
|
+
bounds = GPX::Bounds.new(params) # Create a rectangular area to crop
|
20
|
+
gpx.crop(bounds) # Crop it
|
21
|
+
gpx.write(filename) # Save it
|
22
|
+
|
23
|
+
Converting a Magellan track log to GPX:
|
24
|
+
if GPX::MagellanTrackLog::is_magellan_file?(filename)
|
25
|
+
GPX::MagellanTrackLog::convert_to_gpx(filename, "#{filename}.gpx")
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
== Notes
|
30
|
+
This library was written to bridge the gap between my Garmin Geko
|
31
|
+
and my website, WalkingBoss.org. For that reason, it has always been more of a
|
32
|
+
work-in-progress than an attempt at full GPX compliance. The track side of the
|
33
|
+
library has seen much more use than the route/waypoint side, so if you're doing
|
34
|
+
something with routes or waypoints, you may need to tweak some things.
|
35
|
+
|
36
|
+
Since this code uses REXML to read an entire GPX file into memory, it is not
|
37
|
+
the fastest possible solution for working with GPX data, especially if you are
|
38
|
+
working with tracks from several days or weeks.
|
39
|
+
|
40
|
+
Finally, it should be noted that none of the distance/speed calculation or
|
41
|
+
crop/delete code has been tested under International Date Line-crossing
|
42
|
+
conditions. That particular part of the code will likely be unreliable if
|
43
|
+
you're zig-zagging across 180 degrees longitude routinely.
|
data/Rakefile
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require File.dirname(__FILE__) + '/lib/gpx'
|
7
|
+
|
8
|
+
PKG_VERSION = GPX::VERSION
|
9
|
+
PKG_NAME = "gpx"
|
10
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
11
|
+
RUBY_FORGE_PROJECT = "gpx"
|
12
|
+
RUBY_FORGE_USER = ENV['RUBY_FORGE_USER'] || "dougfales"
|
13
|
+
RELEASE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
14
|
+
|
15
|
+
PKG_FILES = FileList[
|
16
|
+
"lib/**/*", "bin/*", "tests/**/*", "[A-Z]*", "Rakefile", "doc/**/*"
|
17
|
+
]
|
18
|
+
|
19
|
+
desc "Default Task"
|
20
|
+
task :default => [ :test ]
|
21
|
+
|
22
|
+
# Run the unit tests
|
23
|
+
desc "Run all unit tests"
|
24
|
+
Rake::TestTask.new("test") { |t|
|
25
|
+
t.libs << "lib"
|
26
|
+
t.pattern = 'tests/*_test.rb'
|
27
|
+
t.verbose = true
|
28
|
+
}
|
29
|
+
|
30
|
+
# Make a console, useful when working on tests
|
31
|
+
desc "Generate a test console"
|
32
|
+
task :console do
|
33
|
+
verbose( false ) { sh "irb -I lib/ -r 'gpx'" }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Genereate the RDoc documentation
|
37
|
+
desc "Create documentation"
|
38
|
+
Rake::RDocTask.new("doc") { |rdoc|
|
39
|
+
rdoc.title = "Ruby GPX API"
|
40
|
+
rdoc.rdoc_dir = 'html'
|
41
|
+
rdoc.rdoc_files.include('README')
|
42
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
43
|
+
}
|
44
|
+
|
45
|
+
# Genereate the package
|
46
|
+
spec = Gem::Specification.new do |s|
|
47
|
+
|
48
|
+
s.name = 'gpx'
|
49
|
+
s.version = PKG_VERSION
|
50
|
+
s.summary = <<-EOF
|
51
|
+
A basic API for reading and writing GPX files.
|
52
|
+
EOF
|
53
|
+
s.description = <<-EOF
|
54
|
+
A basic API for reading and writing GPX files.
|
55
|
+
EOF
|
56
|
+
|
57
|
+
s.files = PKG_FILES
|
58
|
+
|
59
|
+
s.require_path = 'lib'
|
60
|
+
s.autorequire = 'gpx'
|
61
|
+
|
62
|
+
s.has_rdoc = true
|
63
|
+
|
64
|
+
s.author = "Doug Fales"
|
65
|
+
s.email = "doug.fales@gmail.com"
|
66
|
+
s.homepage = "http://gpx.rubyforge.com/"
|
67
|
+
end
|
68
|
+
|
69
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
70
|
+
pkg.need_zip = true
|
71
|
+
pkg.need_tar = true
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "Report code statistics (KLOCs, etc) from the application"
|
75
|
+
task :stats do
|
76
|
+
require 'code_statistics'
|
77
|
+
CodeStatistics.new(
|
78
|
+
["Library", "lib"],
|
79
|
+
["Units", "tests"]
|
80
|
+
).to_s
|
81
|
+
end
|
data/lib/gpx.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Doug Fales
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
$:.unshift(File.dirname(__FILE__))
|
24
|
+
require 'rexml/document'
|
25
|
+
require 'date'
|
26
|
+
require 'time'
|
27
|
+
require 'csv'
|
28
|
+
require 'gpx/gpx'
|
29
|
+
require 'gpx/gpx_file'
|
30
|
+
require 'gpx/bounds'
|
31
|
+
require 'gpx/track'
|
32
|
+
require 'gpx/route'
|
33
|
+
require 'gpx/segment'
|
34
|
+
require 'gpx/point'
|
35
|
+
require 'gpx/trackpoint'
|
36
|
+
require 'gpx/waypoint'
|
37
|
+
require 'gpx/magellan_track_log'
|
data/lib/gpx/bounds.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Doug Fales
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
module GPX
|
24
|
+
class Bounds < Base
|
25
|
+
attr_accessor :min_lat, :max_lat, :max_lon, :min_lon, :center_lat, :center_lon
|
26
|
+
|
27
|
+
# Creates a new bounds object with the passed-in min and max longitudes
|
28
|
+
# and latitudes.
|
29
|
+
def initialize(opts = { :min_lat => 90.0, :max_lat => -90.0, :min_lon => 180.0, :max_lon => -180.0})
|
30
|
+
@min_lat, @max_lat = opts[:min_lat].to_f, opts[:max_lat].to_f
|
31
|
+
@min_lon, @max_lon = opts[:min_lon].to_f, opts[:max_lon].to_f
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the middle latitude.
|
35
|
+
def center_lat
|
36
|
+
distance = (max_lat - min_lat)/2.0
|
37
|
+
(min_lat + distance)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the middle longitude.
|
41
|
+
def center_lon
|
42
|
+
distance = (max_lon - min_lon)/2.0
|
43
|
+
(min_lon + distance)
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_xml
|
47
|
+
bnd = REXML::Element.new('bounds')
|
48
|
+
bnd.attributes['min_lat'] = min_lat
|
49
|
+
bnd.attributes['min_lon'] = min_lon
|
50
|
+
bnd.attributes['max_lat'] = max_lat
|
51
|
+
bnd.attributes['max_lon'] = max_lon
|
52
|
+
bnd
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns true if the pt is within these bounds.
|
56
|
+
def contains?(pt)
|
57
|
+
(pt.lat >= min_lat and pt.lat <= max_lat and pt.lon >= min_lon and pt.lon <= max_lon)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Adds an item to itself, expanding its min/max lat/lon as needed to
|
61
|
+
# contain the given item. The item can be either another instance of
|
62
|
+
# Bounds or a Point.
|
63
|
+
def add(item)
|
64
|
+
if(item.respond_to?(:lat) and item.respond_to?(:lon))
|
65
|
+
@min_lat = item.lat if item.lat < @min_lat
|
66
|
+
@min_lon = item.lon if item.lon < @min_lon
|
67
|
+
@max_lat = item.lat if item.lat > @max_lat
|
68
|
+
@max_lon = item.lon if item.lon > @max_lon
|
69
|
+
else
|
70
|
+
@min_lat = item.min_lat if item.min_lat < @min_lat
|
71
|
+
@min_lon = item.min_lon if item.min_lon < @min_lon
|
72
|
+
@max_lat = item.max_lat if item.max_lat > @max_lat
|
73
|
+
@max_lon = item.max_lon if item.max_lon > @max_lon
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the min_lat, min_lon, max_lat, and max_lon in a labeled string.
|
78
|
+
def to_s
|
79
|
+
"min_lat: #{min_lat} min_lon: #{min_lon} max_lat: #{max_lat} max_lon: #{max_lon}"
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
data/lib/gpx/gpx.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Doug Fales
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
module GPX
|
24
|
+
VERSION = "0.1"
|
25
|
+
|
26
|
+
# A common base class which provides a useful initializer method to many
|
27
|
+
# class in the GPX library.
|
28
|
+
class Base
|
29
|
+
include REXML
|
30
|
+
|
31
|
+
# This initializer can take a REXML::Element and scrape out any text
|
32
|
+
# elements with the names given in the "text_elements" array. Each
|
33
|
+
# element found underneath "parent" with a name in "text_elements" causes
|
34
|
+
# an attribute to be initialized on the instance. This means you don't
|
35
|
+
# have to pick out individual text elements in each initializer of each
|
36
|
+
# class (Route, TrackPoint, Track, etc). Just pass an array of possible
|
37
|
+
# attributes to this method.
|
38
|
+
def instantiate_with_text_elements(parent, text_elements)
|
39
|
+
text_elements.each do |el|
|
40
|
+
unless parent.elements[el].nil?
|
41
|
+
val = parent.elements[el].text
|
42
|
+
code = <<-code
|
43
|
+
attr_accessor #{ el }
|
44
|
+
#{el}=#{val}
|
45
|
+
code
|
46
|
+
class_eval code
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
data/lib/gpx/gpx_file.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Doug Fales
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
module GPX
|
24
|
+
class GPXFile < Base
|
25
|
+
attr_reader :tracks, :routes, :waypoints, :bounds, :lowest_point, :highest_point, :distance, :duration, :average_speed
|
26
|
+
|
27
|
+
|
28
|
+
# This initializer can be used to create a new GPXFile from an existing
|
29
|
+
# file or to create a new GPXFile instance with no data (so that you can
|
30
|
+
# add tracks and points and write it out to a new file later).
|
31
|
+
# To read an existing GPX file, do this:
|
32
|
+
# gpx_file = GPXFile.new(:gpx_file => 'mygpxfile.gpx')
|
33
|
+
# puts "Speed: #{gpx_file.average_speed}"
|
34
|
+
# puts "Duration: #{gpx_file.duration}"
|
35
|
+
# puts "Bounds: #{gpx_file.bounds}"
|
36
|
+
#
|
37
|
+
# To create a new blank GPXFile instance:
|
38
|
+
# gpx_file = GPXFile.new
|
39
|
+
# Note that you can pass in any instance variables to this form of the initializer, including Tracks or Segments:
|
40
|
+
# some_track = get_track_from_csv('some_other_format.csv')
|
41
|
+
# gpx_file = GPXFile.new(:tracks => [some_track])
|
42
|
+
#
|
43
|
+
def initialize(opts = {})
|
44
|
+
@duration = 0
|
45
|
+
if(opts[:gpx_file])
|
46
|
+
gpx_file = opts[:gpx_file]
|
47
|
+
case gpx_file
|
48
|
+
when String
|
49
|
+
gpx_file = File.open(gpx_file)
|
50
|
+
end
|
51
|
+
reset_meta_data
|
52
|
+
@xml = Document.new(gpx_file, :ignore_whitespace_nodes => :all)
|
53
|
+
|
54
|
+
bounds_element = (XPath.match(@xml, "/gpx/metadata/bounds").first rescue nil)
|
55
|
+
if bounds_element
|
56
|
+
@bounds.min_lat = bounds_element.attributes["min_lat"].to_f
|
57
|
+
@bounds.min_lon = bounds_element.attributes["min_lon"].to_f
|
58
|
+
@bounds.max_lat = bounds_element.attributes["max_lat"].to_f
|
59
|
+
@bounds.max_lon = bounds_element.attributes["max_lon"].to_f
|
60
|
+
else
|
61
|
+
get_bounds = true
|
62
|
+
end
|
63
|
+
|
64
|
+
@tracks = XPath.match(@xml, "/gpx/trk").collect do |trk|
|
65
|
+
trk = Track.new(:element => trk, :gpx_file => self)
|
66
|
+
update_meta_data(trk, get_bounds)
|
67
|
+
trk
|
68
|
+
end
|
69
|
+
@waypoints = XPath.match(@xml, "/gpx/wpt").collect { |wpt| Waypoint.new(:element => wpt, :gpx_file => self) }
|
70
|
+
@routes = XPath.match(@xml, "/gpx/rte").collect { |rte| Route.new(:element => rte, :gpx_file => self) }
|
71
|
+
|
72
|
+
@tracks.delete_if { |t| t.empty? }
|
73
|
+
|
74
|
+
calculate_duration
|
75
|
+
else
|
76
|
+
reset_meta_data
|
77
|
+
opts.each { |attr_name, value| instance_variable_set("@#{attr_name.to_s}", value) }
|
78
|
+
unless(@tracks.nil? or @tracks.size.zero?)
|
79
|
+
@tracks.each { |trk| update_meta_data(trk) }
|
80
|
+
calculate_duration
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the distance, in kilometers, meters, or miles, of all of the
|
86
|
+
# tracks and segments contained in this GPXFile.
|
87
|
+
def distance(opts = { :units => 'kilometers' })
|
88
|
+
case opts[:units]
|
89
|
+
when /kilometers/i
|
90
|
+
return @distance
|
91
|
+
when /meters/i
|
92
|
+
return (@distance * 1000)
|
93
|
+
when /miles/i
|
94
|
+
return (@distance * 0.62)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the average speed, in km/hr, meters/hr, or miles/hr, of this
|
99
|
+
# GPXFile. The calculation is based on the total distance divided by the
|
100
|
+
# total duration of the entire file.
|
101
|
+
def average_speed(opts = { :units => 'kilometers' })
|
102
|
+
case opts[:units]
|
103
|
+
when /kilometers/i
|
104
|
+
return @distance / (@duration/3600.0)
|
105
|
+
when /meters/i
|
106
|
+
return (@distance * 1000) / (@duration/3600.0)
|
107
|
+
when /miles/i
|
108
|
+
return (@distance * 0.62) / (@duration/3600.0)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Crops any points falling within a rectangular area. Identical to the
|
113
|
+
# delete_area method in every respect except that the points outside of
|
114
|
+
# the given area are deleted. Note that this method automatically causes
|
115
|
+
# the meta data to be updated after deletion.
|
116
|
+
def crop(area)
|
117
|
+
reset_meta_data
|
118
|
+
keep_tracks = []
|
119
|
+
tracks.each do |trk|
|
120
|
+
trk.crop(area)
|
121
|
+
unless trk.empty?
|
122
|
+
update_meta_data(trk)
|
123
|
+
keep_tracks << trk
|
124
|
+
end
|
125
|
+
end
|
126
|
+
@tracks = keep_tracks
|
127
|
+
routes.each { |rte| rte.crop(area) }
|
128
|
+
waypoints.each { |wpt| wpt.crop(area) }
|
129
|
+
end
|
130
|
+
|
131
|
+
# Deletes any points falling within a rectangular area. The "area"
|
132
|
+
# parameter is usually an instance of the Bounds class. Note that this
|
133
|
+
# method cascades into similarly named methods of subordinate classes
|
134
|
+
# (i.e. Track, Segment), which means, if you want the deletion to apply
|
135
|
+
# to all the data, you only call this one (and not the one in Track or
|
136
|
+
# Segment classes). Note that this method automatically causes the meta
|
137
|
+
# data to be updated after deletion.
|
138
|
+
def delete_area(area)
|
139
|
+
reset_meta_data
|
140
|
+
keep_tracks = []
|
141
|
+
tracks.each do |trk|
|
142
|
+
trk.delete_area(area)
|
143
|
+
unless trk.empty?
|
144
|
+
update_meta_data(trk)
|
145
|
+
keep_tracks << trk
|
146
|
+
end
|
147
|
+
end
|
148
|
+
@tracks = keep_tracks
|
149
|
+
routes.each { |rte| rte.delete_area(area) }
|
150
|
+
waypoints.each { |wpt| wpt.delete_area(area) }
|
151
|
+
end
|
152
|
+
|
153
|
+
# Resets the meta data for this GPX file. Meta data includes the bounds,
|
154
|
+
# the high and low points, and the distance.
|
155
|
+
def reset_meta_data
|
156
|
+
@bounds = Bounds.new
|
157
|
+
@highest_point = nil
|
158
|
+
@lowest_point = nil
|
159
|
+
@distance = 0.0
|
160
|
+
end
|
161
|
+
|
162
|
+
# Updates the meta data for this GPX file. Meta data includes the
|
163
|
+
# bounds, the high and low points, and the distance. This is useful when
|
164
|
+
# you modify the GPX data (i.e. by adding or deleting points) and you
|
165
|
+
# want the meta data to accurately reflect the new data.
|
166
|
+
def update_meta_data(trk, get_bounds = true)
|
167
|
+
@lowest_point = trk.lowest_point if(@lowest_point.nil? or trk.lowest_point.elevation < @lowest_point.elevation)
|
168
|
+
@highest_point = trk.highest_point if(@highest_point.nil? or trk.highest_point.elevation > @highest_point.elevation)
|
169
|
+
@bounds.add(trk.bounds) if get_bounds
|
170
|
+
@distance += trk.distance
|
171
|
+
end
|
172
|
+
|
173
|
+
# Serialize the current GPXFile to a gpx file named <filename>.
|
174
|
+
# If the file does not exist, it is created. If it does exist, it is overwritten.
|
175
|
+
def write(filename)
|
176
|
+
|
177
|
+
doc = Document.new
|
178
|
+
gpx_elem = Element.new('gpx')
|
179
|
+
doc.add(gpx_elem)
|
180
|
+
gpx_elem.attributes['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
|
181
|
+
gpx_elem.attributes['xmlns'] = "http://www.topografix.com/GPX/1/1"
|
182
|
+
gpx_elem.attributes['version'] = "1.1"
|
183
|
+
gpx_elem.attributes['creator'] = "GPX RubyGem 0.1 Copyright 2006 Doug Fales -- http://walkingboss.com"
|
184
|
+
gpx_elem.attributes['xsi:schemaLocation'] = "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
185
|
+
|
186
|
+
meta_data_elem = Element.new('metadata')
|
187
|
+
name_elem = Element.new('name')
|
188
|
+
name_elem.text = File.basename(filename)
|
189
|
+
meta_data_elem.elements << name_elem
|
190
|
+
|
191
|
+
time_elem = Element.new('time')
|
192
|
+
time_elem.text = Time.now.xmlschema
|
193
|
+
meta_data_elem.elements << time_elem
|
194
|
+
|
195
|
+
meta_data_elem.elements << bounds.to_xml
|
196
|
+
|
197
|
+
gpx_elem.elements << meta_data_elem
|
198
|
+
|
199
|
+
tracks.each { |t| gpx_elem.add_element t.to_xml } unless tracks.nil?
|
200
|
+
waypoints.each { |w| gpx_elem.add_element w.to_xml } unless waypoints.nil?
|
201
|
+
routes.each { |r| gpx_elem.add_element r.to_xml } unless routes.nil?
|
202
|
+
|
203
|
+
File.open(filename, 'w') { |f| doc.write(f) }
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
# Calculates and sets the duration attribute by subtracting the time on
|
209
|
+
# the very first point from the time on the very last point.
|
210
|
+
def calculate_duration
|
211
|
+
@duration = 0
|
212
|
+
if(@tracks.nil? or @tracks.size.zero? or @tracks[0].segments.nil? or @tracks[0].segments.size.zero?)
|
213
|
+
return @duration
|
214
|
+
end
|
215
|
+
@duration = (@tracks[-1].segments[-1].points[-1].time - @tracks.first.segments.first.points.first.time)
|
216
|
+
rescue
|
217
|
+
@duration = 0
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
end
|
222
|
+
end
|