gpx 0.9.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +36 -0
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +162 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +6 -5
  7. data/CHANGELOG.md +15 -0
  8. data/Gemfile +2 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +20 -5
  11. data/Rakefile +22 -12
  12. data/bin/gpx_distance +5 -6
  13. data/bin/gpx_smooth +25 -26
  14. data/gpx.gemspec +13 -12
  15. data/lib/gpx/bounds.rb +13 -31
  16. data/lib/gpx/geo_json.rb +199 -0
  17. data/lib/gpx/gpx.rb +4 -26
  18. data/lib/gpx/gpx_file.rb +140 -134
  19. data/lib/gpx/magellan_track_log.rb +34 -66
  20. data/lib/gpx/point.rb +22 -35
  21. data/lib/gpx/route.rb +10 -31
  22. data/lib/gpx/segment.rb +63 -90
  23. data/lib/gpx/track.rb +38 -42
  24. data/lib/gpx/track_point.rb +32 -0
  25. data/lib/gpx/version.rb +3 -1
  26. data/lib/gpx/waypoint.rb +10 -34
  27. data/lib/gpx.rb +13 -34
  28. data/tests/geojson_files/combined_data.json +68 -0
  29. data/tests/geojson_files/line_string_data.json +83 -0
  30. data/tests/geojson_files/multi_line_string_data.json +74 -0
  31. data/tests/geojson_files/multi_point_data.json +14 -0
  32. data/tests/geojson_files/point_data.json +22 -0
  33. data/tests/geojson_test.rb +92 -0
  34. data/tests/gpx10_test.rb +7 -6
  35. data/tests/gpx_file_test.rb +31 -19
  36. data/tests/gpx_files/one_segment_mixed_times.gpx +884 -0
  37. data/tests/gpx_files/routes_without_names.gpx +29 -0
  38. data/tests/gpx_files/with_empty_tracks.gpx +72 -0
  39. data/tests/magellan_test.rb +12 -11
  40. data/tests/output_test.rb +93 -94
  41. data/tests/route_test.rb +75 -30
  42. data/tests/segment_test.rb +104 -93
  43. data/tests/track_file_test.rb +50 -70
  44. data/tests/track_point_test.rb +22 -11
  45. data/tests/track_test.rb +73 -61
  46. data/tests/waypoint_test.rb +46 -48
  47. metadata +47 -18
  48. data/lib/gpx/trackpoint.rb +0 -60
@@ -1,30 +1,10 @@
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 will parse the lat/lon and time data from a Magellan track log,
25
5
  # which is a NMEA formatted CSV list of points.
26
6
  class MagellanTrackLog
27
- #PMGNTRK
7
+ # PMGNTRK
28
8
  # This message is to be used to transmit Track information (basically a list of previous position fixes)
29
9
  # which is often displayed on the plotter or map screen of the unit. The first field in this message
30
10
  # is the Latitude, followed by N or S. The next field is the Longitude followed by E or W. The next
@@ -35,98 +15,86 @@ module GPX
35
15
  # of the fix. Note that this field is (and its preceding comma) is only produced by the unit when the
36
16
  # command PMGNCMD,TRACK,2 is given. It is not present when a simple command of PMGNCMD,TRACK is issued.
37
17
 
38
- #NOTE: The Latitude and Longitude Fields are shown as having two decimal
18
+ # NOTE: The Latitude and Longitude Fields are shown as having two decimal
39
19
  # places. As many additional decimal places may be added as long as the total
40
20
  # length of the message does not exceed 82 bytes.
41
21
 
42
22
  # $PMGNTRK,llll.ll,a,yyyyy.yy,a,xxxxx,a,hhmmss.ss,A,c----c,ddmmyy*hh<CR><LF>
43
23
  require 'csv'
44
24
 
45
- LAT = 1
46
- LAT_HEMI = 2
47
- LON = 3
48
- LON_HEMI = 4
49
- ELE = 5
25
+ LAT = 1
26
+ LAT_HEMI = 2
27
+ LON = 3
28
+ LON_HEMI = 4
29
+ ELE = 5
50
30
  ELE_UNITS = 6
51
- TIME = 7
31
+ TIME = 7
52
32
  INVALID_FLAG = 8
53
- DATE = 10
33
+ DATE = 10
54
34
 
55
35
  FEET_TO_METERS = 0.3048
56
36
 
57
37
  class << self
58
-
59
38
  # Takes the name of a magellan file, converts the contents to GPX, and
60
39
  # writes the result to gpx_filename.
61
40
  def convert_to_gpx(magellan_filename, gpx_filename)
62
-
63
41
  segment = Segment.new
64
42
 
65
- CSV.open(magellan_filename, "r").each do |row|
66
- next if(row.size < 10 or row[INVALID_FLAG] == 'V')
43
+ CSV.open(magellan_filename, 'r').each do |row|
44
+ next if (row.size < 10) || (row[INVALID_FLAG] == 'V')
67
45
 
68
- lat_deg = row[LAT][0..1]
69
- lat_min = row[LAT][2...-1]
46
+ lat_deg = row[LAT][0..1]
47
+ lat_min = row[LAT][2...-1]
70
48
  lat_hemi = row[LAT_HEMI]
71
49
 
72
50
  lat = lat_deg.to_f + (lat_min.to_f / 60.0)
73
- lat = (-lat) if(lat_hemi == 'S')
51
+ lat = -lat if lat_hemi == 'S'
74
52
 
75
- lon_deg = row[LON][0..2]
76
- lon_min = row[LON][3..-1]
53
+ lon_deg = row[LON][0..2]
54
+ lon_min = row[LON][3..-1]
77
55
  lon_hemi = row[LON_HEMI]
78
56
 
79
57
  lon = lon_deg.to_f + (lon_min.to_f / 60.0)
80
- lon = (-lon) if(lon_hemi == 'W')
81
-
58
+ lon = -lon if lon_hemi == 'W'
82
59
 
83
60
  ele = row[ELE]
84
61
  ele_units = row[ELE_UNITS]
85
62
  ele = ele.to_f
86
- if(ele_units == 'F')
87
- ele *= FEET_TO_METERS
88
- end
63
+ ele *= FEET_TO_METERS if ele_units == 'F'
89
64
 
90
- hrs = row[TIME][0..1].to_i
65
+ hrs = row[TIME][0..1].to_i
91
66
  mins = row[TIME][2..3].to_i
92
67
  secs = row[TIME][4..5].to_i
93
- day = row[DATE][0..1].to_i
94
- mon = row[DATE][2..3].to_i
95
- yr = 2000 + row[DATE][4..5].to_i
68
+ day = row[DATE][0..1].to_i
69
+ mon = row[DATE][2..3].to_i
70
+ yr = 2000 + row[DATE][4..5].to_i
96
71
 
97
72
  time = Time.gm(yr, mon, day, hrs, mins, secs)
98
73
 
99
- #must create point
100
- pt = TrackPoint.new(:lat => lat, :lon => lon, :time => time, :elevation => ele)
74
+ # must create point
75
+ pt = TrackPoint.new(lat: lat, lon: lon, time: time, elevation: ele)
101
76
  segment.append_point(pt)
102
-
103
77
  end
104
78
 
105
79
  trk = Track.new
106
80
  trk.append_segment(segment)
107
- gpx_file = GPXFile.new(:tracks => [trk])
81
+ gpx_file = GPXFile.new(tracks: [trk])
108
82
  gpx_file.write(gpx_filename)
109
-
110
83
  end
111
84
 
112
85
  # Tests to see if the given file is a magellan NMEA track log.
113
- def is_magellan_file?(filename)
86
+ def magellan_file?(filename)
114
87
  i = 0
115
- File.open(filename, "r") do |f|
88
+ File.open(filename, 'r') do |f|
116
89
  f.each do |line|
117
- i += 1
118
- if line =~ /^\$PMGNTRK/
119
- return true
120
- elsif line =~ /<\?xml/
121
- return false
122
- elsif(i > 10)
123
- return false
124
- end
90
+ i += 1
91
+ return true if line =~ /^\$PMGNTRK/
92
+ return false if line =~ /<\?xml/
93
+ return false if i > 10
125
94
  end
126
95
  end
127
- return false
96
+ false
128
97
  end
129
98
  end
130
-
131
99
  end
132
100
  end
data/lib/gpx/point.rb CHANGED
@@ -1,46 +1,35 @@
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
  # The base class for all points. Trackpoint and Waypoint both descend from this base class.
25
5
  class Point < Base
26
- D_TO_R = Math::PI/180.0;
27
- attr_accessor :lat, :lon, :time, :elevation, :gpx_file, :speed, :extensions
6
+ D_TO_R = Math::PI / 180.0
7
+ attr_accessor :time, :elevation, :gpx_file, :speed, :extensions
8
+ attr_reader :lat, :lon
28
9
 
29
10
  # When you need to manipulate individual points, you can create a Point
30
11
  # object with a latitude, a longitude, an elevation, and a time. In
31
12
  # addition, you can pass an XML element to this initializer, and the
32
13
  # relevant info will be parsed out.
33
- def initialize(opts = {:lat => 0.0, :lon => 0.0, :elevation => 0.0, :time => Time.now } )
14
+ def initialize(opts = { lat: 0.0, lon: 0.0, elevation: 0.0, time: Time.now })
15
+ super()
34
16
  @gpx_file = opts[:gpx_file]
35
- if (opts[:element])
17
+ if opts[:element]
36
18
  elem = opts[:element]
37
- @lat, @lon = elem["lat"].to_f, elem["lon"].to_f
38
- @latr, @lonr = (D_TO_R * @lat), (D_TO_R * @lon)
39
- #'-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
40
- @time = (Time.xmlschema(elem.at("time").inner_text) rescue nil)
41
- @elevation = elem.at("ele").inner_text.to_f unless elem.at("ele").nil?
42
- @speed = elem.at("speed").inner_text.to_f unless elem.at("speed").nil?
43
- @extensions = elem.at("extensions") unless elem.at("extensions").nil?
19
+ @lat = elem['lat'].to_f
20
+ @lon = elem['lon'].to_f
21
+ @latr = (D_TO_R * @lat)
22
+ @lonr = (D_TO_R * @lon)
23
+ # '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
24
+ @time = (
25
+ begin
26
+ Time.xmlschema(elem.at('time').inner_text)
27
+ rescue StandardError
28
+ nil
29
+ end)
30
+ @elevation = elem.at('ele').inner_text.to_f unless elem.at('ele').nil?
31
+ @speed = elem.at('speed').inner_text.to_f unless elem.at('speed').nil?
32
+ @extensions = elem.at('extensions') unless elem.at('extensions').nil?
44
33
  else
45
34
  @lat = opts[:lat]
46
35
  @lon = opts[:lon]
@@ -49,10 +38,8 @@ module GPX
49
38
  @speed = opts[:speed]
50
39
  @extensions = opts[:extensions]
51
40
  end
52
-
53
41
  end
54
42
 
55
-
56
43
  # Returns the latitude and longitude (in that order), separated by the
57
44
  # given delimeter. This is useful for passing a point into another API
58
45
  # (i.e. the Google Maps javascript API).
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)..-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