diff --git a/gpx.gemspec b/gpx.gemspec index d8f7f07..ceb7d7a 100644 --- a/gpx.gemspec +++ b/gpx.gemspec @@ -18,5 +18,5 @@ Gem::Specification.new do |s| s.homepage = "http://dougfales.github.com/gpx/" s.rubyforge_project = "gpx" - s.add_dependency 'hpricot' + s.add_dependency 'nokogiri' end diff --git a/lib/gpx.rb b/lib/gpx.rb index 94699fd..b236262 100644 --- a/lib/gpx.rb +++ b/lib/gpx.rb @@ -20,12 +20,12 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -$:.unshift(File.dirname(__FILE__)) -require 'rubygems' -require 'hpricot' -require 'date' + require 'time' -require 'csv' +require 'nokogiri' + +require 'gpx/version' + require 'gpx/gpx' require 'gpx/gpx_file' require 'gpx/bounds' diff --git a/lib/gpx/bounds.rb b/lib/gpx/bounds.rb index 17038f7..2b483f9 100644 --- a/lib/gpx/bounds.rb +++ b/lib/gpx/bounds.rb @@ -43,15 +43,6 @@ module GPX (min_lon + distance) end - def to_xml - bnd = XML::Node.new('bounds') - bnd['minlat'] = min_lat.to_s - bnd['minlon'] = min_lon.to_s - bnd['maxlat'] = max_lat.to_s - bnd['maxlon'] = max_lon.to_s - bnd - end - # Returns true if the pt is within these bounds. def contains?(pt) (pt.lat >= min_lat and pt.lat <= max_lat and pt.lon >= min_lon and pt.lon <= max_lon) diff --git a/lib/gpx/gpx.rb b/lib/gpx/gpx.rb index 8b270c9..9e7438f 100644 --- a/lib/gpx/gpx.rb +++ b/lib/gpx/gpx.rb @@ -34,7 +34,7 @@ module GPX # attributes to this method. def instantiate_with_text_elements(parent, text_elements) text_elements.each do |el| - child_xpath = "//#{el}" + child_xpath = "#{el}" unless parent.at(child_xpath).nil? val = parent.at(child_xpath).inner_text self.send("#{el}=", val) diff --git a/lib/gpx/gpx_file.rb b/lib/gpx/gpx_file.rb index bdfd922..a8b4ecc 100644 --- a/lib/gpx/gpx_file.rb +++ b/lib/gpx/gpx_file.rb @@ -22,7 +22,7 @@ #++ module GPX class GPXFile < Base - attr_accessor :tracks, :routes, :waypoints, :bounds, :lowest_point, :highest_point, :duration, :ns, :version, :time, :name + attr_accessor :tracks, :routes, :waypoints, :bounds, :lowest_point, :highest_point, :duration, :ns, :time, :name # This initializer can be used to create a new GPXFile from an existing @@ -47,24 +47,14 @@ module GPX if(opts[:gpx_file] or opts[:gpx_data]) if opts[:gpx_file] gpx_file = opts[:gpx_file] - #case gpx_file - #when String - # gpx_file = File.open(gpx_file) - #end - gpx_file = gpx_file.name if gpx_file.is_a?(File) - @xml = Hpricot(File.open(gpx_file)) + gpx_file = File.open(gpx_file) unless gpx_file.is_a?(File) + @xml = Nokogiri::XML(gpx_file) else - @xml = Hpricot(opts[:gpx_data]) + @xml = Nokogiri::XML(opts[:gpx_data]) end - # set XML namespace for XML find - #if @xml.root.namespaces.namespace - # @ns = 'gpx:' + @xml.root.namespaces.namespace.href - #else - # @ns = 'gpx:http://www.topografix.com/GPX/1/1' # default to GPX 1.1 - #end reset_meta_data - bounds_element = (@xml.at("//metadata/bounds") rescue nil) + bounds_element = (@xml.at("metadata/bounds") rescue nil) if bounds_element @bounds.min_lat = get_bounds_attr_value(bounds_element, %w{ min_lat minlat minLat }) @bounds.min_lon = get_bounds_attr_value(bounds_element, %w{ min_lon minlon minLon}) @@ -74,18 +64,18 @@ module GPX get_bounds = true end - @time = Time.parse(@xml.at("//metadata/time").inner_text) rescue nil - @name = @xml.at("//metadata/name").inner_text rescue nil + @time = Time.parse(@xml.at("metadata/time").inner_text) rescue nil + @name = @xml.at("metadata/name").inner_text rescue nil @tracks = [] - @xml.search("//trk").each do |trk| + @xml.search("trk").each do |trk| trk = Track.new(:element => trk, :gpx_file => self) update_meta_data(trk, get_bounds) @tracks << trk end @waypoints = [] - @xml.search("//wpt").each { |wpt| @waypoints << Waypoint.new(:element => wpt, :gpx_file => self) } + @xml.search("wpt").each { |wpt| @waypoints << Waypoint.new(:element => wpt, :gpx_file => self) } @routes = [] - @xml.search("//rte").each { |rte| @routes << Route.new(:element => rte, :gpx_file => self) } + @xml.search("rte").each { |rte| @routes << Route.new(:element => rte, :gpx_file => self) } @tracks.delete_if { |t| t.empty? } calculate_duration @@ -205,52 +195,83 @@ module GPX @time = Time.now if(@time.nil? or update_time) @name ||= File.basename(filename) doc = generate_xml_doc - doc.save(filename, :indent => true) + File.open(filename, 'w') { |f| f.write(doc.to_xml) } end def to_s(update_time = true) @time = Time.now if(@time.nil? or update_time) doc = generate_xml_doc - doc.to_s + doc.to_xml + end + + def inspect + "<#{self.class.name}:...>" end private def generate_xml_doc - doc = Document.new - doc.root = Node.new('gpx') - gpx_elem = doc.root - gpx_elem['xsi'] = "http://www.w3.org/2001/XMLSchema-instance" - @version = '1.1' if (@version.nil? || !(['1.0', '1.1'].include?(@version))) # default to version 1.1 of the schema (only version 1.0 and 1.1 of the schema exist) - version_dir = @version.gsub('.','/') - gpx_elem['xmlns'] = @ns || "http://www.topografix.com/GPX/#{version_dir}" - gpx_elem['version'] = "#{@version}" - gpx_elem['creator'] = "GPX RubyGem #{GPX::VERSION} Copyright 2006-2009 Doug Fales -- http://gpx.rubyforge.org/" - gpx_elem['xsi:schemaLocation'] = "http://www.topografix.com/GPX/#{version_dir} http://www.topografix.com/GPX/#{version_dir}/gpx.xsd" + version = '1.1' + version_dir = version.gsub('.','/') - # setup the metadata elements - name_elem = Node.new('name') - name_elem << @name - time_elem = Node.new('time') - time_elem << @time.xmlschema + doc = Nokogiri::XML::Builder.new do |xml| + xml.gpx( + 'xsi' => "http://www.w3.org/2001/XMLSchema-instance", + 'version' => version.to_s, + 'creator' => "GPX RubyGem #{GPX::VERSION}", + 'xsi:schemaLocation' => "http://www.topografix.com/GPX/#{version_dir} http://www.topografix.com/GPX/#{version_dir}/gpx.xsd") \ + { + xml.metadata { + xml.name @name + xml.time @time.xmlschema + xml.bound( + minlat: bounds.min_lat, + minlon: bounds.min_lon, + maxlat: bounds.max_lat, + maxlon: bounds.max_lon, + ) + } - # version 1.0 of the schema doesn't support the metadata element, so push them straight to the root 'gpx' element - if (@version == '1.0') then - gpx_elem << name_elem - gpx_elem << time_elem - gpx_elem << bounds.to_xml - else - meta_data_elem = Node.new('metadata') - meta_data_elem << name_elem - meta_data_elem << time_elem - meta_data_elem << bounds.to_xml - gpx_elem << meta_data_elem - end + tracks.each do |t| + xml.trk { + xml.name t.name - tracks.each { |t| gpx_elem << t.to_xml } unless tracks.nil? - waypoints.each { |w| gpx_elem << w.to_xml } unless waypoints.nil? - routes.each { |r| gpx_elem << r.to_xml } unless routes.nil? + t.segments.each do |seg| + xml.trkseg { + seg.points.each do |p| + xml.trkpt(lat: p.lat, lon: p.lon) { + xml.time p.time.xmlschema unless p.time.nil? + xml.ele p.elevation unless p.elevation.nil? + } + end + } + end + } + end unless tracks.nil? - return doc + waypoints.each do |w| + xml.wpt(lat: w.lat, lon: w.lon) { + Waypoint::SUB_ELEMENTS.each do |sub_elem| + xml.send(sub_elem, w.send(sub_elem)) if w.respond_to?(sub_elem) && !w.send(sub_elem).nil? + end + } + end unless waypoints.nil? + + routes.each do |r| + xml.rte { + xml.name r.name + + r.points.each do |p| + xml.rtept(lat: p.lat, lon: p.lon) { + xml.time p.time.xmlschema unless p.time.nil? + xml.ele p.elevation unless p.elevation.nil? + } + end + } + end unless routes.nil? + } + end + + return doc end # Calculates and sets the duration attribute by subtracting the time on diff --git a/lib/gpx/magellan_track_log.rb b/lib/gpx/magellan_track_log.rb index 7c31009..b14c1b5 100644 --- a/lib/gpx/magellan_track_log.rb +++ b/lib/gpx/magellan_track_log.rb @@ -20,6 +20,9 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ + +require 'csv' + module GPX # This class will parse the lat/lon and time data from a Magellan track log, diff --git a/lib/gpx/point.rb b/lib/gpx/point.rb index b2d2912..e42076e 100644 --- a/lib/gpx/point.rb +++ b/lib/gpx/point.rb @@ -87,22 +87,5 @@ module GPX @lonr = (longitude * D_TO_R) @lon = longitude end - - # Convert this point to a XML::Node. - def to_xml(elem_name = 'trkpt') - pt = Node.new(elem_name) - pt['lat'] = lat.to_s - pt['lon'] = lon.to_s - unless time.nil? - time_elem = Node.new('time') - time_elem << time.xmlschema - pt << time_elem - end - elev = Node.new('ele') - elev << elevation - pt << elev - pt - end - end end diff --git a/lib/gpx/route.rb b/lib/gpx/route.rb index cf69eea..9e69ec8 100644 --- a/lib/gpx/route.rb +++ b/lib/gpx/route.rb @@ -34,9 +34,9 @@ module GPX if(opts[:gpx_file] and opts[:element]) rte_element = opts[:element] @gpx_file = opts[:gpx_file] - @name = rte_element.at("//name").inner_text + @name = rte_element.at("name").inner_text @points = [] - rte_element.search("//rtept").each do |point| + rte_element.search("rtept").each do |point| @points << Point.new(:element => point, :gpx_file => @gpx_file) end else @@ -55,16 +55,5 @@ module GPX def delete_area(area) points.delete_if{ |pt| area.contains? pt } end - - # Convert this Route to a XML::Node. - def to_xml - rte = Node.new('rte') - name_elem = Node.new('name') - name_elem << name - rte << name_elem - points.each { |rte_pt| rte << rte_pt.to_xml('rtept') } - rte - end - end end diff --git a/lib/gpx/segment.rb b/lib/gpx/segment.rb index 8e97571..3159ac2 100644 --- a/lib/gpx/segment.rb +++ b/lib/gpx/segment.rb @@ -46,8 +46,8 @@ module GPX if(opts[:element]) segment_element = opts[:element] last_pt = nil - if segment_element.is_a?(Hpricot::Elem) - segment_element.search("//trkpt").each do |trkpt| + if 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) unless pt.time.nil? @earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time) @@ -132,13 +132,6 @@ module GPX (points.nil? or (points.size == 0)) end - # Converts this Segment to a XML::Node object. - def to_xml - seg = Node.new('trkseg') - points.each { |pt| seg << pt.to_xml } - seg - end - # Prints out a nice summary of this Segment. def to_s result = "Track Segment\n" diff --git a/lib/gpx/track.rb b/lib/gpx/track.rb index bbfb72a..ce473eb 100644 --- a/lib/gpx/track.rb +++ b/lib/gpx/track.rb @@ -41,8 +41,8 @@ module GPX reset_meta_data if(opts[:element]) trk_element = opts[:element] - @name = (trk_element.at("//name").inner_text rescue "") - trk_element.search("//trkseg").each do |seg_element| + @name = (trk_element.at("name").inner_text rescue "") + trk_element.search("trkseg").each do |seg_element| seg = Segment.new(:element => seg_element, :track => self, :gpx_file => @gpx_file) update_meta_data(seg) @segments << seg @@ -100,18 +100,6 @@ module GPX (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. diff --git a/lib/gpx/waypoint.rb b/lib/gpx/waypoint.rb index 12684cc..9cba358 100644 --- a/lib/gpx/waypoint.rb +++ b/lib/gpx/waypoint.rb @@ -27,7 +27,7 @@ module GPX # not seen much use yet, since WalkingBoss does not use waypoints right now. class Waypoint < Point - SUB_ELEMENTS = %w{ magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions } + SUB_ELEMENTS = %w{ magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions ele} attr_reader :gpx_file SUB_ELEMENTS.each { |sub_el| attr_accessor sub_el.to_sym } @@ -72,25 +72,5 @@ module GPX end result end - - # Converts a waypoint to a XML::Node. - def to_xml(elem_name = 'wpt') - wpt = Node.new(elem_name) - wpt['lat'] = lat.to_s - wpt['lon'] = lon.to_s - SUB_ELEMENTS.each do |sub_element_name| - if(self.respond_to?(sub_element_name) and (!self.send(sub_element_name).nil?)) - sub_elem_node = Node.new(sub_element_name) - sub_elem_node << self.send(sub_element_name) - wpt << sub_elem_node - end - end - unless(self.elevation.nil?) - ele_node = Node.new('ele') - ele_node << self.elevation - wpt << ele_node - end - wpt - end end end