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