require 'nokogiri'
module GeoRuby
module Gpx4r
# An interface to GPX files
class GpxFile
attr_reader :record_count, :file_root #:xmin, :ymin, :xmax, :ymax, :zmin, :zmax, :mmin, :mmax, :file_length
include Enumerable
# Opens a GPX file. Both "abc.shp" and "abc" are accepted.
def initialize(file, *opts) # with_z = true, with_m = true)
@file_root = file.gsub(/\.gpx$/i, '')
fail MalformedGpxException.new('Missing GPX File') unless
File.exist? @file_root + '.gpx'
@points, @envelope = [], nil
@gpx = File.open(@file_root + '.gpx', 'rb')
opt = opts.reduce({}) { |a, e| e.merge(a) }
parse_file(opt[:with_z], opt[:with_m])
end
# force the reopening of the files compsing the shp. Close before calling this.
def reload!
initialize(@file_root)
end
# opens a GPX "file". If a block is given, the GpxFile object is yielded to it and is closed upon return. Else a call to open is equivalent to GpxFile.new(...).
def self.open(file, *opts)
gpxfile = GpxFile.new(file, *opts)
if block_given?
yield gpxfile
# gpxfile.close
else
gpxfile
end
end
# Closes a gpxfile
def close
@gpx.close
end
# Tests if the file has no record
def empty?
record_count == 0
end
# Goes through each record
def each
(0...record_count).each do |i|
yield get_record(i)
end
end
alias_method :each_record, :each
# Returns record +i+
def [](i)
get_record(i)
end
# Returns all the records
def records
@points
end
# Return the GPX file as LineString
def as_line_string
GeoRuby::SimpleFeatures::LineString.from_points(@points)
end
alias_method :as_polyline, :as_line_string
# Return the GPX file as a Polygon
# If the GPX isn't closed, a line from the first
# to the last point will be created to close it.
def as_polygon
GeoRuby::SimpleFeatures::Polygon.from_points([@points[0] == @points[-1] ? @points : @points.push(@points[0].clone)])
end
# Return GPX Envelope
def envelope
@envelope ||= as_polygon.envelope
end
private
def get_record(i)
@points[i]
end
# wpt => waypoint => TODO?
# rte(pt) => route
# trk(pt) => track /
def parse_file(with_z, with_m)
data = @gpx.read
@file_mode = data =~ /trkpt/ ? '//trkpt' : (data =~ /wpt/ ? '//wpt' : '//rtept')
Nokogiri.HTML(data).search(@file_mode).each do |tp|
z = z.inner_text.to_f if with_z && z = tp.at('ele')
m = m.inner_text if with_m && m = tp.at('time')
@points << GeoRuby::SimpleFeatures::Point.from_coordinates([tp['lon'].to_f, tp['lat'].to_f, z, m], 4326, with_z, with_m)
end
close
@record_count = @points.length
envelope
rescue => e
raise MalformedGpxException.new("Bad GPX. Error: #{e}")
# trackpoint.at("gpxdata:hr").nil? # heartrate
end
end
class MalformedGpxException < StandardError
end
end
end