andrewhao-gpx 0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|