andrewhao-gpx 0.7 → 0.8
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.
- checksums.yaml +7 -0
- data/.travis.yml +8 -0
- data/README.md +98 -0
- data/Rakefile +8 -0
- data/bin/gpx_distance +10 -0
- data/bin/gpx_smooth +63 -0
- data/gpx.gemspec +2 -0
- data/lib/gpx.rb +11 -11
- data/lib/gpx/gpx_file.rb +48 -6
- data/lib/gpx/point.rb +3 -1
- data/lib/gpx/segment.rb +67 -21
- data/lib/gpx/track.rb +7 -0
- data/lib/gpx/trackpoint.rb +2 -0
- data/lib/gpx/version.rb +1 -1
- data/lib/gpx/waypoint.rb +1 -1
- data/tests/gpx10_test.rb +2 -2
- data/tests/gpx_file_test.rb +2 -2
- data/tests/gpx_files/waypoints.gpx +2 -0
- data/tests/magellan_test.rb +2 -2
- data/tests/output_test.rb +4 -3
- data/tests/route_test.rb +2 -2
- data/tests/segment_test.rb +34 -2
- data/tests/track_file_test.rb +2 -2
- data/tests/track_point_test.rb +2 -2
- data/tests/track_test.rb +2 -2
- data/tests/waypoint_test.rb +8 -4
- metadata +45 -25
- data/README.rdoc +0 -46
checksums.yaml
ADDED
@@ -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
|
data/.travis.yml
ADDED
data/README.md
ADDED
@@ -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
|
+
[](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|
|
data/bin/gpx_distance
ADDED
data/bin/gpx_smooth
ADDED
@@ -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
|
+
|
data/gpx.gemspec
CHANGED
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__)
|
data/lib/gpx/gpx_file.rb
CHANGED
@@ -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
|
data/lib/gpx/point.rb
CHANGED
@@ -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
|
data/lib/gpx/segment.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
data/lib/gpx/track.rb
CHANGED
data/lib/gpx/trackpoint.rb
CHANGED
@@ -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
|
data/lib/gpx/version.rb
CHANGED
data/lib/gpx/waypoint.rb
CHANGED
@@ -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
|
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 }
|
data/tests/gpx10_test.rb
CHANGED
data/tests/gpx_file_test.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'gpx'
|
3
3
|
|
4
|
-
class GPXFileTest < Test
|
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>
|
data/tests/magellan_test.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'gpx'
|
3
3
|
|
4
|
-
class MagellanTest < Test
|
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
|
|
data/tests/output_test.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'fileutils'
|
3
3
|
require 'gpx'
|
4
4
|
|
5
|
-
class OutputTest < Test
|
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|
|
data/tests/route_test.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'gpx'
|
3
3
|
|
4
|
-
class RouteTest < Test
|
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"))
|
data/tests/segment_test.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
require '
|
1
|
+
#require 'minitest/autorun'
|
2
|
+
require 'minitest/autorun'
|
2
3
|
require 'yaml'
|
3
4
|
require 'gpx'
|
4
5
|
|
5
|
-
class SegmentTest < Test
|
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
|
data/tests/track_file_test.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'gpx'
|
3
3
|
|
4
|
-
class TrackFileTest < Test
|
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
|
|
data/tests/track_point_test.rb
CHANGED
data/tests/track_test.rb
CHANGED
data/tests/waypoint_test.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'gpx'
|
3
3
|
|
4
|
-
class WaypointTest < Test
|
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(
|
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.
|
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:
|
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.
|
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:
|
141
|
+
rubygems_version: 2.4.5
|
123
142
|
signing_key:
|
124
|
-
specification_version:
|
143
|
+
specification_version: 4
|
125
144
|
summary: A basic API for reading and writing GPX files.
|
126
145
|
test_files: []
|
146
|
+
has_rdoc: true
|
data/README.rdoc
DELETED
@@ -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.
|