gpx/lib/gpx/track.rb

150 lines
5.5 KiB
Ruby

#--
# Copyright (c) 2006 Doug Fales
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
module GPX
# In GPX, a single Track can hold multiple Segments, each of which hold
# multiple points (in this library, those points are instances of
# TrackPoint). Each instance of this class has its own meta-data, including
# low point, high point, and distance. Of course, each track references an
# array of the segments that copmrise it, but additionally each track holds
# a reference to all of its points as one big array called "points".
class Track < Base
attr_reader :points, :bounds, :lowest_point, :highest_point, :distance
attr_accessor :segments, :name, :gpx_file
# Initialize a track from a XML::Node, or, if no :element option is
# passed, initialize a blank Track object.
def initialize(opts = {})
@gpx_file = opts[:gpx_file]
@segments = []
@points = []
reset_meta_data
if(opts[:element])
trk_element = opts[:element]
@name = (trk_element.find("child::gpx:name", @gpx_file.ns).first.content rescue "")
trk_element.find("child::gpx:trkseg", @gpx_file.ns).each do |seg_element|
seg = Segment.new(:element => seg_element, :track => self, :gpx_file => @gpx_file)
update_meta_data(seg)
@segments << seg
@points.concat(seg.points) unless seg.nil?
end
end
end
# Append a segment to this track, updating its meta data along the way.
def append_segment(seg)
update_meta_data(seg)
@segments << seg
@points.concat(seg.points) unless seg.nil?
end
# Returns true if the given time occurs within any of the segments of this track.
def contains_time?(time)
segments.each do |seg|
return true if seg.contains_time?(time)
end
return false
end
# Finds the closest point (to "time") within this track. Useful for
# correlating things like pictures, video, and other events, if you are
# working with a timestamp.
def closest_point(time)
segment = segments.find { |s| s.contains_time?(time) }
segment.closest_point(time)
end
# Removes all points outside of a given area and updates the meta data.
# The "area" paremeter is usually a Bounds object.
def crop(area)
reset_meta_data
segments.each do |seg|
seg.crop(area)
update_meta_data(seg) unless seg.empty?
end
segments.delete_if { |seg| seg.empty? }
end
# Deletes all points within a given area and updates the meta data.
def delete_area(area)
reset_meta_data
segments.each do |seg|
seg.delete_area(area)
update_meta_data(seg) unless seg.empty?
end
segments.delete_if { |seg| seg.empty? }
end
# Returns true if this track has no points in it. This should return
# true even when the track has empty segments.
def empty?
(points.nil? or points.size.zero?)
end
# Creates a new XML::Node from the contents of this instance.
def to_xml
trk= Node.new('trk')
name_elem = Node.new('name')
name_elem << name
trk << name_elem
segments.each do |seg|
trk << seg.to_xml
end
trk
end
# Prints out a friendly summary of this track (sans points). Useful for
# debugging and sanity checks.
def to_s
result = "Track \n"
result << "\tName: #{name}\n"
result << "\tSize: #{points.size} points\n"
result << "\tSegments: #{segments.size} \n"
result << "\tDistance: #{distance} km\n"
result << "\tLowest Point: #{lowest_point.elevation} \n"
result << "\tHighest Point: #{highest_point.elevation}\n "
result << "\tBounds: #{bounds.to_s}"
result
end
protected
def update_meta_data(seg)
@lowest_point = seg.lowest_point if(@lowest_point.nil? or seg.lowest_point.elevation < @lowest_point.elevation)
@highest_point = seg.highest_point if(@highest_point.nil? or seg.highest_point.elevation > @highest_point.elevation)
@bounds.add(seg.bounds)
@distance += seg.distance
@points.concat(seg.points)
end
def reset_meta_data
@bounds = Bounds.new
@highest_point = nil
@lowest_point = nil
@distance = 0.0
@points = []
end
end
end