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 ADDED
@@ -0,0 +1,3 @@
1
+ tests/output/*
2
+ .*.swp
3
+ Gemfile.lock
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in seryz.gemspec
4
+ gemspec
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
@@ -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