Class: GPX::Segment

Inherits:
Base
  • Object
show all
Defined in:
lib/gpx/segment.rb

Overview

A segment is the basic container in a GPX file. A Segment contains points (in this lib, they’re called TrackPoints). A Track contains Segments. An instance of Segment knows its highest point, lowest point, earliest and latest points, distance, and bounds.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#instantiate_with_text_elements

Constructor Details

#initialize(opts = {}) ⇒ Segment

If a XML::Node object is passed-in, this will initialize a new Segment based on its contents. Otherwise, a blank Segment is created.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/gpx/segment.rb', line 14

def initialize(opts = {})
  super()
  @gpx_file = opts[:gpx_file]
  @track = opts[:track]
  @points = []
  @earliest_point = nil
  @latest_point = nil
  @highest_point = nil
  @lowest_point = nil
  @distance = 0.0
  @duration = 0.0
  @bounds = Bounds.new

  segment_element = opts[:element]
  return unless segment_element.is_a?(Nokogiri::XML::Node)

  segment_element.search('trkpt').each do |trkpt|
    pt = TrackPoint.new(element: trkpt, segment: self, gpx_file: @gpx_file)
    append_point(pt)
  end
end

Instance Attribute Details

#boundsObject (readonly)

Returns the value of attribute bounds.



9
10
11
# File 'lib/gpx/segment.rb', line 9

def bounds
  @bounds
end

#distanceObject (readonly)

Returns the value of attribute distance.



9
10
11
# File 'lib/gpx/segment.rb', line 9

def distance
  @distance
end

#durationObject (readonly)

Returns the value of attribute duration.



9
10
11
# File 'lib/gpx/segment.rb', line 9

def duration
  @duration
end

#earliest_pointObject (readonly)

Returns the value of attribute earliest_point.



9
10
11
# File 'lib/gpx/segment.rb', line 9

def earliest_point
  @earliest_point
end

#highest_pointObject (readonly)

Returns the value of attribute highest_point.



9
10
11
# File 'lib/gpx/segment.rb', line 9

def highest_point
  @highest_point
end

#latest_pointObject (readonly)

Returns the value of attribute latest_point.



9
10
11
# File 'lib/gpx/segment.rb', line 9

def latest_point
  @latest_point
end

#lowest_pointObject (readonly)

Returns the value of attribute lowest_point.



9
10
11
# File 'lib/gpx/segment.rb', line 9

def lowest_point
  @lowest_point
end

#pointsObject

Returns the value of attribute points.



10
11
12
# File 'lib/gpx/segment.rb', line 10

def points
  @points
end

#trackObject

Returns the value of attribute track.



10
11
12
# File 'lib/gpx/segment.rb', line 10

def track
  @track
end

Instance Method Details

#append_point(pt) ⇒ Object

Tack on a point to this Segment. All meta-data will be updated.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/gpx/segment.rb', line 37

def append_point(pt)
  last_pt = @points[-1]
  if pt.time
    @earliest_point = pt if @earliest_point.nil? || (@earliest_point.time && pt.time < @earliest_point.time)
    @latest_point = pt if @latest_point.nil? || (@latest_point.time && pt.time > @latest_point.time)
  else
    # when no time information in data, we consider the points are ordered
    @earliest_point = @points[0]
    @latest_point = pt
  end

  if pt.elevation
    @lowest_point = pt if @lowest_point.nil? || (pt.elevation < @lowest_point.elevation)
    @highest_point = pt if @highest_point.nil? || (pt.elevation > @highest_point.elevation)
  end
  @bounds.min_lat = pt.lat if pt.lat < @bounds.min_lat
  @bounds.min_lon = pt.lon if pt.lon < @bounds.min_lon
  @bounds.max_lat = pt.lat if pt.lat > @bounds.max_lat
  @bounds.max_lon = pt.lon if pt.lon > @bounds.max_lon
  if last_pt
    @distance += haversine_distance(last_pt, pt)
    @duration += pt.time - last_pt.time if pt.time && last_pt.time
  end
  @points << pt
end

#closest_point(time) ⇒ Object

Finds the closest point in time to the passed-in time argument. Useful for matching up time-based objects (photos, video, etc) with a geographic location.



73
74
75
# File 'lib/gpx/segment.rb', line 73

def closest_point(time)
  find_closest(points, time)
end

#contains_time?(time) ⇒ Boolean

Returns true if the given time is within this Segment.

Returns:

  • (Boolean)


64
65
66
67
68
# File 'lib/gpx/segment.rb', line 64

def contains_time?(time)
  (time >= @earliest_point.time) && (time <= @latest_point.time)
rescue StandardError
  false
end

#crop(area) ⇒ Object

Deletes all points within this Segment that lie outside of the given area (which should be a Bounds object).



79
80
81
# File 'lib/gpx/segment.rb', line 79

def crop(area)
  delete_if { |pt| !area.contains?(pt) }
end

#delete_area(area) ⇒ Object

Deletes all points in this Segment that lie within the given area.



84
85
86
# File 'lib/gpx/segment.rb', line 84

def delete_area(area)
  delete_if { |pt| area.contains?(pt) }
end

#delete_ifObject

A handy method that deletes points based on a block that is passed in. If the passed-in block returns true when given a point, then that point is deleted. For example:

delete_if{ |pt| area.contains?(pt) }


92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/gpx/segment.rb', line 92

def delete_if
  
  keep_points = []
  last_pt = nil
  points.each do |pt|
    next if yield(pt)

    keep_points << pt
    (pt, last_pt)
    last_pt = pt
  end
  @points = keep_points
end

#empty?Boolean

Returns true if this Segment has no points.

Returns:

  • (Boolean)


107
108
109
# File 'lib/gpx/segment.rb', line 107

def empty?
  points.nil? || points.empty?
end

#find_point_by_time_or_offset(indicator) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
# File 'lib/gpx/segment.rb', line 124

def find_point_by_time_or_offset(indicator)
  if indicator.nil?
    nil
  elsif indicator.is_a?(Integer)
    closest_point(@earliest_point.time + indicator)
  elsif indicator.is_a?(Time)
    closest_point(indicator)
  else
    raise ArgumentError, 'find_end_point_by_time_or_offset requires an argument of type Time or Integer'
  end
end

#smooth_location_by_average(opts = {}) ⇒ Object

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.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/gpx/segment.rb', line 137

def smooth_location_by_average(opts = {})
  seconds_either_side = opts[:averaging_window] || 20

  # calculate the first and last points to which the smoothing should be applied
  earliest = (find_point_by_time_or_offset(opts[:start]) || @earliest_point).time
  latest = (find_point_by_time_or_offset(opts[:end]) || @latest_point).time

  tmp_points = []

  @points.each do |point|
    if point.time > latest || point.time < earliest
      tmp_points.push point # add the point unaltered
      next
    end
    lat_av = 0.to_f
    lon_av = 0.to_f
    alt_av = 0.to_f
    n = 0
    # k ranges from the time of the current point +/- 20s
    (-1 * seconds_either_side..seconds_either_side).each do |k|
      # find the point nearest to the time offset indicated by k
      contributing_point = closest_point(point.time + k)
      # sum up the contributions to the average
      lat_av += contributing_point.lat
      lon_av += contributing_point.lon
      alt_av += contributing_point.elevation
      n += 1
    end
    # calculate the averages
    tmp_point = point.clone
    tmp_point.lon = (lon_av / n).round(7)
    tmp_point.elevation = (alt_av / n).round(2)
    tmp_point.lat = (lat_av / n).round(7)
    tmp_points.push tmp_point
  end
  @points.clear
  
  # now commit the averages back and recalculate the distances
  tmp_points.each do |point|
    append_point(point)
  end
end

#to_sObject

Prints out a nice summary of this Segment.



112
113
114
115
116
117
118
119
120
121
122
# File 'lib/gpx/segment.rb', line 112

def to_s
  result = "Track Segment\n"
  result << "\tSize: #{points.size} points\n"
  result << "\tDistance: #{distance} km\n"
  result << "\tEarliest Point: #{earliest_point.time} \n"
  result << "\tLatest Point: #{latest_point.time} \n"
  result << "\tLowest Point: #{lowest_point.elevation} \n"
  result << "\tHighest Point: #{highest_point.elevation}\n "
  result << "\tBounds: #{bounds}"
  result
end