andrewhao-gpx 0.7 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5f51764c0a0123d2d6f5a835667a95ec6bee62a6
4
+ data.tar.gz: 18edbd7be186b8c271ab7a75cb3e685688cfd1a6
5
+ SHA512:
6
+ metadata.gz: d163c7cfcc8cbaad5029280365824fd80d89e9fe9100e871b3a595eb2e35108c487ee744990686f6d951e5d8e1bcc3691dd8defbae1826ba49e1df3b3560b355
7
+ data.tar.gz: b46cb4436005b206bb4a833ceecc3acf5d200c0169106da710d00cdc03771e3863e7d73e7a664f930d096252d87bc4643ecccfcdf95f197332e798fb863a37bb
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0
5
+ - 2.1
6
+ - 2.2
7
+ - jruby-19mode
8
+ script: "bundle exec rake ci:build"
@@ -0,0 +1,98 @@
1
+ # GPX Gem
2
+
3
+ [<img src="https://travis-ci.org/andrewhao/gpx.svg" alt="Build Status" />](https://travis-ci.org/andrewhao/gpx)
4
+ [![Code Climate](https://codeclimate.com/github/andrewhao/gpx/badges/gpa.svg)](https://codeclimate.com/github/andrewhao/gpx)
5
+
6
+ Copyright (C) 2006 Doug Fales doug@falesafeconsulting.com
7
+
8
+ ## What It Does
9
+
10
+ This library reads GPX files and provides an API for reading and manipulating
11
+ the data as objects. For more info on the GPX format, see
12
+ http://www.topografix.com/gpx.asp.
13
+
14
+ In addition to parsing GPX files, this library is capable of converting
15
+ Magellan NMEA files to GPX, and writing new GPX files. It can crop and delete
16
+ rectangular areas within a file, and it also calculates some meta-data about
17
+ the tracks and points in a file (such as distance, duration, average speed,
18
+ etc).
19
+
20
+ ## Examples
21
+
22
+ Reading a GPX file, and cropping its contents to a given area:
23
+ ```ruby
24
+ gpx = GPX::GPXFile.new(:gpx_file => filename) # Read GPX file
25
+ bounds = GPX::Bounds.new(params) # Create a rectangular area to crop
26
+ gpx.crop(bounds) # Crop it
27
+ gpx.write(filename) # Save it
28
+ ```
29
+
30
+ Converting a Magellan track log to GPX:
31
+ ```ruby
32
+ if GPX::MagellanTrackLog::is_magellan_file?(filename)
33
+ GPX::MagellanTrackLog::convert_to_gpx(filename, "#{filename}.gpx")
34
+ end
35
+ ```
36
+
37
+ Exporting an ActiveRecord to GPXFile (as Waypoints)
38
+ ```ruby
39
+ #
40
+ # Our active record in this example is called stop
41
+ #
42
+
43
+ # models/stop.rb
44
+ class Stop < ActiveRecord::Base
45
+ # This model has the following attributes:
46
+ # name
47
+ # lat
48
+ # lon
49
+ # updated_at
50
+
51
+ def self.to_gpx
52
+ require 'GPX'
53
+ gpx = GPX::GPXFile.new
54
+ all.each do |stop|
55
+ gpx.waypoints << GPX::Waypoint.new({name: stop.name, lat: stop.lat, lon: stop.lon, time: stop.updated_at})
56
+ end
57
+ gpx.to_s
58
+ end
59
+ end # class
60
+
61
+
62
+ # controllers/stops.rb
63
+ def index
64
+ @stops = Stop.all
65
+ respond_to do |format|
66
+ format.html {render :index}
67
+ format.gpx { send_data @stops.to_gpx, filename: controller_name + '.gpx' }
68
+ end
69
+ end
70
+
71
+
72
+ # Add this line to config/initializers/mime_types.rb
73
+ Mime::Type.register "application/gpx+xml", :gpx
74
+
75
+
76
+ # To get the xml file:
77
+ # http://localhost:3000/stops.gpx
78
+ ```
79
+
80
+ You have a complete example on how to create a gpx file from scratch on `tests/output_text.rb`.
81
+
82
+
83
+ ## Notes
84
+
85
+ This library was written to bridge the gap between my Garmin Geko
86
+ and my website, WalkingBoss.org (RIP). For that reason, it has always been more of a
87
+ work-in-progress than an attempt at full GPX compliance. The track side of the
88
+ library has seen much more use than the route/waypoint side, so if you're doing
89
+ something with routes or waypoints, you may need to tweak some things.
90
+
91
+ Since this code uses XML to read an entire GPX file into memory, it is not
92
+ the fastest possible solution for working with GPX data, especially if you are
93
+ working with tracks from several days or weeks.
94
+
95
+ Finally, it should be noted that none of the distance/speed calculation or
96
+ crop/delete code has been tested under International Date Line-crossing
97
+ conditions. That particular part of the code will likely be unreliable if
98
+ you're zig-zagging across 180 degrees longitude routinely.
data/Rakefile CHANGED
@@ -5,6 +5,14 @@ require 'rdoc/task'
5
5
  desc "Default Task"
6
6
  task :default => [ :test ]
7
7
 
8
+ namespace :ci do
9
+ task :build do
10
+ puts "Creating tests/output directory..."
11
+ FileUtils.mkdir_p "tests/output"
12
+ Rake::Task[:test].invoke
13
+ end
14
+ end
15
+
8
16
  # Run the unit tests
9
17
  desc "Run all unit tests"
10
18
  Rake::TestTask.new("test") { |t|
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path('../../lib/gpx', __FILE__)
4
+
5
+ filename = ARGV[0]
6
+ gpx = GPX::GPXFile.new(:gpx_file => filename)
7
+ puts "read track with distance #{gpx.distance}"
8
+
9
+
10
+
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path('../../lib/gpx', __FILE__)
4
+ require 'optparse'
5
+
6
+
7
+ def str_to_int_or_time(str)
8
+ if str =~ /\A\d{10}\Z/
9
+ return Time.at(str.to_i)
10
+ elsif str =~ /\A\d+\Z/
11
+ return str.to_i
12
+ else
13
+ return DateTime.strptime(str, '%Y%m%d-%H:%M:%S').to_time
14
+ end
15
+ end
16
+
17
+
18
+ options = {}
19
+ OptionParser.new do |opts|
20
+ opts.banner = "Usage: smooth [options]"
21
+
22
+ opts.on("-i", "--input-file FILE", "Input file to read gpx data from (if omitted, data will be read from stdin)") do |v|
23
+ options[:infile] = v
24
+ end
25
+ opts.on("-o", "--output-file FILE", "Output file to write smoothed gpx data to (if omitted, data will be written to stdout)") do |v|
26
+ options[:outfile] = v
27
+ end
28
+ opts.on("-s", "--start-time [YYYYMMDD-HH:MM:SS|EPOCH|OFFSET]", "Start smoothing from time or offset specified (if omitted start from the start of the file)") do |v|
29
+ options[:start] = v
30
+ end
31
+ opts.on("-e", "--end-time [YYYYMMDD-HH:MM:SS|EPOCH|OFFSET]", "Finish smoothing from time or offset specified (if omitted finish at the end of the file)") do |v|
32
+ options[:end] = v
33
+ end
34
+ end.parse!
35
+
36
+
37
+ if options[:infile]
38
+ input = File.open(options[:infile])
39
+ else
40
+ input = $stdin
41
+ end
42
+
43
+ options[:start] = str_to_int_or_time(options[:start]) if options[:start]
44
+ options[:end] = str_to_int_or_time(options[:end]) if options[:end]
45
+
46
+ gpx = GPX::GPXFile.new(:gpx_data => input)
47
+ $stderr.puts "read track with distance #{gpx.distance}"
48
+
49
+ #1419062980
50
+ gpx.tracks.each do |track|
51
+ track.segments.each do |segment|
52
+ segment.smooth_location_by_average({:end => options[:end], :start => options[:start]})
53
+ end
54
+ end
55
+ gpx.recalculate_distance
56
+ $stderr.puts "smoothed distance #{gpx.distance}"
57
+
58
+ if options[:outfile]
59
+ gpx.write(options[:outfile], false)
60
+ else
61
+ puts gpx.to_s(false)
62
+ end
63
+
@@ -19,4 +19,6 @@ Gem::Specification.new do |s|
19
19
  s.homepage = "http://www.github.com/andrewhao/gpx"
20
20
  s.add_dependency 'rake'
21
21
  s.add_dependency 'nokogiri'
22
+ s.add_development_dependency 'bundler'
23
+ s.add_development_dependency 'minitest'
22
24
  end
data/lib/gpx.rb CHANGED
@@ -24,15 +24,15 @@
24
24
  require 'time'
25
25
  require 'nokogiri'
26
26
 
27
- require 'gpx/version'
27
+ require File.expand_path('../gpx/version', __FILE__)
28
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'
29
+ require File.expand_path('../gpx/gpx', __FILE__)
30
+ require File.expand_path('../gpx/gpx_file', __FILE__)
31
+ require File.expand_path('../gpx/bounds', __FILE__)
32
+ require File.expand_path('../gpx/track', __FILE__)
33
+ require File.expand_path('../gpx/route', __FILE__)
34
+ require File.expand_path('../gpx/segment', __FILE__)
35
+ require File.expand_path('../gpx/point', __FILE__)
36
+ require File.expand_path('../gpx/trackpoint', __FILE__)
37
+ require File.expand_path('../gpx/waypoint', __FILE__)
38
+ require File.expand_path('../gpx/magellan_track_log', __FILE__)
@@ -45,6 +45,8 @@ module GPX
45
45
  #
46
46
  def initialize(opts = {})
47
47
  @duration = 0
48
+ @attributes = {}
49
+ @namespace_defs = []
48
50
  if(opts[:gpx_file] or opts[:gpx_data])
49
51
  if opts[:gpx_file]
50
52
  gpx_file = opts[:gpx_file]
@@ -54,6 +56,13 @@ module GPX
54
56
  @xml = Nokogiri::XML(opts[:gpx_data])
55
57
  end
56
58
 
59
+ gpx_element = @xml.at('gpx')
60
+ @attributes = gpx_element.attributes
61
+ @namespace_defs = gpx_element.namespace_definitions
62
+ #$stderr.puts gpx_element.attributes.sort.inspect
63
+ #$stderr.puts @xmlns.inspect
64
+ #$stderr.puts @xsi.inspect
65
+ @version = gpx_element['version']
57
66
  reset_meta_data
58
67
  bounds_element = (@xml.at("metadata/bounds") rescue nil)
59
68
  if bounds_element
@@ -196,7 +205,7 @@ module GPX
196
205
  @time = Time.now if(@time.nil? or update_time)
197
206
  @name ||= File.basename(filename)
198
207
  doc = generate_xml_doc
199
- File.open(filename, 'w') { |f| f.write(doc.to_xml) }
208
+ File.open(filename, 'w+') { |f| f.write(doc.to_xml) }
200
209
  end
201
210
 
202
211
  def to_s(update_time = true)
@@ -209,17 +218,48 @@ module GPX
209
218
  "<#{self.class.name}:...>"
210
219
  end
211
220
 
221
+ def recalculate_distance
222
+ @distance = 0
223
+ @tracks.each do |track|
224
+ track.recalculate_distance
225
+ @distance += track.distance
226
+ end
227
+ end
228
+
212
229
  private
230
+ def attributes_and_nsdefs_as_gpx_attributes
231
+ #$stderr.puts @namespace_defs.inspect
232
+ gpx_header = {}
233
+ @attributes.each do |k,v|
234
+ k = v.namespace.prefix + ':' + k if v.namespace
235
+ gpx_header[k] = v.value
236
+ end
237
+
238
+ @namespace_defs.each do |nsd|
239
+ tag = 'xmlns'
240
+ if nsd.prefix
241
+ tag += ':' + nsd.prefix
242
+ end
243
+ gpx_header[tag] = nsd.href
244
+ end
245
+ return gpx_header
246
+ end
247
+
213
248
  def generate_xml_doc
214
249
  @version ||= '1.1'
215
250
  version_dir = version.gsub('.','/')
216
251
 
252
+ gpx_header = attributes_and_nsdefs_as_gpx_attributes
253
+
254
+ gpx_header['version'] = @version.to_s if !gpx_header['version']
255
+ gpx_header['creator'] = DEFAULT_CREATOR if !gpx_header['creator']
256
+ gpx_header['xsi:schemaLocation'] = "http://www.topografix.com/GPX/#{version_dir} http://www.topografix.com/GPX/#{version_dir}/gpx.xsd" if !gpx_header['xsi:schemaLocation']
257
+ gpx_header['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" if !gpx_header['xsi'] and !gpx_header['xmlns:xsi']
258
+
259
+ #$stderr.puts gpx_header.keys.inspect
260
+
217
261
  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") \
262
+ xml.gpx(gpx_header) \
223
263
  {
224
264
  # version 1.0 of the schema doesn't support the metadata element, so push them straight to the root 'gpx' element
225
265
  if (@version == '1.0') then
@@ -254,6 +294,7 @@ module GPX
254
294
  xml.trkpt(lat: p.lat, lon: p.lon) {
255
295
  xml.time p.time.xmlschema unless p.time.nil?
256
296
  xml.ele p.elevation unless p.elevation.nil?
297
+ xml << p.extensions.to_xml unless p.extensions.nil?
257
298
  }
258
299
  end
259
300
  }
@@ -263,6 +304,7 @@ module GPX
263
304
 
264
305
  waypoints.each do |w|
265
306
  xml.wpt(lat: w.lat, lon: w.lon) {
307
+ xml.time w.time.xmlschema unless w.time.nil?
266
308
  Waypoint::SUB_ELEMENTS.each do |sub_elem|
267
309
  xml.send(sub_elem, w.send(sub_elem)) if w.respond_to?(sub_elem) && !w.send(sub_elem).nil?
268
310
  end
@@ -24,7 +24,7 @@ module GPX
24
24
  # The base class for all points. Trackpoint and Waypoint both descend from this base class.
25
25
  class Point < Base
26
26
  D_TO_R = Math::PI/180.0;
27
- attr_accessor :lat, :lon, :time, :elevation, :gpx_file, :speed
27
+ attr_accessor :lat, :lon, :time, :elevation, :gpx_file, :speed, :extensions
28
28
 
29
29
  # When you need to manipulate individual points, you can create a Point
30
30
  # object with a latitude, a longitude, an elevation, and a time. In
@@ -40,12 +40,14 @@ module GPX
40
40
  @time = (Time.xmlschema(elem.at("time").inner_text) rescue nil)
41
41
  @elevation = elem.at("ele").inner_text.to_f unless elem.at("ele").nil?
42
42
  @speed = elem.at("speed").inner_text.to_f unless elem.at("speed").nil?
43
+ @extensions = elem.at("extensions") unless elem.at("extensions").nil?
43
44
  else
44
45
  @lat = opts[:lat]
45
46
  @lon = opts[:lon]
46
47
  @elevation = opts[:elevation]
47
48
  @time = opts[:time]
48
49
  @speed = opts[:speed]
50
+ @extensions = opts[:extensions]
49
51
  end
50
52
 
51
53
  end
@@ -48,23 +48,7 @@ module GPX
48
48
  if segment_element.is_a?(Nokogiri::XML::Node)
49
49
  segment_element.search("trkpt").each do |trkpt|
50
50
  pt = TrackPoint.new(:element => trkpt, :segment => self, :gpx_file => @gpx_file)
51
- unless pt.time.nil?
52
- @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time)
53
- @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time)
54
- end
55
- unless pt.elevation.nil?
56
- @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation)
57
- @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation)
58
- end
59
- @bounds.min_lat = pt.lat if pt.lat < @bounds.min_lat
60
- @bounds.min_lon = pt.lon if pt.lon < @bounds.min_lon
61
- @bounds.max_lat = pt.lat if pt.lat > @bounds.max_lat
62
- @bounds.max_lon = pt.lon if pt.lon > @bounds.max_lon
63
-
64
- @distance += haversine_distance(last_pt, pt) unless last_pt.nil?
65
-
66
- @points << pt
67
- last_pt = pt
51
+ append_point(pt)
68
52
  end
69
53
  end
70
54
  end
@@ -73,10 +57,14 @@ module GPX
73
57
  # Tack on a point to this Segment. All meta-data will be updated.
74
58
  def append_point(pt)
75
59
  last_pt = @points[-1]
76
- @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time)
77
- @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time)
78
- @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation)
79
- @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation)
60
+ unless pt.time.nil?
61
+ @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time)
62
+ @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time)
63
+ end
64
+ unless pt.elevation.nil?
65
+ @lowest_point = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation)
66
+ @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation)
67
+ end
80
68
  @bounds.min_lat = pt.lat if pt.lat < @bounds.min_lat
81
69
  @bounds.min_lon = pt.lon if pt.lon < @bounds.min_lon
82
70
  @bounds.max_lat = pt.lat if pt.lat > @bounds.max_lat
@@ -144,6 +132,64 @@ module GPX
144
132
  result
145
133
  end
146
134
 
135
+ def find_point_by_time_or_offset(indicator)
136
+ if indicator.nil?
137
+ return nil
138
+ elsif indicator.is_a?(Integer)
139
+ return closest_point(@earliest_point.time + indicator)
140
+ elsif(indicator.is_a?(Time))
141
+ return closest_point(indicator)
142
+ else
143
+ raise Exception, "find_end_point_by_time_or_offset requires an argument of type Time or Integer"
144
+ end
145
+ end
146
+
147
+ # smooths the location data in the segment (by recalculating the location as an average of 20 neighbouring points. Useful for removing noise from GPS traces.
148
+ def smooth_location_by_average(opts={})
149
+ seconds_either_side = opts[:averaging_window] || 20
150
+
151
+ #calculate the first and last points to which the smoothing should be applied
152
+ earliest = (find_point_by_time_or_offset(opts[:start]) || @earliest_point).time
153
+ latest = (find_point_by_time_or_offset(opts[:end]) || @latest_point).time
154
+
155
+ tmp_points = []
156
+
157
+ @points.each do |point|
158
+ if point.time > latest || point.time < earliest
159
+ tmp_points.push point #add the point unaltered
160
+ next
161
+ end
162
+ lat_av = 0.to_f
163
+ lon_av = 0.to_f
164
+ alt_av = 0.to_f
165
+ n = 0
166
+ # k ranges from the time of the current point +/- 20s
167
+ (-1*seconds_either_side..seconds_either_side).each do |k|
168
+ # find the point nearest to the time offset indicated by k
169
+ contributing_point = closest_point(point.time + k)
170
+ #sum up the contributions to the average
171
+ lat_av += contributing_point.lat
172
+ lon_av += contributing_point.lon
173
+ alt_av += contributing_point.elevation
174
+ n += 1
175
+ end
176
+ # calculate the averages
177
+ tmp_point = point.clone
178
+ tmp_point.lon = ((lon_av) / n).round(7)
179
+ tmp_point.elevation = ((alt_av) / n).round(2)
180
+ tmp_point.lat = ((lat_av) / n).round(7)
181
+ tmp_points.push tmp_point
182
+ end
183
+ last_pt = nil
184
+ @distance = 0
185
+ @points.clear
186
+ reset_meta_data
187
+ #now commit the averages back and recalculate the distances
188
+ tmp_points.each do |point|
189
+ append_point(point)
190
+ end
191
+ end
192
+
147
193
  protected
148
194
  def find_closest(pts, time)
149
195
  return pts.first if pts.size == 1
@@ -114,6 +114,13 @@ module GPX
114
114
  result
115
115
  end
116
116
 
117
+ def recalculate_distance
118
+ @distance = 0
119
+ @segments.each do |seg|
120
+ @distance += seg.distance
121
+ end
122
+ end
123
+
117
124
  protected
118
125
 
119
126
  def update_meta_data(seg)
@@ -30,6 +30,7 @@ module GPX
30
30
 
31
31
  attr_accessor :segment
32
32
 
33
+
33
34
  def initialize(opts = {})
34
35
  super(opts)
35
36
  @segment = opts[:segment]
@@ -54,5 +55,6 @@ module GPX
54
55
  def law_of_cosines_distance_from(p2)
55
56
  (Math.acos(Math.sin(latr)*Math.sin(p2.latr) + Math.cos(latr)*Math.cos(p2.latr)*Math.cos(p2.lonr-lonr)) * RADIUS)
56
57
  end
58
+
57
59
  end
58
60
  end
@@ -1,3 +1,3 @@
1
1
  module GPX
2
- VERSION = "0.7"
2
+ VERSION = "0.8"
3
3
  end
@@ -25,7 +25,7 @@ module GPX
25
25
  # not seen much use yet, since WalkingBoss does not use waypoints right now.
26
26
  class Waypoint < Point
27
27
 
28
- SUB_ELEMENTS = %w{ magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions ele}
28
+ SUB_ELEMENTS = %w{ele magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions}
29
29
 
30
30
  attr_reader :gpx_file
31
31
  SUB_ELEMENTS.each { |sub_el| attr_accessor sub_el.to_sym }
@@ -1,7 +1,7 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
- class GPX10Test < Test::Unit::TestCase
4
+ class GPX10Test < Minitest::Test
5
5
  GPX_FILE = File.join(File.dirname(__FILE__), "gpx_files/gpx10.gpx")
6
6
 
7
7
  def test_read
@@ -1,7 +1,7 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
- class GPXFileTest < Test::Unit::TestCase
4
+ class GPXFileTest < Minitest::Test
5
5
 
6
6
  ONE_TRACK_FILE = File.join(File.dirname(__FILE__), "gpx_files/one_track.gpx")
7
7
  WITH_OR_WITHOUT_ELEV_FILE = File.join(File.dirname(__FILE__), "gpx_files/with_or_without_elev.gpx")
@@ -17,4 +17,6 @@
17
17
  <wpt lat="39.999840" lon="-105.214696"><name><![CDATA[SBDR]]></name><sym>Waypoint</sym><ele>1612.965</ele></wpt>
18
18
  <wpt lat="39.989739" lon="-105.295285"><name><![CDATA[TO]]></name><sym>Waypoint</sym><ele>2163.556</ele></wpt>
19
19
  <wpt lat="40.035301" lon="-105.254443"><name><![CDATA[VICS]]></name><sym>Waypoint</sym><ele>1535.34</ele></wpt>
20
+ <wpt lat="8.992441" lon="-79.530365"><time>2015-01-01T12:12:12Z</time><name>loma la pava ???</name><desc>Mar 22, 2015, 10:46:17</desc>
21
+ </wpt>
20
22
  </gpx>
@@ -1,7 +1,7 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
- class MagellanTest < Test::Unit::TestCase
4
+ class MagellanTest < Minitest::Test
5
5
  MAGELLAN_TRACK_LOG = File.join(File.dirname(__FILE__), "gpx_files/magellan_track.log")
6
6
  GPX_FILE = File.join(File.dirname(__FILE__), "gpx_files/one_segment.gpx")
7
7
 
@@ -1,8 +1,8 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'fileutils'
3
3
  require 'gpx'
4
4
 
5
- class OutputTest < Test::Unit::TestCase
5
+ class OutputTest < Minitest::Test
6
6
 
7
7
  include GPX
8
8
 
@@ -42,7 +42,8 @@ class OutputTest < Test::Unit::TestCase
42
42
  {:lat => 25.061783, :lon => 121.640267, :name => 'GRMTWN', :sym => 'Waypoint', :ele => '38.09766'},
43
43
  {:lat => 39.999840, :lon => -105.214696, :name => 'SBDR', :sym => 'Waypoint', :ele => '1612.965'},
44
44
  {:lat => 39.989739, :lon => -105.295285, :name => 'TO', :sym => 'Waypoint', :ele => '2163.556'},
45
- {:lat => 40.035301, :lon => -105.254443, :name => 'VICS', :sym => 'Waypoint', :ele => '1535.34'}
45
+ {:lat => 40.035301, :lon => -105.254443, :name => 'VICS', :sym => 'Waypoint', :ele => '1535.34'},
46
+ {:lat => 40.035301, :lon => -105.254443, :name => 'TIMEDWPT', :sym => 'Waypoint', :ele => '1535.34', :time => Time.parse("2005-12-31T22:05:09Z")}
46
47
  ]
47
48
 
48
49
  waypoint_data.each do |wpt_hash|
@@ -1,7 +1,7 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
- class RouteTest < Test::Unit::TestCase
4
+ class RouteTest < Minitest::Test
5
5
 
6
6
  def test_read_routes
7
7
  gpx = GPX::GPXFile.new(:gpx_file => File.join(File.dirname(__FILE__), "gpx_files/routes.gpx"))
@@ -1,8 +1,9 @@
1
- require 'test/unit'
1
+ #require 'minitest/autorun'
2
+ require 'minitest/autorun'
2
3
  require 'yaml'
3
4
  require 'gpx'
4
5
 
5
- class SegmentTest < Test::Unit::TestCase
6
+ class SegmentTest < Minitest::Test
6
7
  ONE_SEGMENT = File.join(File.dirname(__FILE__), "gpx_files/one_segment.gpx")
7
8
 
8
9
  def setup
@@ -55,4 +56,35 @@ class SegmentTest < Test::Unit::TestCase
55
56
  assert_equal(39.188747, @segment.bounds.max_lat)
56
57
  assert_equal(-109.007978, @segment.bounds.max_lon)
57
58
  end
59
+
60
+ def test_segment_smooth
61
+ @segment.smooth_location_by_average
62
+ assert_equal(189, @segment.points.size)
63
+ assert_equal(1144433525, @segment.earliest_point.time.to_i)
64
+ assert_equal(1144437991, @segment.latest_point.time.to_i)
65
+ assert_equal(1342.58, @segment.lowest_point.elevation)
66
+ assert_equal(1479.09, @segment.highest_point.elevation)
67
+ assert_in_delta(6.458085658, @segment.distance, 0.001)
68
+ end
69
+
70
+ def test_segment_smooth_offset
71
+ @segment.smooth_location_by_average({:start => 1000, :end => 2000})
72
+ assert_equal(189, @segment.points.size)
73
+ assert_equal(1144433525, @segment.earliest_point.time.to_i)
74
+ assert_equal(1144437991, @segment.latest_point.time.to_i)
75
+ assert_equal(1334.447, @segment.lowest_point.elevation)
76
+ assert_equal(1480.087, @segment.highest_point.elevation)
77
+ assert_in_delta(6.900813095, @segment.distance, 0.001)
78
+ end
79
+
80
+ def test_segment_smooth_absolute
81
+ @segment.smooth_location_by_average({:start => Time.at(1144434520), :end => Time.at(1144435520)})
82
+ assert_equal(189, @segment.points.size)
83
+ assert_equal(1144433525, @segment.earliest_point.time.to_i)
84
+ assert_equal(1144437991, @segment.latest_point.time.to_i)
85
+ assert_equal(1334.447, @segment.lowest_point.elevation)
86
+ assert_equal(1480.087, @segment.highest_point.elevation)
87
+ assert_in_delta(6.900813095, @segment.distance, 0.001)
88
+ end
89
+
58
90
  end
@@ -1,7 +1,7 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
- class TrackFileTest < Test::Unit::TestCase
4
+ class TrackFileTest < Minitest::Test
5
5
  TRACK_FILE = File.join(File.dirname(__FILE__), "gpx_files/tracks.gpx")
6
6
  OTHER_TRACK_FILE = File.join(File.dirname(__FILE__), "gpx_files/arches.gpx")
7
7
 
@@ -1,7 +1,7 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
- class TrackPointTest < Test::Unit::TestCase
4
+ class TrackPointTest < Minitest::Test
5
5
  def setup
6
6
  @point1 = GPX::TrackPoint.new({
7
7
  :lat => 37.7985474,
@@ -1,7 +1,7 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
- class TrackTest < Test::Unit::TestCase
4
+ class TrackTest < Minitest::Test
5
5
  ONE_TRACK = File.join(File.dirname(__FILE__), "gpx_files/one_track.gpx")
6
6
 
7
7
  def setup
@@ -1,12 +1,12 @@
1
- require 'test/unit'
1
+ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
- class WaypointTest < Test::Unit::TestCase
4
+ class WaypointTest < Minitest::Test
5
5
 
6
6
  def test_read_waypoints
7
7
 
8
8
  gpx = GPX::GPXFile.new(:gpx_file => File.join(File.dirname(__FILE__), "gpx_files/waypoints.gpx"))
9
- assert_equal(17, gpx.waypoints.size)
9
+ assert_equal(18, gpx.waypoints.size)
10
10
 
11
11
  # First Waypoint
12
12
  # <wpt lat="40.035557" lon="-105.248268">
@@ -38,7 +38,11 @@ class WaypointTest < Test::Unit::TestCase
38
38
  assert_equal('002', second_wpt.name)
39
39
  assert_equal('Waypoint', second_wpt.sym)
40
40
  assert_equal(1955.192, second_wpt.elevation)
41
-
41
+
42
+ # test loads time properly in waypoint
43
+ time_wpt = gpx.waypoints[17]
44
+ assert_equal(Time.xmlschema("2015-01-01T12:12:12Z"), time_wpt.time)
45
+
42
46
  end
43
47
  end
44
48
 
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: andrewhao-gpx
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.7'
5
- prerelease:
4
+ version: '0.8'
6
5
  platform: ruby
7
6
  authors:
8
7
  - Guillaume Dott
@@ -11,38 +10,62 @@ authors:
11
10
  autorequire:
12
11
  bindir: bin
13
12
  cert_chain: []
14
- date: 2014-11-08 00:00:00.000000000 Z
13
+ date: 2015-04-20 00:00:00.000000000 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: rake
18
17
  requirement: !ruby/object:Gem::Requirement
19
- none: false
20
18
  requirements:
21
- - - ! '>='
19
+ - - ">="
22
20
  - !ruby/object:Gem::Version
23
21
  version: '0'
24
22
  type: :runtime
25
23
  prerelease: false
26
24
  version_requirements: !ruby/object:Gem::Requirement
27
- none: false
28
25
  requirements:
29
- - - ! '>='
26
+ - - ">="
30
27
  - !ruby/object:Gem::Version
31
28
  version: '0'
32
29
  - !ruby/object:Gem::Dependency
33
30
  name: nokogiri
34
31
  requirement: !ruby/object:Gem::Requirement
35
- none: false
36
32
  requirements:
37
- - - ! '>='
33
+ - - ">="
38
34
  - !ruby/object:Gem::Version
39
35
  version: '0'
40
36
  type: :runtime
41
37
  prerelease: false
42
38
  version_requirements: !ruby/object:Gem::Requirement
43
- none: false
44
39
  requirements:
45
- - - ! '>='
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: bundler
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: minitest
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
46
69
  - !ruby/object:Gem::Version
47
70
  version: '0'
48
71
  description: A basic API for reading and writing GPX files.
@@ -54,11 +77,14 @@ executables: []
54
77
  extensions: []
55
78
  extra_rdoc_files: []
56
79
  files:
57
- - .gitignore
80
+ - ".gitignore"
81
+ - ".travis.yml"
58
82
  - ChangeLog
59
83
  - Gemfile
60
- - README.rdoc
84
+ - README.md
61
85
  - Rakefile
86
+ - bin/gpx_distance
87
+ - bin/gpx_smooth
62
88
  - gpx.gemspec
63
89
  - lib/gpx.rb
64
90
  - lib/gpx/bounds.rb
@@ -95,32 +121,26 @@ files:
95
121
  - tests/waypoint_test.rb
96
122
  homepage: http://www.github.com/andrewhao/gpx
97
123
  licenses: []
124
+ metadata: {}
98
125
  post_install_message:
99
126
  rdoc_options: []
100
127
  require_paths:
101
128
  - lib
102
129
  required_ruby_version: !ruby/object:Gem::Requirement
103
- none: false
104
130
  requirements:
105
- - - ! '>='
131
+ - - ">="
106
132
  - !ruby/object:Gem::Version
107
133
  version: '0'
108
- segments:
109
- - 0
110
- hash: -3842906676439924695
111
134
  required_rubygems_version: !ruby/object:Gem::Requirement
112
- none: false
113
135
  requirements:
114
- - - ! '>='
136
+ - - ">="
115
137
  - !ruby/object:Gem::Version
116
138
  version: '0'
117
- segments:
118
- - 0
119
- hash: -3842906676439924695
120
139
  requirements: []
121
140
  rubyforge_project:
122
- rubygems_version: 1.8.23
141
+ rubygems_version: 2.4.5
123
142
  signing_key:
124
- specification_version: 3
143
+ specification_version: 4
125
144
  summary: A basic API for reading and writing GPX files.
126
145
  test_files: []
146
+ has_rdoc: true
@@ -1,46 +0,0 @@
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.