andrewhao-gpx 0.7

Sign up to get free protection for your applications and to get access to all the features.
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