gpx 0.9.0 → 1.0.0
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 +5 -5
- data/.gitignore +4 -0
- data/.rubocop.yml +165 -0
- data/.travis.yml +3 -3
- data/CHANGELOG.md +8 -0
- data/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/Rakefile +15 -12
- data/bin/gpx_distance +3 -6
- data/bin/gpx_smooth +20 -24
- data/gpx.gemspec +12 -11
- data/lib/gpx.rb +11 -34
- data/lib/gpx/bounds.rb +10 -31
- data/lib/gpx/gpx.rb +2 -26
- data/lib/gpx/gpx_file.rb +112 -108
- data/lib/gpx/magellan_track_log.rb +32 -66
- data/lib/gpx/point.rb +18 -35
- data/lib/gpx/route.rb +7 -31
- data/lib/gpx/segment.rb +56 -88
- data/lib/gpx/track.rb +31 -42
- data/lib/gpx/track_point.rb +30 -0
- data/lib/gpx/version.rb +1 -1
- data/lib/gpx/waypoint.rb +7 -34
- data/tests/gpx10_test.rb +5 -6
- data/tests/gpx_file_test.rb +29 -19
- data/tests/gpx_files/with_empty_tracks.gpx +72 -0
- data/tests/magellan_test.rb +10 -11
- data/tests/output_test.rb +92 -95
- data/tests/route_test.rb +25 -32
- data/tests/segment_test.rb +93 -94
- data/tests/track_file_test.rb +47 -70
- data/tests/track_point_test.rb +20 -11
- data/tests/track_test.rb +71 -61
- data/tests/waypoint_test.rb +44 -48
- metadata +29 -13
- data/lib/gpx/trackpoint.rb +0 -60
@@ -1,30 +1,8 @@
|
|
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
1
|
module GPX
|
24
2
|
# This class will parse the lat/lon and time data from a Magellan track log,
|
25
3
|
# which is a NMEA formatted CSV list of points.
|
26
4
|
class MagellanTrackLog
|
27
|
-
#PMGNTRK
|
5
|
+
# PMGNTRK
|
28
6
|
# This message is to be used to transmit Track information (basically a list of previous position fixes)
|
29
7
|
# which is often displayed on the plotter or map screen of the unit. The first field in this message
|
30
8
|
# is the Latitude, followed by N or S. The next field is the Longitude followed by E or W. The next
|
@@ -35,98 +13,86 @@ module GPX
|
|
35
13
|
# of the fix. Note that this field is (and its preceding comma) is only produced by the unit when the
|
36
14
|
# command PMGNCMD,TRACK,2 is given. It is not present when a simple command of PMGNCMD,TRACK is issued.
|
37
15
|
|
38
|
-
#NOTE: The Latitude and Longitude Fields are shown as having two decimal
|
16
|
+
# NOTE: The Latitude and Longitude Fields are shown as having two decimal
|
39
17
|
# places. As many additional decimal places may be added as long as the total
|
40
18
|
# length of the message does not exceed 82 bytes.
|
41
19
|
|
42
20
|
# $PMGNTRK,llll.ll,a,yyyyy.yy,a,xxxxx,a,hhmmss.ss,A,c----c,ddmmyy*hh<CR><LF>
|
43
21
|
require 'csv'
|
44
22
|
|
45
|
-
LAT
|
46
|
-
LAT_HEMI
|
47
|
-
LON
|
48
|
-
LON_HEMI
|
49
|
-
ELE
|
23
|
+
LAT = 1
|
24
|
+
LAT_HEMI = 2
|
25
|
+
LON = 3
|
26
|
+
LON_HEMI = 4
|
27
|
+
ELE = 5
|
50
28
|
ELE_UNITS = 6
|
51
|
-
TIME
|
29
|
+
TIME = 7
|
52
30
|
INVALID_FLAG = 8
|
53
|
-
DATE
|
31
|
+
DATE = 10
|
54
32
|
|
55
33
|
FEET_TO_METERS = 0.3048
|
56
34
|
|
57
35
|
class << self
|
58
|
-
|
59
36
|
# Takes the name of a magellan file, converts the contents to GPX, and
|
60
37
|
# writes the result to gpx_filename.
|
61
38
|
def convert_to_gpx(magellan_filename, gpx_filename)
|
62
|
-
|
63
39
|
segment = Segment.new
|
64
40
|
|
65
|
-
CSV.open(magellan_filename,
|
66
|
-
next if(row.size < 10
|
41
|
+
CSV.open(magellan_filename, 'r').each do |row|
|
42
|
+
next if (row.size < 10) || (row[INVALID_FLAG] == 'V')
|
67
43
|
|
68
|
-
lat_deg
|
69
|
-
lat_min
|
44
|
+
lat_deg = row[LAT][0..1]
|
45
|
+
lat_min = row[LAT][2...-1]
|
70
46
|
lat_hemi = row[LAT_HEMI]
|
71
47
|
|
72
48
|
lat = lat_deg.to_f + (lat_min.to_f / 60.0)
|
73
|
-
lat =
|
49
|
+
lat = -lat if lat_hemi == 'S'
|
74
50
|
|
75
|
-
lon_deg
|
76
|
-
lon_min
|
51
|
+
lon_deg = row[LON][0..2]
|
52
|
+
lon_min = row[LON][3..-1]
|
77
53
|
lon_hemi = row[LON_HEMI]
|
78
54
|
|
79
55
|
lon = lon_deg.to_f + (lon_min.to_f / 60.0)
|
80
|
-
lon =
|
81
|
-
|
56
|
+
lon = -lon if lon_hemi == 'W'
|
82
57
|
|
83
58
|
ele = row[ELE]
|
84
59
|
ele_units = row[ELE_UNITS]
|
85
60
|
ele = ele.to_f
|
86
|
-
if
|
87
|
-
ele *= FEET_TO_METERS
|
88
|
-
end
|
61
|
+
ele *= FEET_TO_METERS if ele_units == 'F'
|
89
62
|
|
90
|
-
hrs
|
63
|
+
hrs = row[TIME][0..1].to_i
|
91
64
|
mins = row[TIME][2..3].to_i
|
92
65
|
secs = row[TIME][4..5].to_i
|
93
|
-
day
|
94
|
-
mon
|
95
|
-
yr
|
66
|
+
day = row[DATE][0..1].to_i
|
67
|
+
mon = row[DATE][2..3].to_i
|
68
|
+
yr = 2000 + row[DATE][4..5].to_i
|
96
69
|
|
97
70
|
time = Time.gm(yr, mon, day, hrs, mins, secs)
|
98
71
|
|
99
|
-
#must create point
|
100
|
-
pt = TrackPoint.new(:
|
72
|
+
# must create point
|
73
|
+
pt = TrackPoint.new(lat: lat, lon: lon, time: time, elevation: ele)
|
101
74
|
segment.append_point(pt)
|
102
|
-
|
103
75
|
end
|
104
76
|
|
105
77
|
trk = Track.new
|
106
78
|
trk.append_segment(segment)
|
107
|
-
gpx_file = GPXFile.new(:
|
79
|
+
gpx_file = GPXFile.new(tracks: [trk])
|
108
80
|
gpx_file.write(gpx_filename)
|
109
|
-
|
110
81
|
end
|
111
82
|
|
112
83
|
# Tests to see if the given file is a magellan NMEA track log.
|
113
|
-
def
|
84
|
+
def magellan_file?(filename)
|
114
85
|
i = 0
|
115
|
-
File.open(filename,
|
86
|
+
File.open(filename, 'r') do |f|
|
116
87
|
f.each do |line|
|
117
|
-
i +=
|
118
|
-
if line =~ /^\$PMGNTRK/
|
119
|
-
|
120
|
-
|
121
|
-
return false
|
122
|
-
elsif(i > 10)
|
123
|
-
return false
|
124
|
-
end
|
88
|
+
i += 1
|
89
|
+
return true if line =~ /^\$PMGNTRK/
|
90
|
+
return false if line =~ /<\?xml/
|
91
|
+
return false if i > 10
|
125
92
|
end
|
126
93
|
end
|
127
|
-
|
94
|
+
false
|
128
95
|
end
|
129
96
|
end
|
130
|
-
|
131
97
|
end
|
132
98
|
end
|
data/lib/gpx/point.rb
CHANGED
@@ -1,46 +1,31 @@
|
|
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
1
|
module GPX
|
24
2
|
# The base class for all points. Trackpoint and Waypoint both descend from this base class.
|
25
3
|
class Point < Base
|
26
|
-
D_TO_R = Math::PI/180.0
|
27
|
-
attr_accessor :
|
4
|
+
D_TO_R = Math::PI / 180.0
|
5
|
+
attr_accessor :time, :elevation, :gpx_file, :speed, :extensions
|
6
|
+
attr_reader :lat, :lon
|
28
7
|
|
29
8
|
# When you need to manipulate individual points, you can create a Point
|
30
9
|
# object with a latitude, a longitude, an elevation, and a time. In
|
31
10
|
# addition, you can pass an XML element to this initializer, and the
|
32
11
|
# relevant info will be parsed out.
|
33
|
-
def initialize(opts = {:
|
12
|
+
def initialize(opts = { lat: 0.0, lon: 0.0, elevation: 0.0, time: Time.now })
|
34
13
|
@gpx_file = opts[:gpx_file]
|
35
|
-
if
|
14
|
+
if opts[:element]
|
36
15
|
elem = opts[:element]
|
37
|
-
@lat
|
38
|
-
@
|
39
|
-
|
40
|
-
@
|
41
|
-
|
42
|
-
@
|
43
|
-
|
16
|
+
@lat = elem['lat'].to_f
|
17
|
+
@lon = elem['lon'].to_f
|
18
|
+
@latr = (D_TO_R * @lat)
|
19
|
+
@lonr = (D_TO_R * @lon)
|
20
|
+
# '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
|
21
|
+
@time = (begin
|
22
|
+
Time.xmlschema(elem.at('time').inner_text)
|
23
|
+
rescue StandardError
|
24
|
+
nil
|
25
|
+
end)
|
26
|
+
@elevation = elem.at('ele').inner_text.to_f unless elem.at('ele').nil?
|
27
|
+
@speed = elem.at('speed').inner_text.to_f unless elem.at('speed').nil?
|
28
|
+
@extensions = elem.at('extensions') unless elem.at('extensions').nil?
|
44
29
|
else
|
45
30
|
@lat = opts[:lat]
|
46
31
|
@lon = opts[:lon]
|
@@ -49,10 +34,8 @@ module GPX
|
|
49
34
|
@speed = opts[:speed]
|
50
35
|
@extensions = opts[:extensions]
|
51
36
|
end
|
52
|
-
|
53
37
|
end
|
54
38
|
|
55
|
-
|
56
39
|
# Returns the latitude and longitude (in that order), separated by the
|
57
40
|
# given delimeter. This is useful for passing a point into another API
|
58
41
|
# (i.e. the Google Maps javascript API).
|
data/lib/gpx/route.rb
CHANGED
@@ -1,58 +1,34 @@
|
|
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
1
|
module GPX
|
24
2
|
# A Route in GPX is very similar to a Track, but it is created by a user
|
25
3
|
# from a series of Waypoints, whereas a Track is created by the GPS device
|
26
4
|
# automatically logging your progress at regular intervals.
|
27
5
|
class Route < Base
|
28
|
-
|
29
6
|
attr_accessor :points, :name, :gpx_file
|
30
7
|
|
31
8
|
# Initialize a Route from a XML::Node.
|
32
9
|
def initialize(opts = {})
|
33
|
-
if
|
10
|
+
if opts[:gpx_file] && opts[:element]
|
34
11
|
rte_element = opts[:element]
|
35
12
|
@gpx_file = opts[:gpx_file]
|
36
|
-
@name = rte_element.at(
|
13
|
+
@name = rte_element.at('name').inner_text
|
37
14
|
@points = []
|
38
|
-
rte_element.search(
|
39
|
-
@points << Point.new(:
|
15
|
+
rte_element.search('rtept').each do |point|
|
16
|
+
@points << Point.new(element: point, gpx_file: @gpx_file)
|
40
17
|
end
|
41
18
|
else
|
42
|
-
@points = (opts[:points]
|
19
|
+
@points = (opts[:points] || [])
|
43
20
|
@name = (opts[:name])
|
44
21
|
end
|
45
|
-
|
46
22
|
end
|
47
23
|
|
48
24
|
# Delete points outside of a given area.
|
49
25
|
def crop(area)
|
50
|
-
points.delete_if{ |pt|
|
26
|
+
points.delete_if { |pt| !area.contains? pt }
|
51
27
|
end
|
52
28
|
|
53
29
|
# Delete points within the given area.
|
54
30
|
def delete_area(area)
|
55
|
-
points.delete_if{ |pt| area.contains? pt }
|
31
|
+
points.delete_if { |pt| area.contains? pt }
|
56
32
|
end
|
57
33
|
end
|
58
34
|
end
|
data/lib/gpx/segment.rb
CHANGED
@@ -1,32 +1,9 @@
|
|
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
1
|
module GPX
|
24
2
|
# A segment is the basic container in a GPX file. A Segment contains points
|
25
3
|
# (in this lib, they're called TrackPoints). A Track contains Segments. An
|
26
4
|
# instance of Segment knows its highest point, lowest point, earliest and
|
27
5
|
# latest points, distance, and bounds.
|
28
6
|
class Segment < Base
|
29
|
-
|
30
7
|
attr_reader :earliest_point, :latest_point, :bounds, :highest_point, :lowest_point, :distance, :duration
|
31
8
|
attr_accessor :points, :track
|
32
9
|
|
@@ -43,15 +20,12 @@ module GPX
|
|
43
20
|
@distance = 0.0
|
44
21
|
@duration = 0.0
|
45
22
|
@bounds = Bounds.new
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
append_point(pt)
|
53
|
-
end
|
54
|
-
end
|
23
|
+
|
24
|
+
segment_element = opts[:element]
|
25
|
+
return unless segment_element && segment_element.is_a?(Nokogiri::XML::Node)
|
26
|
+
segment_element.search('trkpt').each do |trkpt|
|
27
|
+
pt = TrackPoint.new(element: trkpt, segment: self, gpx_file: @gpx_file)
|
28
|
+
append_point(pt)
|
55
29
|
end
|
56
30
|
end
|
57
31
|
|
@@ -59,8 +33,8 @@ module GPX
|
|
59
33
|
def append_point(pt)
|
60
34
|
last_pt = @points[-1]
|
61
35
|
if pt.time
|
62
|
-
@earliest_point = pt if
|
63
|
-
@latest_point
|
36
|
+
@earliest_point = pt if @earliest_point.nil? || (pt.time < @earliest_point.time)
|
37
|
+
@latest_point = pt if @latest_point.nil? || (pt.time > @latest_point.time)
|
64
38
|
else
|
65
39
|
# when no time information in data, we consider the points are ordered
|
66
40
|
@earliest_point = @points[0]
|
@@ -68,8 +42,8 @@ module GPX
|
|
68
42
|
end
|
69
43
|
|
70
44
|
if pt.elevation
|
71
|
-
@lowest_point
|
72
|
-
@highest_point
|
45
|
+
@lowest_point = pt if @lowest_point.nil? || (pt.elevation < @lowest_point.elevation)
|
46
|
+
@highest_point = pt if @highest_point.nil? || (pt.elevation > @highest_point.elevation)
|
73
47
|
end
|
74
48
|
@bounds.min_lat = pt.lat if pt.lat < @bounds.min_lat
|
75
49
|
@bounds.min_lon = pt.lon if pt.lon < @bounds.min_lon
|
@@ -77,14 +51,16 @@ module GPX
|
|
77
51
|
@bounds.max_lon = pt.lon if pt.lon > @bounds.max_lon
|
78
52
|
if last_pt
|
79
53
|
@distance += haversine_distance(last_pt, pt)
|
80
|
-
@duration += pt.time - last_pt.time if pt.time
|
54
|
+
@duration += pt.time - last_pt.time if pt.time && last_pt.time
|
81
55
|
end
|
82
56
|
@points << pt
|
83
57
|
end
|
84
58
|
|
85
59
|
# Returns true if the given time is within this Segment.
|
86
60
|
def contains_time?(time)
|
87
|
-
(time >= @earliest_point.time
|
61
|
+
((time >= @earliest_point.time) && (time <= @latest_point.time))
|
62
|
+
rescue StandardError
|
63
|
+
false
|
88
64
|
end
|
89
65
|
|
90
66
|
# Finds the closest point in time to the passed-in time argument. Useful
|
@@ -97,12 +73,12 @@ module GPX
|
|
97
73
|
# Deletes all points within this Segment that lie outside of the given
|
98
74
|
# area (which should be a Bounds object).
|
99
75
|
def crop(area)
|
100
|
-
delete_if { |pt|
|
76
|
+
delete_if { |pt| !area.contains?(pt) }
|
101
77
|
end
|
102
78
|
|
103
79
|
# Deletes all points in this Segment that lie within the given area.
|
104
80
|
def delete_area(area)
|
105
|
-
delete_if{ |pt| area.contains?(pt) }
|
81
|
+
delete_if { |pt| area.contains?(pt) }
|
106
82
|
end
|
107
83
|
|
108
84
|
# A handy method that deletes points based on a block that is passed in.
|
@@ -114,18 +90,17 @@ module GPX
|
|
114
90
|
keep_points = []
|
115
91
|
last_pt = nil
|
116
92
|
points.each do |pt|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
end
|
93
|
+
next if yield(pt)
|
94
|
+
keep_points << pt
|
95
|
+
update_meta_data(pt, last_pt)
|
96
|
+
last_pt = pt
|
122
97
|
end
|
123
98
|
@points = keep_points
|
124
99
|
end
|
125
100
|
|
126
101
|
# Returns true if this Segment has no points.
|
127
102
|
def empty?
|
128
|
-
(points.nil?
|
103
|
+
(points.nil? || points.empty?)
|
129
104
|
end
|
130
105
|
|
131
106
|
# Prints out a nice summary of this Segment.
|
@@ -133,31 +108,31 @@ module GPX
|
|
133
108
|
result = "Track Segment\n"
|
134
109
|
result << "\tSize: #{points.size} points\n"
|
135
110
|
result << "\tDistance: #{distance} km\n"
|
136
|
-
result << "\tEarliest Point: #{earliest_point.time
|
137
|
-
result << "\tLatest Point: #{latest_point.time
|
111
|
+
result << "\tEarliest Point: #{earliest_point.time} \n"
|
112
|
+
result << "\tLatest Point: #{latest_point.time} \n"
|
138
113
|
result << "\tLowest Point: #{lowest_point.elevation} \n"
|
139
114
|
result << "\tHighest Point: #{highest_point.elevation}\n "
|
140
|
-
result << "\tBounds: #{bounds
|
115
|
+
result << "\tBounds: #{bounds}"
|
141
116
|
result
|
142
117
|
end
|
143
118
|
|
144
119
|
def find_point_by_time_or_offset(indicator)
|
145
120
|
if indicator.nil?
|
146
|
-
|
121
|
+
nil
|
147
122
|
elsif indicator.is_a?(Integer)
|
148
|
-
|
149
|
-
elsif
|
150
|
-
|
123
|
+
closest_point(@earliest_point.time + indicator)
|
124
|
+
elsif indicator.is_a?(Time)
|
125
|
+
closest_point(indicator)
|
151
126
|
else
|
152
|
-
raise Exception,
|
127
|
+
raise Exception, 'find_end_point_by_time_or_offset requires an argument of type Time or Integer'
|
153
128
|
end
|
154
129
|
end
|
155
|
-
|
130
|
+
|
156
131
|
# 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.
|
157
|
-
def smooth_location_by_average(opts={})
|
132
|
+
def smooth_location_by_average(opts = {})
|
158
133
|
seconds_either_side = opts[:averaging_window] || 20
|
159
134
|
|
160
|
-
#calculate the first and last points to which the smoothing should be applied
|
135
|
+
# calculate the first and last points to which the smoothing should be applied
|
161
136
|
earliest = (find_point_by_time_or_offset(opts[:start]) || @earliest_point).time
|
162
137
|
latest = (find_point_by_time_or_offset(opts[:end]) || @latest_point).time
|
163
138
|
|
@@ -165,18 +140,18 @@ module GPX
|
|
165
140
|
|
166
141
|
@points.each do |point|
|
167
142
|
if point.time > latest || point.time < earliest
|
168
|
-
tmp_points.push point #add the point unaltered
|
169
|
-
next
|
143
|
+
tmp_points.push point # add the point unaltered
|
144
|
+
next
|
170
145
|
end
|
171
146
|
lat_av = 0.to_f
|
172
147
|
lon_av = 0.to_f
|
173
148
|
alt_av = 0.to_f
|
174
149
|
n = 0
|
175
|
-
# k ranges from the time of the current point +/- 20s
|
176
|
-
(-1*seconds_either_side..seconds_either_side).each do |k|
|
150
|
+
# k ranges from the time of the current point +/- 20s
|
151
|
+
(-1 * seconds_either_side..seconds_either_side).each do |k|
|
177
152
|
# find the point nearest to the time offset indicated by k
|
178
153
|
contributing_point = closest_point(point.time + k)
|
179
|
-
#sum up the contributions to the average
|
154
|
+
# sum up the contributions to the average
|
180
155
|
lat_av += contributing_point.lat
|
181
156
|
lon_av += contributing_point.lon
|
182
157
|
alt_av += contributing_point.elevation
|
@@ -184,39 +159,39 @@ module GPX
|
|
184
159
|
end
|
185
160
|
# calculate the averages
|
186
161
|
tmp_point = point.clone
|
187
|
-
tmp_point.lon = (
|
188
|
-
tmp_point.elevation = (
|
189
|
-
tmp_point.lat = (
|
162
|
+
tmp_point.lon = (lon_av / n).round(7)
|
163
|
+
tmp_point.elevation = (alt_av / n).round(2)
|
164
|
+
tmp_point.lat = (lat_av / n).round(7)
|
190
165
|
tmp_points.push tmp_point
|
191
166
|
end
|
192
|
-
last_pt = nil
|
193
167
|
@points.clear
|
194
168
|
reset_meta_data
|
195
|
-
#now commit the averages back and recalculate the distances
|
169
|
+
# now commit the averages back and recalculate the distances
|
196
170
|
tmp_points.each do |point|
|
197
171
|
append_point(point)
|
198
172
|
end
|
199
173
|
end
|
200
174
|
|
201
175
|
protected
|
176
|
+
|
177
|
+
# rubocop:disable Style/GuardClause
|
202
178
|
def find_closest(pts, time)
|
203
179
|
return pts.first if pts.size == 1
|
204
|
-
midpoint = pts.size/2
|
180
|
+
midpoint = pts.size / 2
|
205
181
|
if pts.size == 2
|
206
182
|
diff_1 = pts[0].time - time
|
207
183
|
diff_2 = pts[1].time - time
|
208
184
|
return (diff_1 < diff_2 ? pts[0] : pts[1])
|
209
185
|
end
|
210
|
-
if time >= pts[midpoint].time
|
211
|
-
|
186
|
+
if (time >= pts[midpoint].time) && (time <= pts[midpoint + 1].time)
|
212
187
|
return pts[midpoint]
|
213
|
-
|
214
|
-
elsif(time <= pts[midpoint].time)
|
188
|
+
elsif time <= pts[midpoint].time
|
215
189
|
return find_closest(pts[0..midpoint], time)
|
216
190
|
else
|
217
|
-
return find_closest(pts[(midpoint+1)..-1], time)
|
191
|
+
return find_closest(pts[(midpoint + 1)..-1], time)
|
218
192
|
end
|
219
193
|
end
|
194
|
+
# rubocop:enable Style/GuardClause
|
220
195
|
|
221
196
|
# Calculate the Haversine distance between two points. This is the method
|
222
197
|
# the library uses to calculate the cumulative distance of GPX files.
|
@@ -224,11 +199,6 @@ module GPX
|
|
224
199
|
p1.haversine_distance_from(p2)
|
225
200
|
end
|
226
201
|
|
227
|
-
# Calculate the plain Pythagorean difference between two points. Not currently used.
|
228
|
-
def pythagorean_distance(p1, p2)
|
229
|
-
p1.pythagorean_distance_from(p2)
|
230
|
-
end
|
231
|
-
|
232
202
|
# Calculates the distance between two points using the Law of Cosines formula. Not currently used.
|
233
203
|
def law_of_cosines_distance(p1, p2)
|
234
204
|
p1.law_of_cosines_distance_from(p2)
|
@@ -246,8 +216,8 @@ module GPX
|
|
246
216
|
|
247
217
|
def update_meta_data(pt, last_pt)
|
248
218
|
if pt.time
|
249
|
-
@earliest_point = pt if
|
250
|
-
@latest_point
|
219
|
+
@earliest_point = pt if @earliest_point.nil? || (pt.time < @earliest_point.time)
|
220
|
+
@latest_point = pt if @latest_point.nil? || (pt.time > @latest_point.time)
|
251
221
|
else
|
252
222
|
# when no time information in data, we consider the points are ordered
|
253
223
|
@earliest_point = @points[0]
|
@@ -255,16 +225,14 @@ module GPX
|
|
255
225
|
end
|
256
226
|
|
257
227
|
if pt.elevation
|
258
|
-
@lowest_point
|
259
|
-
@highest_point
|
228
|
+
@lowest_point = pt if @lowest_point.nil? || (pt.elevation < @lowest_point.elevation)
|
229
|
+
@highest_point = pt if @highest_point.nil? || (pt.elevation > @highest_point.elevation)
|
260
230
|
end
|
261
231
|
@bounds.add(pt)
|
262
|
-
if last_pt
|
263
|
-
@distance += haversine_distance(last_pt, pt)
|
264
|
-
@duration += pt.time - last_pt.time if pt.time and last_pt.time
|
265
|
-
end
|
266
|
-
end
|
267
232
|
|
233
|
+
return unless last_pt
|
234
|
+
@distance += haversine_distance(last_pt, pt)
|
235
|
+
@duration += pt.time - last_pt.time if pt.time && last_pt.time
|
236
|
+
end
|
268
237
|
end
|
269
|
-
|
270
238
|
end
|