andrewhao-gpx 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/ChangeLog +60 -0
- data/Gemfile +4 -0
- data/README.rdoc +46 -0
- data/Rakefile +23 -0
- data/gpx.gemspec +22 -0
- data/lib/gpx.rb +38 -0
- data/lib/gpx/bounds.rb +74 -0
- data/lib/gpx/gpx.rb +46 -0
- data/lib/gpx/gpx_file.rb +302 -0
- data/lib/gpx/magellan_track_log.rb +132 -0
- data/lib/gpx/point.rb +90 -0
- data/lib/gpx/route.rb +58 -0
- data/lib/gpx/segment.rb +207 -0
- data/lib/gpx/track.rb +136 -0
- data/lib/gpx/trackpoint.rb +58 -0
- data/lib/gpx/version.rb +3 -0
- data/lib/gpx/waypoint.rb +74 -0
- data/pkg/gpx-0.7.gem +0 -0
- data/tests/gpx10_test.rb +12 -0
- data/tests/gpx_file_test.rb +48 -0
- data/tests/gpx_files/arches.gpx +1 -0
- data/tests/gpx_files/big.gpx +1 -0
- data/tests/gpx_files/gpx10.gpx +17 -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 +9 -0
- data/tests/gpx_files/tracks.gpx +6304 -0
- data/tests/gpx_files/waypoints.gpx +20 -0
- data/tests/gpx_files/with_or_without_elev.gpx +29 -0
- data/tests/magellan_test.rb +18 -0
- data/tests/output_test.rb +115 -0
- data/tests/route_test.rb +63 -0
- data/tests/segment_test.rb +58 -0
- data/tests/track_file_test.rb +75 -0
- data/tests/track_point_test.rb +30 -0
- data/tests/track_test.rb +65 -0
- data/tests/waypoint_test.rb +44 -0
- metadata +126 -0
data/.gitignore
ADDED
data/ChangeLog
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
2010-02-27 Doug Fales <doug@falesafeconsulting.com>
|
2
|
+
* Putting the gem building stuff into a gemspec.
|
3
|
+
* Fixing some tests since git does not believe in empty directories.
|
4
|
+
* Fixing README formatting.
|
5
|
+
|
6
|
+
2010-02-27 Doug Fales <doug@falesafeconsulting.com>
|
7
|
+
* README edits.
|
8
|
+
* More rdoc tweaks.
|
9
|
+
* Changing README to rdoc ext for github.
|
10
|
+
|
11
|
+
2009-10-13 Doug Fales <doug@falesafeconsulting.com>
|
12
|
+
* Adding the ability to write GPX to a string in addition to a file. Thanks to Douglas Robertson for the patch.
|
13
|
+
|
14
|
+
2009-09-27 Doug Fales <doug@falesafeconsulting.com>
|
15
|
+
* Adding a patch from Douglas Robertson that allows using version 1.0 of the schema for output.
|
16
|
+
|
17
|
+
2009-07-07 Doug Fales <doug@falesafeconsulting.com>
|
18
|
+
* Adding changelog.
|
19
|
+
* Revving to version 0.5.
|
20
|
+
* Changing my contact email address.
|
21
|
+
* Patches from Tom Verbeure (mtbguru.com) to work with libxml-ruby 1.x.
|
22
|
+
|
23
|
+
2009-06-17 Doug Fales <doug@falesafeconsulting.com>
|
24
|
+
* Patch from Kang-min Liu to support speed element.
|
25
|
+
|
26
|
+
2008-02-19 Doug Fales <doug@falesafeconsulting.com>
|
27
|
+
* Revving to 0.4.
|
28
|
+
* 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.
|
29
|
+
|
30
|
+
2008-02-11 Doug Fales <doug@falesafeconsulting.com>
|
31
|
+
* Going to version 0.3.
|
32
|
+
* Updating unit tests in light of recent fixes to routes and waypoints code.
|
33
|
+
|
34
|
+
2008-02-08 Doug Fales <doug@falesafeconsulting.com>
|
35
|
+
* 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.
|
36
|
+
|
37
|
+
2007-12-04 Doug Fales <doug@falesafeconsulting.com>
|
38
|
+
* Thanks to Christian Koerner for finding and fixing these bugs in the waypoint code.
|
39
|
+
* Another patch from Gaku Ueda. This one allows you to pass in a string of GPX data using the :gpx_date => option. Thanks Gaku!
|
40
|
+
|
41
|
+
2007-11-30 Doug Fales <doug@falesafeconsulting.com>
|
42
|
+
* Updating the version #.
|
43
|
+
* 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.
|
44
|
+
|
45
|
+
2006-12-04 Doug Fales <doug@falesafeconsulting.com>
|
46
|
+
* 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.
|
47
|
+
|
48
|
+
2006-12-03 Doug Fales <doug@falesafeconsulting.com>
|
49
|
+
* Fixing more nil time exceptions.
|
50
|
+
* Fixing an exception in contains_time?.
|
51
|
+
|
52
|
+
2006-11-28 Doug Fales <doug@falesafeconsulting.com>
|
53
|
+
* A couple of fixes to make the library comply with the different attribute names possible on the bounds element.
|
54
|
+
|
55
|
+
2006-10-28 Doug Fales <doug@falesafeconsulting.com>
|
56
|
+
* Fixing nil time bug.
|
57
|
+
|
58
|
+
2006-10-14 Doug Fales <doug@falesafeconsulting.com>
|
59
|
+
* Initial import of gpx gem.
|
60
|
+
|
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
= GPX Gem
|
2
|
+
|
3
|
+
Copyright (C) 2006 Doug Fales mailto:doug@falesafeconsulting.com
|
4
|
+
|
5
|
+
== What It Does
|
6
|
+
|
7
|
+
This library reads GPX files and provides an API for reading and manipulating
|
8
|
+
the data as objects. For more info on the GPX format, see
|
9
|
+
http://www.topografix.com/gpx.asp.
|
10
|
+
|
11
|
+
In addition to parsing GPX files, this library is capable of converting
|
12
|
+
Magellan NMEA files to GPX, and writing new GPX files. It can crop and delete
|
13
|
+
rectangular areas within a file, and it also calculates some meta-data about
|
14
|
+
the tracks and points in a file (such as distance, duration, average speed,
|
15
|
+
etc).
|
16
|
+
|
17
|
+
== Examples
|
18
|
+
|
19
|
+
Reading a GPX file, and cropping its contents to a given area:
|
20
|
+
gpx = GPX::GPXFile.new(:gpx_file => filename) # Read GPX file
|
21
|
+
bounds = GPX::Bounds.new(params) # Create a rectangular area to crop
|
22
|
+
gpx.crop(bounds) # Crop it
|
23
|
+
gpx.write(filename) # Save it
|
24
|
+
|
25
|
+
Converting a Magellan track log to GPX:
|
26
|
+
if GPX::MagellanTrackLog::is_magellan_file?(filename)
|
27
|
+
GPX::MagellanTrackLog::convert_to_gpx(filename, "#{filename}.gpx")
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
== Notes
|
32
|
+
|
33
|
+
This library was written to bridge the gap between my Garmin Geko
|
34
|
+
and my website, WalkingBoss.org (RIP). For that reason, it has always been more of a
|
35
|
+
work-in-progress than an attempt at full GPX compliance. The track side of the
|
36
|
+
library has seen much more use than the route/waypoint side, so if you're doing
|
37
|
+
something with routes or waypoints, you may need to tweak some things.
|
38
|
+
|
39
|
+
Since this code uses XML to read an entire GPX file into memory, it is not
|
40
|
+
the fastest possible solution for working with GPX data, especially if you are
|
41
|
+
working with tracks from several days or weeks.
|
42
|
+
|
43
|
+
Finally, it should be noted that none of the distance/speed calculation or
|
44
|
+
crop/delete code has been tested under International Date Line-crossing
|
45
|
+
conditions. That particular part of the code will likely be unreliable if
|
46
|
+
you're zig-zagging across 180 degrees longitude routinely.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
4
|
+
|
5
|
+
desc "Default Task"
|
6
|
+
task :default => [ :test ]
|
7
|
+
|
8
|
+
# Run the unit tests
|
9
|
+
desc "Run all unit tests"
|
10
|
+
Rake::TestTask.new("test") { |t|
|
11
|
+
t.libs << "lib"
|
12
|
+
t.pattern = 'tests/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
}
|
15
|
+
|
16
|
+
# Genereate the RDoc documentation
|
17
|
+
desc "Create documentation"
|
18
|
+
Rake::RDocTask.new("doc") { |rdoc|
|
19
|
+
rdoc.title = "Ruby GPX API"
|
20
|
+
rdoc.rdoc_dir = 'html'
|
21
|
+
rdoc.rdoc_files.include('README')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
}
|
data/gpx.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gpx/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'andrewhao-gpx'
|
8
|
+
s.version = GPX::VERSION
|
9
|
+
s.authors = ["Guillaume Dott", "Doug Fales", "Andrew Hao"]
|
10
|
+
s.email = ["guillaume+github@dott.fr", "doug.fales@gmail.com", "andrewhao@gmail.com"]
|
11
|
+
s.summary = %q{A basic API for reading and writing GPX files.}
|
12
|
+
s.description = %q{A basic API for reading and writing GPX files.}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split($/)
|
15
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.has_rdoc = true
|
18
|
+
|
19
|
+
s.homepage = "http://www.github.com/andrewhao/gpx"
|
20
|
+
s.add_dependency 'rake'
|
21
|
+
s.add_dependency 'nokogiri'
|
22
|
+
end
|
data/lib/gpx.rb
ADDED
@@ -0,0 +1,38 @@
|
|
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
|
+
|
24
|
+
require 'time'
|
25
|
+
require 'nokogiri'
|
26
|
+
|
27
|
+
require 'gpx/version'
|
28
|
+
|
29
|
+
require 'gpx/gpx'
|
30
|
+
require 'gpx/gpx_file'
|
31
|
+
require 'gpx/bounds'
|
32
|
+
require 'gpx/track'
|
33
|
+
require 'gpx/route'
|
34
|
+
require 'gpx/segment'
|
35
|
+
require 'gpx/point'
|
36
|
+
require 'gpx/trackpoint'
|
37
|
+
require 'gpx/waypoint'
|
38
|
+
require 'gpx/magellan_track_log'
|
data/lib/gpx/bounds.rb
ADDED
@@ -0,0 +1,74 @@
|
|
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
|
+
# Returns true if the pt is within these bounds.
|
47
|
+
def contains?(pt)
|
48
|
+
(pt.lat >= min_lat and pt.lat <= max_lat and pt.lon >= min_lon and pt.lon <= max_lon)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Adds an item to itself, expanding its min/max lat/lon as needed to
|
52
|
+
# contain the given item. The item can be either another instance of
|
53
|
+
# Bounds or a Point.
|
54
|
+
def add(item)
|
55
|
+
if(item.respond_to?(:lat) and item.respond_to?(:lon))
|
56
|
+
@min_lat = item.lat if item.lat < @min_lat
|
57
|
+
@min_lon = item.lon if item.lon < @min_lon
|
58
|
+
@max_lat = item.lat if item.lat > @max_lat
|
59
|
+
@max_lon = item.lon if item.lon > @max_lon
|
60
|
+
else
|
61
|
+
@min_lat = item.min_lat if item.min_lat < @min_lat
|
62
|
+
@min_lon = item.min_lon if item.min_lon < @min_lon
|
63
|
+
@max_lat = item.max_lat if item.max_lat > @max_lat
|
64
|
+
@max_lon = item.max_lon if item.max_lon > @max_lon
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the min_lat, min_lon, max_lat, and max_lon in a labeled string.
|
69
|
+
def to_s
|
70
|
+
"min_lat: #{min_lat} min_lon: #{min_lon} max_lat: #{max_lat} max_lon: #{max_lon}"
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
data/lib/gpx/gpx.rb
ADDED
@@ -0,0 +1,46 @@
|
|
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
|
+
# A common base class which provides a useful initializer method to many
|
25
|
+
# class in the GPX library.
|
26
|
+
class Base
|
27
|
+
# This initializer can take an XML::Node and scrape out any text
|
28
|
+
# elements with the names given in the "text_elements" array. Each
|
29
|
+
# element found underneath "parent" with a name in "text_elements" causes
|
30
|
+
# an attribute to be initialized on the instance. This means you don't
|
31
|
+
# have to pick out individual text elements in each initializer of each
|
32
|
+
# class (Route, TrackPoint, Track, etc). Just pass an array of possible
|
33
|
+
# attributes to this method.
|
34
|
+
def instantiate_with_text_elements(parent, text_elements)
|
35
|
+
text_elements.each do |el|
|
36
|
+
child_xpath = "#{el}"
|
37
|
+
unless parent.at(child_xpath).nil?
|
38
|
+
val = parent.at(child_xpath).inner_text
|
39
|
+
self.send("#{el}=", val)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/gpx/gpx_file.rb
ADDED
@@ -0,0 +1,302 @@
|
|
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_accessor :tracks, :routes, :waypoints, :bounds, :lowest_point, :highest_point, :duration, :ns, :time, :name, :version, :creator
|
26
|
+
|
27
|
+
DEFAULT_CREATOR = "GPX RubyGem #{GPX::VERSION} -- http://dougfales.github.io/gpx/".freeze
|
28
|
+
|
29
|
+
# This initializer can be used to create a new GPXFile from an existing
|
30
|
+
# file or to create a new GPXFile instance with no data (so that you can
|
31
|
+
# add tracks and points and write it out to a new file later).
|
32
|
+
# To read an existing GPX file, do this:
|
33
|
+
# gpx_file = GPXFile.new(:gpx_file => 'mygpxfile.gpx')
|
34
|
+
# puts "Speed: #{gpx_file.average_speed}"
|
35
|
+
# puts "Duration: #{gpx_file.duration}"
|
36
|
+
# puts "Bounds: #{gpx_file.bounds}"
|
37
|
+
#
|
38
|
+
# To read a GPX file from a string, use :gpx_data.
|
39
|
+
# gpx_file = GPXFile.new(:gpx_data => '<xml ...><gpx>...</gpx>)
|
40
|
+
# To create a new blank GPXFile instance:
|
41
|
+
# gpx_file = GPXFile.new
|
42
|
+
# Note that you can pass in any instance variables to this form of the initializer, including Tracks or Segments:
|
43
|
+
# some_track = get_track_from_csv('some_other_format.csv')
|
44
|
+
# gpx_file = GPXFile.new(:tracks => [some_track])
|
45
|
+
#
|
46
|
+
def initialize(opts = {})
|
47
|
+
@duration = 0
|
48
|
+
if(opts[:gpx_file] or opts[:gpx_data])
|
49
|
+
if opts[:gpx_file]
|
50
|
+
gpx_file = opts[:gpx_file]
|
51
|
+
gpx_file = File.open(gpx_file) unless gpx_file.is_a?(File)
|
52
|
+
@xml = Nokogiri::XML(gpx_file)
|
53
|
+
else
|
54
|
+
@xml = Nokogiri::XML(opts[:gpx_data])
|
55
|
+
end
|
56
|
+
|
57
|
+
reset_meta_data
|
58
|
+
bounds_element = (@xml.at("metadata/bounds") rescue nil)
|
59
|
+
if bounds_element
|
60
|
+
@bounds.min_lat = get_bounds_attr_value(bounds_element, %w{ min_lat minlat minLat })
|
61
|
+
@bounds.min_lon = get_bounds_attr_value(bounds_element, %w{ min_lon minlon minLon})
|
62
|
+
@bounds.max_lat = get_bounds_attr_value(bounds_element, %w{ max_lat maxlat maxLat})
|
63
|
+
@bounds.max_lon = get_bounds_attr_value(bounds_element, %w{ max_lon maxlon maxLon})
|
64
|
+
else
|
65
|
+
get_bounds = true
|
66
|
+
end
|
67
|
+
|
68
|
+
@time = Time.parse(@xml.at("metadata/time").inner_text) rescue nil
|
69
|
+
@name = @xml.at("metadata/name").inner_text rescue nil
|
70
|
+
@tracks = []
|
71
|
+
@xml.search("trk").each do |trk|
|
72
|
+
trk = Track.new(:element => trk, :gpx_file => self)
|
73
|
+
update_meta_data(trk, get_bounds)
|
74
|
+
@tracks << trk
|
75
|
+
end
|
76
|
+
@waypoints = []
|
77
|
+
@xml.search("wpt").each { |wpt| @waypoints << Waypoint.new(:element => wpt, :gpx_file => self) }
|
78
|
+
@routes = []
|
79
|
+
@xml.search("rte").each { |rte| @routes << Route.new(:element => rte, :gpx_file => self) }
|
80
|
+
@tracks.delete_if { |t| t.empty? }
|
81
|
+
|
82
|
+
calculate_duration
|
83
|
+
else
|
84
|
+
reset_meta_data
|
85
|
+
opts.each { |attr_name, value| instance_variable_set("@#{attr_name.to_s}", value) }
|
86
|
+
unless(@tracks.nil? or @tracks.size.zero?)
|
87
|
+
@tracks.each { |trk| update_meta_data(trk) }
|
88
|
+
calculate_duration
|
89
|
+
end
|
90
|
+
end
|
91
|
+
@tracks ||= []
|
92
|
+
@routes ||= []
|
93
|
+
@waypoints ||= []
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_bounds_attr_value(el, possible_names)
|
97
|
+
result = nil
|
98
|
+
possible_names.each do |name|
|
99
|
+
result = el[name]
|
100
|
+
break unless result.nil?
|
101
|
+
end
|
102
|
+
return (result.to_f rescue nil)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the distance, in kilometers, meters, or miles, of all of the
|
106
|
+
# tracks and segments contained in this GPXFile.
|
107
|
+
def distance(opts = { :units => 'kilometers' })
|
108
|
+
case opts[:units]
|
109
|
+
when /kilometers/i
|
110
|
+
return @distance
|
111
|
+
when /meters/i
|
112
|
+
return (@distance * 1000)
|
113
|
+
when /miles/i
|
114
|
+
return (@distance * 0.62)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns the average speed, in km/hr, meters/hr, or miles/hr, of this
|
119
|
+
# GPXFile. The calculation is based on the total distance divided by the
|
120
|
+
# total duration of the entire file.
|
121
|
+
def average_speed(opts = { :units => 'kilometers' })
|
122
|
+
case opts[:units]
|
123
|
+
when /kilometers/i
|
124
|
+
return @distance / (@duration/3600.0)
|
125
|
+
when /meters/i
|
126
|
+
return (@distance * 1000) / (@duration/3600.0)
|
127
|
+
when /miles/i
|
128
|
+
return (@distance * 0.62) / (@duration/3600.0)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Crops any points falling within a rectangular area. Identical to the
|
133
|
+
# delete_area method in every respect except that the points outside of
|
134
|
+
# the given area are deleted. Note that this method automatically causes
|
135
|
+
# the meta data to be updated after deletion.
|
136
|
+
def crop(area)
|
137
|
+
reset_meta_data
|
138
|
+
keep_tracks = []
|
139
|
+
tracks.each do |trk|
|
140
|
+
trk.crop(area)
|
141
|
+
unless trk.empty?
|
142
|
+
update_meta_data(trk)
|
143
|
+
keep_tracks << trk
|
144
|
+
end
|
145
|
+
end
|
146
|
+
@tracks = keep_tracks
|
147
|
+
routes.each { |rte| rte.crop(area) }
|
148
|
+
waypoints.each { |wpt| wpt.crop(area) }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Deletes any points falling within a rectangular area. The "area"
|
152
|
+
# parameter is usually an instance of the Bounds class. Note that this
|
153
|
+
# method cascades into similarly named methods of subordinate classes
|
154
|
+
# (i.e. Track, Segment), which means, if you want the deletion to apply
|
155
|
+
# to all the data, you only call this one (and not the one in Track or
|
156
|
+
# Segment classes). Note that this method automatically causes the meta
|
157
|
+
# data to be updated after deletion.
|
158
|
+
def delete_area(area)
|
159
|
+
reset_meta_data
|
160
|
+
keep_tracks = []
|
161
|
+
tracks.each do |trk|
|
162
|
+
trk.delete_area(area)
|
163
|
+
unless trk.empty?
|
164
|
+
update_meta_data(trk)
|
165
|
+
keep_tracks << trk
|
166
|
+
end
|
167
|
+
end
|
168
|
+
@tracks = keep_tracks
|
169
|
+
routes.each { |rte| rte.delete_area(area) }
|
170
|
+
waypoints.each { |wpt| wpt.delete_area(area) }
|
171
|
+
end
|
172
|
+
|
173
|
+
# Resets the meta data for this GPX file. Meta data includes the bounds,
|
174
|
+
# the high and low points, and the distance.
|
175
|
+
def reset_meta_data
|
176
|
+
@bounds = Bounds.new
|
177
|
+
@highest_point = nil
|
178
|
+
@lowest_point = nil
|
179
|
+
@distance = 0.0
|
180
|
+
end
|
181
|
+
|
182
|
+
# Updates the meta data for this GPX file. Meta data includes the
|
183
|
+
# bounds, the high and low points, and the distance. This is useful when
|
184
|
+
# you modify the GPX data (i.e. by adding or deleting points) and you
|
185
|
+
# want the meta data to accurately reflect the new data.
|
186
|
+
def update_meta_data(trk, get_bounds = true)
|
187
|
+
@lowest_point = trk.lowest_point if(@lowest_point.nil? or (!trk.lowest_point.nil? and trk.lowest_point.elevation < @lowest_point.elevation))
|
188
|
+
@highest_point = trk.highest_point if(@highest_point.nil? or (!trk.highest_point.nil? and trk.highest_point.elevation > @highest_point.elevation))
|
189
|
+
@bounds.add(trk.bounds) if get_bounds
|
190
|
+
@distance += trk.distance
|
191
|
+
end
|
192
|
+
|
193
|
+
# Serialize the current GPXFile to a gpx file named <filename>.
|
194
|
+
# If the file does not exist, it is created. If it does exist, it is overwritten.
|
195
|
+
def write(filename, update_time = true)
|
196
|
+
@time = Time.now if(@time.nil? or update_time)
|
197
|
+
@name ||= File.basename(filename)
|
198
|
+
doc = generate_xml_doc
|
199
|
+
File.open(filename, 'w') { |f| f.write(doc.to_xml) }
|
200
|
+
end
|
201
|
+
|
202
|
+
def to_s(update_time = true)
|
203
|
+
@time = Time.now if(@time.nil? or update_time)
|
204
|
+
doc = generate_xml_doc
|
205
|
+
doc.to_xml
|
206
|
+
end
|
207
|
+
|
208
|
+
def inspect
|
209
|
+
"<#{self.class.name}:...>"
|
210
|
+
end
|
211
|
+
|
212
|
+
private
|
213
|
+
def generate_xml_doc
|
214
|
+
@version ||= '1.1'
|
215
|
+
version_dir = version.gsub('.','/')
|
216
|
+
|
217
|
+
doc = Nokogiri::XML::Builder.new do |xml|
|
218
|
+
xml.gpx(
|
219
|
+
'xsi' => "http://www.w3.org/2001/XMLSchema-instance",
|
220
|
+
'version' => @version.to_s,
|
221
|
+
'creator' => @creator.nil? ? DEFAULT_CREATOR : @creator.to_s,
|
222
|
+
'xsi:schemaLocation' => "http://www.topografix.com/GPX/#{version_dir} http://www.topografix.com/GPX/#{version_dir}/gpx.xsd") \
|
223
|
+
{
|
224
|
+
# version 1.0 of the schema doesn't support the metadata element, so push them straight to the root 'gpx' element
|
225
|
+
if (@version == '1.0') then
|
226
|
+
xml.name @name
|
227
|
+
xml.time @time.xmlschema
|
228
|
+
xml.bound(
|
229
|
+
minlat: bounds.min_lat,
|
230
|
+
minlon: bounds.min_lon,
|
231
|
+
maxlat: bounds.max_lat,
|
232
|
+
maxlon: bounds.max_lon,
|
233
|
+
)
|
234
|
+
else
|
235
|
+
xml.metadata {
|
236
|
+
xml.name @name
|
237
|
+
xml.time @time.xmlschema
|
238
|
+
xml.bound(
|
239
|
+
minlat: bounds.min_lat,
|
240
|
+
minlon: bounds.min_lon,
|
241
|
+
maxlat: bounds.max_lat,
|
242
|
+
maxlon: bounds.max_lon,
|
243
|
+
)
|
244
|
+
}
|
245
|
+
end
|
246
|
+
|
247
|
+
tracks.each do |t|
|
248
|
+
xml.trk {
|
249
|
+
xml.name t.name
|
250
|
+
|
251
|
+
t.segments.each do |seg|
|
252
|
+
xml.trkseg {
|
253
|
+
seg.points.each do |p|
|
254
|
+
xml.trkpt(lat: p.lat, lon: p.lon) {
|
255
|
+
xml.time p.time.xmlschema unless p.time.nil?
|
256
|
+
xml.ele p.elevation unless p.elevation.nil?
|
257
|
+
}
|
258
|
+
end
|
259
|
+
}
|
260
|
+
end
|
261
|
+
}
|
262
|
+
end unless tracks.nil?
|
263
|
+
|
264
|
+
waypoints.each do |w|
|
265
|
+
xml.wpt(lat: w.lat, lon: w.lon) {
|
266
|
+
Waypoint::SUB_ELEMENTS.each do |sub_elem|
|
267
|
+
xml.send(sub_elem, w.send(sub_elem)) if w.respond_to?(sub_elem) && !w.send(sub_elem).nil?
|
268
|
+
end
|
269
|
+
}
|
270
|
+
end unless waypoints.nil?
|
271
|
+
|
272
|
+
routes.each do |r|
|
273
|
+
xml.rte {
|
274
|
+
xml.name r.name
|
275
|
+
|
276
|
+
r.points.each do |p|
|
277
|
+
xml.rtept(lat: p.lat, lon: p.lon) {
|
278
|
+
xml.time p.time.xmlschema unless p.time.nil?
|
279
|
+
xml.ele p.elevation unless p.elevation.nil?
|
280
|
+
}
|
281
|
+
end
|
282
|
+
}
|
283
|
+
end unless routes.nil?
|
284
|
+
}
|
285
|
+
end
|
286
|
+
|
287
|
+
return doc
|
288
|
+
end
|
289
|
+
|
290
|
+
# Calculates and sets the duration attribute by subtracting the time on
|
291
|
+
# the very first point from the time on the very last point.
|
292
|
+
def calculate_duration
|
293
|
+
@duration = 0
|
294
|
+
if(@tracks.nil? or @tracks.size.zero? or @tracks[0].segments.nil? or @tracks[0].segments.size.zero?)
|
295
|
+
return @duration
|
296
|
+
end
|
297
|
+
@duration = (@tracks[-1].segments[-1].points[-1].time - @tracks.first.segments.first.points.first.time)
|
298
|
+
rescue
|
299
|
+
@duration = 0
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|