gpx 0.8.3 → 1.1.1
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/.github/workflows/ruby.yml +37 -0
- data/.gitignore +4 -0
- data/.rubocop +1 -0
- data/.rubocop.yml +162 -0
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/.travis.yml +4 -6
- data/CHANGELOG.md +32 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +38 -17
- data/Rakefile +22 -12
- data/UPGRADING.md +7 -0
- data/bin/gpx_distance +5 -6
- data/bin/gpx_smooth +25 -26
- data/gpx.gemspec +14 -11
- data/lib/gpx/bounds.rb +13 -31
- data/lib/gpx/geo_json.rb +199 -0
- data/lib/gpx/gpx.rb +4 -26
- data/lib/gpx/gpx_file.rb +140 -134
- data/lib/gpx/magellan_track_log.rb +34 -66
- data/lib/gpx/point.rb +22 -35
- data/lib/gpx/route.rb +10 -31
- data/lib/gpx/segment.rb +63 -90
- data/lib/gpx/track.rb +38 -42
- data/lib/gpx/track_point.rb +32 -0
- data/lib/gpx/version.rb +3 -1
- data/lib/gpx/waypoint.rb +10 -34
- data/lib/gpx.rb +13 -34
- data/tests/geojson_files/combined_data.json +68 -0
- data/tests/geojson_files/line_string_data.json +83 -0
- data/tests/geojson_files/multi_line_string_data.json +74 -0
- data/tests/geojson_files/multi_point_data.json +14 -0
- data/tests/geojson_files/point_data.json +22 -0
- data/tests/geojson_test.rb +92 -0
- data/tests/gpx10_test.rb +7 -6
- data/tests/gpx_file_test.rb +31 -19
- data/tests/gpx_files/one_segment_mixed_times.gpx +884 -0
- data/tests/gpx_files/routes_without_names.gpx +29 -0
- data/tests/gpx_files/with_empty_tracks.gpx +72 -0
- data/tests/magellan_test.rb +12 -11
- data/tests/output_test.rb +93 -94
- data/tests/route_test.rb +75 -30
- data/tests/segment_test.rb +104 -93
- data/tests/track_file_test.rb +50 -70
- data/tests/track_point_test.rb +22 -11
- data/tests/track_test.rb +73 -61
- data/tests/waypoint_test.rb +46 -48
- metadata +45 -13
- data/lib/gpx/trackpoint.rb +0 -60
data/lib/gpx/route.rb
CHANGED
@@ -1,58 +1,37 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
#++
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
23
3
|
module GPX
|
24
4
|
# A Route in GPX is very similar to a Track, but it is created by a user
|
25
5
|
# from a series of Waypoints, whereas a Track is created by the GPS device
|
26
6
|
# automatically logging your progress at regular intervals.
|
27
7
|
class Route < Base
|
28
|
-
|
29
8
|
attr_accessor :points, :name, :gpx_file
|
30
9
|
|
31
10
|
# Initialize a Route from a XML::Node.
|
32
11
|
def initialize(opts = {})
|
33
|
-
|
12
|
+
super()
|
13
|
+
if opts[:gpx_file] && opts[:element]
|
34
14
|
rte_element = opts[:element]
|
35
15
|
@gpx_file = opts[:gpx_file]
|
36
|
-
@name = rte_element.at(
|
16
|
+
@name = rte_element.at('name')&.inner_text
|
37
17
|
@points = []
|
38
|
-
rte_element.search(
|
39
|
-
@points << Point.new(:
|
18
|
+
rte_element.search('rtept').each do |point|
|
19
|
+
@points << Point.new(element: point, gpx_file: @gpx_file)
|
40
20
|
end
|
41
21
|
else
|
42
|
-
@points = (opts[:points]
|
22
|
+
@points = (opts[:points] || [])
|
43
23
|
@name = (opts[:name])
|
44
24
|
end
|
45
|
-
|
46
25
|
end
|
47
26
|
|
48
27
|
# Delete points outside of a given area.
|
49
28
|
def crop(area)
|
50
|
-
points.delete_if{ |pt|
|
29
|
+
points.delete_if { |pt| !area.contains? pt }
|
51
30
|
end
|
52
31
|
|
53
32
|
# Delete points within the given area.
|
54
33
|
def delete_area(area)
|
55
|
-
points.delete_if{ |pt| area.contains? pt }
|
34
|
+
points.delete_if { |pt| area.contains? pt }
|
56
35
|
end
|
57
36
|
end
|
58
37
|
end
|
data/lib/gpx/segment.rb
CHANGED
@@ -1,38 +1,18 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
#++
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
23
3
|
module GPX
|
24
4
|
# A segment is the basic container in a GPX file. A Segment contains points
|
25
5
|
# (in this lib, they're called TrackPoints). A Track contains Segments. An
|
26
6
|
# instance of Segment knows its highest point, lowest point, earliest and
|
27
7
|
# latest points, distance, and bounds.
|
28
8
|
class Segment < Base
|
29
|
-
|
30
9
|
attr_reader :earliest_point, :latest_point, :bounds, :highest_point, :lowest_point, :distance, :duration
|
31
10
|
attr_accessor :points, :track
|
32
11
|
|
33
12
|
# If a XML::Node object is passed-in, this will initialize a new
|
34
13
|
# Segment based on its contents. Otherwise, a blank Segment is created.
|
35
14
|
def initialize(opts = {})
|
15
|
+
super()
|
36
16
|
@gpx_file = opts[:gpx_file]
|
37
17
|
@track = opts[:track]
|
38
18
|
@points = []
|
@@ -43,15 +23,13 @@ module GPX
|
|
43
23
|
@distance = 0.0
|
44
24
|
@duration = 0.0
|
45
25
|
@bounds = Bounds.new
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
end
|
26
|
+
|
27
|
+
segment_element = opts[:element]
|
28
|
+
return unless segment_element.is_a?(Nokogiri::XML::Node)
|
29
|
+
|
30
|
+
segment_element.search('trkpt').each do |trkpt|
|
31
|
+
pt = TrackPoint.new(element: trkpt, segment: self, gpx_file: @gpx_file)
|
32
|
+
append_point(pt)
|
55
33
|
end
|
56
34
|
end
|
57
35
|
|
@@ -59,8 +37,8 @@ module GPX
|
|
59
37
|
def append_point(pt)
|
60
38
|
last_pt = @points[-1]
|
61
39
|
if pt.time
|
62
|
-
@earliest_point = pt if
|
63
|
-
@latest_point
|
40
|
+
@earliest_point = pt if @earliest_point.nil? || (@earliest_point.time && pt.time < @earliest_point.time)
|
41
|
+
@latest_point = pt if @latest_point.nil? || (@latest_point.time && pt.time > @latest_point.time)
|
64
42
|
else
|
65
43
|
# when no time information in data, we consider the points are ordered
|
66
44
|
@earliest_point = @points[0]
|
@@ -68,8 +46,8 @@ module GPX
|
|
68
46
|
end
|
69
47
|
|
70
48
|
if pt.elevation
|
71
|
-
@lowest_point
|
72
|
-
@highest_point
|
49
|
+
@lowest_point = pt if @lowest_point.nil? || (pt.elevation < @lowest_point.elevation)
|
50
|
+
@highest_point = pt if @highest_point.nil? || (pt.elevation > @highest_point.elevation)
|
73
51
|
end
|
74
52
|
@bounds.min_lat = pt.lat if pt.lat < @bounds.min_lat
|
75
53
|
@bounds.min_lon = pt.lon if pt.lon < @bounds.min_lon
|
@@ -77,14 +55,16 @@ module GPX
|
|
77
55
|
@bounds.max_lon = pt.lon if pt.lon > @bounds.max_lon
|
78
56
|
if last_pt
|
79
57
|
@distance += haversine_distance(last_pt, pt)
|
80
|
-
@duration += pt.time - last_pt.time if pt.time
|
58
|
+
@duration += pt.time - last_pt.time if pt.time && last_pt.time
|
81
59
|
end
|
82
60
|
@points << pt
|
83
61
|
end
|
84
62
|
|
85
63
|
# Returns true if the given time is within this Segment.
|
86
64
|
def contains_time?(time)
|
87
|
-
(time >= @earliest_point.time
|
65
|
+
((time >= @earliest_point.time) && (time <= @latest_point.time))
|
66
|
+
rescue StandardError
|
67
|
+
false
|
88
68
|
end
|
89
69
|
|
90
70
|
# Finds the closest point in time to the passed-in time argument. Useful
|
@@ -97,12 +77,12 @@ module GPX
|
|
97
77
|
# Deletes all points within this Segment that lie outside of the given
|
98
78
|
# area (which should be a Bounds object).
|
99
79
|
def crop(area)
|
100
|
-
delete_if { |pt|
|
80
|
+
delete_if { |pt| !area.contains?(pt) }
|
101
81
|
end
|
102
82
|
|
103
83
|
# Deletes all points in this Segment that lie within the given area.
|
104
84
|
def delete_area(area)
|
105
|
-
delete_if{ |pt| area.contains?(pt) }
|
85
|
+
delete_if { |pt| area.contains?(pt) }
|
106
86
|
end
|
107
87
|
|
108
88
|
# A handy method that deletes points based on a block that is passed in.
|
@@ -114,18 +94,18 @@ module GPX
|
|
114
94
|
keep_points = []
|
115
95
|
last_pt = nil
|
116
96
|
points.each do |pt|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
97
|
+
next if yield(pt)
|
98
|
+
|
99
|
+
keep_points << pt
|
100
|
+
update_meta_data(pt, last_pt)
|
101
|
+
last_pt = pt
|
122
102
|
end
|
123
103
|
@points = keep_points
|
124
104
|
end
|
125
105
|
|
126
106
|
# Returns true if this Segment has no points.
|
127
107
|
def empty?
|
128
|
-
(points.nil?
|
108
|
+
(points.nil? || points.empty?)
|
129
109
|
end
|
130
110
|
|
131
111
|
# Prints out a nice summary of this Segment.
|
@@ -133,31 +113,31 @@ module GPX
|
|
133
113
|
result = "Track Segment\n"
|
134
114
|
result << "\tSize: #{points.size} points\n"
|
135
115
|
result << "\tDistance: #{distance} km\n"
|
136
|
-
result << "\tEarliest Point: #{earliest_point.time
|
137
|
-
result << "\tLatest Point: #{latest_point.time
|
116
|
+
result << "\tEarliest Point: #{earliest_point.time} \n"
|
117
|
+
result << "\tLatest Point: #{latest_point.time} \n"
|
138
118
|
result << "\tLowest Point: #{lowest_point.elevation} \n"
|
139
119
|
result << "\tHighest Point: #{highest_point.elevation}\n "
|
140
|
-
result << "\tBounds: #{bounds
|
120
|
+
result << "\tBounds: #{bounds}"
|
141
121
|
result
|
142
122
|
end
|
143
123
|
|
144
124
|
def find_point_by_time_or_offset(indicator)
|
145
125
|
if indicator.nil?
|
146
|
-
|
126
|
+
nil
|
147
127
|
elsif indicator.is_a?(Integer)
|
148
|
-
|
149
|
-
elsif
|
150
|
-
|
128
|
+
closest_point(@earliest_point.time + indicator)
|
129
|
+
elsif indicator.is_a?(Time)
|
130
|
+
closest_point(indicator)
|
151
131
|
else
|
152
|
-
raise
|
132
|
+
raise ArgumentError, 'find_end_point_by_time_or_offset requires an argument of type Time or Integer'
|
153
133
|
end
|
154
134
|
end
|
155
|
-
|
135
|
+
|
156
136
|
# 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={})
|
137
|
+
def smooth_location_by_average(opts = {})
|
158
138
|
seconds_either_side = opts[:averaging_window] || 20
|
159
139
|
|
160
|
-
#calculate the first and last points to which the smoothing should be applied
|
140
|
+
# calculate the first and last points to which the smoothing should be applied
|
161
141
|
earliest = (find_point_by_time_or_offset(opts[:start]) || @earliest_point).time
|
162
142
|
latest = (find_point_by_time_or_offset(opts[:end]) || @latest_point).time
|
163
143
|
|
@@ -165,18 +145,18 @@ module GPX
|
|
165
145
|
|
166
146
|
@points.each do |point|
|
167
147
|
if point.time > latest || point.time < earliest
|
168
|
-
tmp_points.push point #add the point unaltered
|
169
|
-
next
|
148
|
+
tmp_points.push point # add the point unaltered
|
149
|
+
next
|
170
150
|
end
|
171
151
|
lat_av = 0.to_f
|
172
152
|
lon_av = 0.to_f
|
173
153
|
alt_av = 0.to_f
|
174
154
|
n = 0
|
175
|
-
# k ranges from the time of the current point +/- 20s
|
176
|
-
(-1*seconds_either_side..seconds_either_side).each do |k|
|
155
|
+
# k ranges from the time of the current point +/- 20s
|
156
|
+
(-1 * seconds_either_side..seconds_either_side).each do |k|
|
177
157
|
# find the point nearest to the time offset indicated by k
|
178
158
|
contributing_point = closest_point(point.time + k)
|
179
|
-
#sum up the contributions to the average
|
159
|
+
# sum up the contributions to the average
|
180
160
|
lat_av += contributing_point.lat
|
181
161
|
lon_av += contributing_point.lon
|
182
162
|
alt_av += contributing_point.elevation
|
@@ -184,37 +164,36 @@ module GPX
|
|
184
164
|
end
|
185
165
|
# calculate the averages
|
186
166
|
tmp_point = point.clone
|
187
|
-
tmp_point.lon = (
|
188
|
-
tmp_point.elevation = (
|
189
|
-
tmp_point.lat = (
|
167
|
+
tmp_point.lon = (lon_av / n).round(7)
|
168
|
+
tmp_point.elevation = (alt_av / n).round(2)
|
169
|
+
tmp_point.lat = (lat_av / n).round(7)
|
190
170
|
tmp_points.push tmp_point
|
191
171
|
end
|
192
|
-
last_pt = nil
|
193
172
|
@points.clear
|
194
173
|
reset_meta_data
|
195
|
-
#now commit the averages back and recalculate the distances
|
174
|
+
# now commit the averages back and recalculate the distances
|
196
175
|
tmp_points.each do |point|
|
197
176
|
append_point(point)
|
198
177
|
end
|
199
178
|
end
|
200
179
|
|
201
180
|
protected
|
181
|
+
|
202
182
|
def find_closest(pts, time)
|
203
183
|
return pts.first if pts.size == 1
|
204
|
-
|
184
|
+
|
185
|
+
midpoint = pts.size / 2
|
205
186
|
if pts.size == 2
|
206
187
|
diff_1 = pts[0].time - time
|
207
188
|
diff_2 = pts[1].time - time
|
208
189
|
return (diff_1 < diff_2 ? pts[0] : pts[1])
|
209
190
|
end
|
210
|
-
if time >= pts[midpoint].time
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
elsif(time <= pts[midpoint].time)
|
215
|
-
return find_closest(pts[0..midpoint], time)
|
191
|
+
if (time >= pts[midpoint].time) && (time <= pts[midpoint + 1].time)
|
192
|
+
pts[midpoint]
|
193
|
+
elsif time <= pts[midpoint].time
|
194
|
+
find_closest(pts[0..midpoint], time)
|
216
195
|
else
|
217
|
-
|
196
|
+
find_closest(pts[(midpoint + 1)..], time)
|
218
197
|
end
|
219
198
|
end
|
220
199
|
|
@@ -224,11 +203,6 @@ module GPX
|
|
224
203
|
p1.haversine_distance_from(p2)
|
225
204
|
end
|
226
205
|
|
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
206
|
# Calculates the distance between two points using the Law of Cosines formula. Not currently used.
|
233
207
|
def law_of_cosines_distance(p1, p2)
|
234
208
|
p1.law_of_cosines_distance_from(p2)
|
@@ -246,8 +220,8 @@ module GPX
|
|
246
220
|
|
247
221
|
def update_meta_data(pt, last_pt)
|
248
222
|
if pt.time
|
249
|
-
@earliest_point = pt if
|
250
|
-
@latest_point
|
223
|
+
@earliest_point = pt if @earliest_point.nil? || (pt.time < @earliest_point.time)
|
224
|
+
@latest_point = pt if @latest_point.nil? || (pt.time > @latest_point.time)
|
251
225
|
else
|
252
226
|
# when no time information in data, we consider the points are ordered
|
253
227
|
@earliest_point = @points[0]
|
@@ -255,16 +229,15 @@ module GPX
|
|
255
229
|
end
|
256
230
|
|
257
231
|
if pt.elevation
|
258
|
-
@lowest_point
|
259
|
-
@highest_point
|
232
|
+
@lowest_point = pt if @lowest_point.nil? || (pt.elevation < @lowest_point.elevation)
|
233
|
+
@highest_point = pt if @highest_point.nil? || (pt.elevation > @highest_point.elevation)
|
260
234
|
end
|
261
235
|
@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
236
|
|
268
|
-
|
237
|
+
return unless last_pt
|
269
238
|
|
239
|
+
@distance += haversine_distance(last_pt, pt)
|
240
|
+
@duration += pt.time - last_pt.time if pt.time && last_pt.time
|
241
|
+
end
|
242
|
+
end
|
270
243
|
end
|
data/lib/gpx/track.rb
CHANGED
@@ -1,25 +1,5 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
#++
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
23
3
|
module GPX
|
24
4
|
# In GPX, a single Track can hold multiple Segments, each of which hold
|
25
5
|
# multiple points (in this library, those points are instances of
|
@@ -34,28 +14,45 @@ module GPX
|
|
34
14
|
# Initialize a track from a XML::Node, or, if no :element option is
|
35
15
|
# passed, initialize a blank Track object.
|
36
16
|
def initialize(opts = {})
|
17
|
+
super()
|
37
18
|
@gpx_file = opts[:gpx_file]
|
38
19
|
@segments = []
|
39
20
|
@points = []
|
40
21
|
reset_meta_data
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
22
|
+
|
23
|
+
return unless opts[:element]
|
24
|
+
|
25
|
+
trk_element = opts[:element]
|
26
|
+
@name = (
|
27
|
+
begin
|
28
|
+
trk_element.at('name').inner_text
|
29
|
+
rescue StandardError
|
30
|
+
''
|
31
|
+
end)
|
32
|
+
@comment = (
|
33
|
+
begin
|
34
|
+
trk_element.at('cmt').inner_text
|
35
|
+
rescue StandardError
|
36
|
+
''
|
37
|
+
end)
|
38
|
+
@description = (
|
39
|
+
begin
|
40
|
+
trk_element.at('desc').inner_text
|
41
|
+
rescue StandardError
|
42
|
+
''
|
43
|
+
end)
|
44
|
+
trk_element.search('trkseg').each do |seg_element|
|
45
|
+
seg = Segment.new(element: seg_element, track: self, gpx_file: @gpx_file)
|
46
|
+
append_segment(seg)
|
51
47
|
end
|
52
48
|
end
|
53
49
|
|
54
50
|
# Append a segment to this track, updating its meta data along the way.
|
55
51
|
def append_segment(seg)
|
52
|
+
return if seg.points.empty?
|
53
|
+
|
56
54
|
update_meta_data(seg)
|
57
55
|
@segments << seg
|
58
|
-
@points.concat(seg.points) unless seg.nil?
|
59
56
|
end
|
60
57
|
|
61
58
|
# Returns true if the given time occurs within any of the segments of this track.
|
@@ -63,7 +60,7 @@ module GPX
|
|
63
60
|
segments.each do |seg|
|
64
61
|
return true if seg.contains_time?(time)
|
65
62
|
end
|
66
|
-
|
63
|
+
false
|
67
64
|
end
|
68
65
|
|
69
66
|
# Finds the closest point (to "time") within this track. Useful for
|
@@ -82,7 +79,7 @@ module GPX
|
|
82
79
|
seg.crop(area)
|
83
80
|
update_meta_data(seg) unless seg.empty?
|
84
81
|
end
|
85
|
-
segments.delete_if
|
82
|
+
segments.delete_if(&:empty?)
|
86
83
|
end
|
87
84
|
|
88
85
|
# Deletes all points within a given area and updates the meta data.
|
@@ -92,13 +89,13 @@ module GPX
|
|
92
89
|
seg.delete_area(area)
|
93
90
|
update_meta_data(seg) unless seg.empty?
|
94
91
|
end
|
95
|
-
segments.delete_if
|
92
|
+
segments.delete_if(&:empty?)
|
96
93
|
end
|
97
94
|
|
98
95
|
# Returns true if this track has no points in it. This should return
|
99
96
|
# true even when the track has empty segments.
|
100
97
|
def empty?
|
101
|
-
(points.nil?
|
98
|
+
(points.nil? || points.empty?)
|
102
99
|
end
|
103
100
|
|
104
101
|
# Prints out a friendly summary of this track (sans points). Useful for
|
@@ -115,7 +112,7 @@ module GPX
|
|
115
112
|
result << "\tMoving duration: #{moving_duration} km\n"
|
116
113
|
result << "\tLowest Point: #{lowest_point.elevation} \n"
|
117
114
|
result << "\tHighest Point: #{highest_point.elevation}\n "
|
118
|
-
result << "\tBounds: #{bounds
|
115
|
+
result << "\tBounds: #{bounds}"
|
119
116
|
result
|
120
117
|
end
|
121
118
|
|
@@ -125,12 +122,12 @@ module GPX
|
|
125
122
|
@distance += seg.distance
|
126
123
|
end
|
127
124
|
end
|
128
|
-
|
125
|
+
|
129
126
|
protected
|
130
127
|
|
131
128
|
def update_meta_data(seg)
|
132
|
-
@lowest_point
|
133
|
-
@highest_point
|
129
|
+
@lowest_point = seg.lowest_point if @lowest_point.nil? || (seg.lowest_point.elevation < @lowest_point.elevation)
|
130
|
+
@highest_point = seg.highest_point if @highest_point.nil? || (seg.highest_point.elevation > @highest_point.elevation)
|
134
131
|
@bounds.add(seg.bounds)
|
135
132
|
@distance += seg.distance
|
136
133
|
@moving_duration += seg.duration
|
@@ -145,6 +142,5 @@ module GPX
|
|
145
142
|
@moving_duration = 0.0
|
146
143
|
@points = []
|
147
144
|
end
|
148
|
-
|
149
145
|
end
|
150
146
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GPX
|
4
|
+
# Basically the same as a point, the TrackPoint class is supposed to
|
5
|
+
# represent the points that are children of Segment elements. So, the only
|
6
|
+
# real difference is that TrackPoints hold a reference to their parent
|
7
|
+
# Segments.
|
8
|
+
class TrackPoint < Point
|
9
|
+
RADIUS = 6371 # earth's mean radius in km
|
10
|
+
|
11
|
+
attr_accessor :segment
|
12
|
+
|
13
|
+
def initialize(opts = {})
|
14
|
+
super(opts)
|
15
|
+
@segment = opts[:segment]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Units are in km
|
19
|
+
def haversine_distance_from(p2)
|
20
|
+
d_lat = p2.latr - latr
|
21
|
+
d_lon = p2.lonr - lonr
|
22
|
+
a = Math.sin(d_lat / 2) * Math.sin(d_lat / 2) + Math.cos(latr) * Math.cos(p2.latr) * Math.sin(d_lon / 2) * Math.sin(d_lon / 2)
|
23
|
+
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
24
|
+
RADIUS * c
|
25
|
+
end
|
26
|
+
|
27
|
+
# Units are in km
|
28
|
+
def law_of_cosines_distance_from(p2)
|
29
|
+
Math.acos(Math.sin(latr) * Math.sin(p2.latr) + Math.cos(latr) * Math.cos(p2.latr) * Math.cos(p2.lonr - lonr)) * RADIUS
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/gpx/version.rb
CHANGED
data/lib/gpx/waypoint.rb
CHANGED
@@ -1,56 +1,32 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
#++
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
23
3
|
module GPX
|
24
4
|
# This class supports the concept of a waypoint. Beware that this class has
|
25
5
|
# not seen much use yet, since WalkingBoss does not use waypoints right now.
|
26
6
|
class Waypoint < Point
|
27
|
-
|
28
|
-
SUB_ELEMENTS = %w{ele magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions}
|
7
|
+
SUB_ELEMENTS = %w[ele magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions].freeze
|
29
8
|
|
30
9
|
attr_reader :gpx_file
|
10
|
+
|
31
11
|
SUB_ELEMENTS.each { |sub_el| attr_accessor sub_el.to_sym }
|
32
12
|
|
33
13
|
# Not implemented
|
34
|
-
def crop(area)
|
35
|
-
end
|
14
|
+
def crop(area); end
|
36
15
|
|
37
16
|
# Not implemented
|
38
|
-
def delete_area(area)
|
39
|
-
end
|
17
|
+
def delete_area(area); end
|
40
18
|
|
41
19
|
# Initializes a waypoint from a XML::Node.
|
42
20
|
def initialize(opts = {})
|
43
|
-
if
|
21
|
+
if opts[:element] && opts[:gpx_file]
|
44
22
|
wpt_elem = opts[:element]
|
45
23
|
@gpx_file = opts[:gpx_file]
|
46
|
-
super(:
|
24
|
+
super(element: wpt_elem, gpx_file: @gpx_file)
|
47
25
|
instantiate_with_text_elements(wpt_elem, SUB_ELEMENTS)
|
48
26
|
else
|
49
27
|
opts.each do |key, value|
|
50
28
|
assignment_method = "#{key}="
|
51
|
-
if
|
52
|
-
self.send(assignment_method, value)
|
53
|
-
end
|
29
|
+
send(assignment_method, value) if respond_to?(assignment_method)
|
54
30
|
end
|
55
31
|
end
|
56
32
|
end
|
@@ -65,7 +41,7 @@ module GPX
|
|
65
41
|
result << "\tElevation: #{elevation}\n "
|
66
42
|
result << "\tTime: #{time}\n"
|
67
43
|
SUB_ELEMENTS.each do |sub_element_attribute|
|
68
|
-
val =
|
44
|
+
val = send(sub_element_attribute)
|
69
45
|
result << "\t#{sub_element_attribute}: #{val}\n" unless val.nil?
|
70
46
|
end
|
71
47
|
result
|