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.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.gitignore +4 -0
  4. data/.rubocop +1 -0
  5. data/.rubocop.yml +162 -0
  6. data/.ruby-version +1 -0
  7. data/.tool-versions +1 -0
  8. data/.travis.yml +4 -6
  9. data/CHANGELOG.md +32 -0
  10. data/Gemfile +2 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +38 -17
  13. data/Rakefile +22 -12
  14. data/UPGRADING.md +7 -0
  15. data/bin/gpx_distance +5 -6
  16. data/bin/gpx_smooth +25 -26
  17. data/gpx.gemspec +14 -11
  18. data/lib/gpx/bounds.rb +13 -31
  19. data/lib/gpx/geo_json.rb +199 -0
  20. data/lib/gpx/gpx.rb +4 -26
  21. data/lib/gpx/gpx_file.rb +140 -134
  22. data/lib/gpx/magellan_track_log.rb +34 -66
  23. data/lib/gpx/point.rb +22 -35
  24. data/lib/gpx/route.rb +10 -31
  25. data/lib/gpx/segment.rb +63 -90
  26. data/lib/gpx/track.rb +38 -42
  27. data/lib/gpx/track_point.rb +32 -0
  28. data/lib/gpx/version.rb +3 -1
  29. data/lib/gpx/waypoint.rb +10 -34
  30. data/lib/gpx.rb +13 -34
  31. data/tests/geojson_files/combined_data.json +68 -0
  32. data/tests/geojson_files/line_string_data.json +83 -0
  33. data/tests/geojson_files/multi_line_string_data.json +74 -0
  34. data/tests/geojson_files/multi_point_data.json +14 -0
  35. data/tests/geojson_files/point_data.json +22 -0
  36. data/tests/geojson_test.rb +92 -0
  37. data/tests/gpx10_test.rb +7 -6
  38. data/tests/gpx_file_test.rb +31 -19
  39. data/tests/gpx_files/one_segment_mixed_times.gpx +884 -0
  40. data/tests/gpx_files/routes_without_names.gpx +29 -0
  41. data/tests/gpx_files/with_empty_tracks.gpx +72 -0
  42. data/tests/magellan_test.rb +12 -11
  43. data/tests/output_test.rb +93 -94
  44. data/tests/route_test.rb +75 -30
  45. data/tests/segment_test.rb +104 -93
  46. data/tests/track_file_test.rb +50 -70
  47. data/tests/track_point_test.rb +22 -11
  48. data/tests/track_test.rb +73 -61
  49. data/tests/waypoint_test.rb +46 -48
  50. metadata +45 -13
  51. data/lib/gpx/trackpoint.rb +0 -60
data/lib/gpx/route.rb CHANGED
@@ -1,58 +1,37 @@
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
- #++
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
- if(opts[:gpx_file] and opts[:element])
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("name").inner_text
16
+ @name = rte_element.at('name')&.inner_text
37
17
  @points = []
38
- rte_element.search("rtept").each do |point|
39
- @points << Point.new(:element => point, :gpx_file => @gpx_file)
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] or [])
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| not area.contains? 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
- # 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
- #++
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
- if(opts[:element])
47
- segment_element = opts[:element]
48
- last_pt = nil
49
- if segment_element.is_a?(Nokogiri::XML::Node)
50
- segment_element.search("trkpt").each do |trkpt|
51
- pt = TrackPoint.new(:element => trkpt, :segment => self, :gpx_file => @gpx_file)
52
- append_point(pt)
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(@earliest_point.nil? or pt.time < @earliest_point.time)
63
- @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time)
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 = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation)
72
- @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation)
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 and last_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 and time <= @latest_point.time) rescue false
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| not area.contains?(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
- unless yield(pt)
118
- keep_points << pt
119
- update_meta_data(pt, last_pt)
120
- last_pt = pt
121
- end
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? or (points.size == 0))
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.to_s} \n"
137
- result << "\tLatest Point: #{latest_point.time.to_s} \n"
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.to_s}"
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
- return nil
126
+ nil
147
127
  elsif indicator.is_a?(Integer)
148
- return closest_point(@earliest_point.time + indicator)
149
- elsif(indicator.is_a?(Time))
150
- return closest_point(indicator)
128
+ closest_point(@earliest_point.time + indicator)
129
+ elsif indicator.is_a?(Time)
130
+ closest_point(indicator)
151
131
  else
152
- raise Exception, "find_end_point_by_time_or_offset requires an argument of type Time or Integer"
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 = ((lon_av) / n).round(7)
188
- tmp_point.elevation = ((alt_av) / n).round(2)
189
- tmp_point.lat = ((lat_av) / n).round(7)
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
- midpoint = pts.size/2
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 and time <= pts[midpoint+1].time
211
-
212
- return pts[midpoint]
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
- return find_closest(pts[(midpoint+1)..-1], time)
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(@earliest_point.nil? or pt.time < @earliest_point.time)
250
- @latest_point = pt if(@latest_point.nil? or pt.time > @latest_point.time)
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 = pt if(@lowest_point.nil? or pt.elevation < @lowest_point.elevation)
259
- @highest_point = pt if(@highest_point.nil? or pt.elevation > @highest_point.elevation)
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
- end
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
- # 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
- #++
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
- if(opts[:element])
42
- trk_element = opts[:element]
43
- @name = (trk_element.at("name").inner_text rescue "")
44
- @comment = (trk_element.at('cmt').inner_text rescue '')
45
- @description = (trk_element.at('desc').inner_text rescue '')
46
- trk_element.search("trkseg").each do |seg_element|
47
- seg = Segment.new(:element => seg_element, :track => self, :gpx_file => @gpx_file)
48
- update_meta_data(seg)
49
- @segments << seg
50
- end
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
- return false
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 { |seg| seg.empty? }
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 { |seg| seg.empty? }
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? or points.size.zero?)
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.to_s}"
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 = seg.lowest_point if(@lowest_point.nil? or seg.lowest_point.elevation < @lowest_point.elevation)
133
- @highest_point = seg.highest_point if(@highest_point.nil? or seg.highest_point.elevation > @highest_point.elevation)
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GPX
2
- VERSION = "0.8.3"
4
+ VERSION = "1.1.1"
3
5
  end
data/lib/gpx/waypoint.rb CHANGED
@@ -1,56 +1,32 @@
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
- #++
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(opts[:element] and opts[:gpx_file])
21
+ if opts[:element] && opts[:gpx_file]
44
22
  wpt_elem = opts[:element]
45
23
  @gpx_file = opts[:gpx_file]
46
- super(:element => wpt_elem, :gpx_file => @gpx_file)
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 self.respond_to?(assignment_method)
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 = self.send(sub_element_attribute)
44
+ val = send(sub_element_attribute)
69
45
  result << "\t#{sub_element_attribute}: #{val}\n" unless val.nil?
70
46
  end
71
47
  result