First stab at using libxml-ruby instead of REXML. I'm seeing the unit tests

finish in under 14 seconds.  That is compared to 2 minutes using REXML.
master
Doug Fales 2006-12-04 06:47:41 +00:00
parent fcf2b9e9a9
commit 022c7c6813
11 changed files with 99 additions and 92 deletions

2
README
View File

@ -33,7 +33,7 @@ work-in-progress than an attempt at full GPX compliance. The track side of the
library has seen much more use than the route/waypoint side, so if you're doing
something with routes or waypoints, you may need to tweak some things.
Since this code uses REXML to read an entire GPX file into memory, it is not
Since this code uses XML to read an entire GPX file into memory, it is not
the fastest possible solution for working with GPX data, especially if you are
working with tracks from several days or weeks.

View File

@ -21,7 +21,8 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
$:.unshift(File.dirname(__FILE__))
require 'rexml/document'
require 'rubygems'
require 'xml/libxml'
require 'date'
require 'time'
require 'csv'

View File

@ -44,11 +44,11 @@ module GPX
end
def to_xml
bnd = REXML::Element.new('bounds')
bnd.attributes['minlat'] = min_lat
bnd.attributes['minlon'] = min_lon
bnd.attributes['maxlat'] = max_lat
bnd.attributes['maxlon'] = max_lon
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

View File

@ -26,9 +26,10 @@ module GPX
# A common base class which provides a useful initializer method to many
# class in the GPX library.
class Base
include REXML
include XML
# This initializer can take a REXML::Element and scrape out any text
NS = 'gpx:http://www.topografix.com/GPX/1/1'
# This initializer can take an XML::Node and scrape out any text
# elements with the names given in the "text_elements" array. Each
# element found underneath "parent" with a name in "text_elements" causes
# an attribute to be initialized on the instance. This means you don't
@ -37,8 +38,8 @@ module GPX
# attributes to this method.
def instantiate_with_text_elements(parent, text_elements)
text_elements.each do |el|
unless parent.elements[el].nil?
val = parent.elements[el].text
unless parent.find(el).empty?
val = parent.find(el).first.content
code = <<-code
attr_accessor #{ el }
#{el}=#{val}

View File

@ -44,14 +44,15 @@ module GPX
@duration = 0
if(opts[:gpx_file])
gpx_file = opts[:gpx_file]
case gpx_file
when String
gpx_file = File.open(gpx_file)
end
#case gpx_file
#when String
# gpx_file = File.open(gpx_file)
#end
gpx_file = gpx_file.name if gpx_file.is_a?(File)
reset_meta_data
@xml = Document.new(gpx_file, :ignore_whitespace_nodes => :all)
@xml = Document.file(gpx_file)
bounds_element = (XPath.match(@xml, "/gpx/metadata/bounds").first rescue nil)
bounds_element = (@xml.find("//gpx:gpx/gpx:metadata/gpx:bounds", NS).to_a.first 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})
@ -61,13 +62,16 @@ module GPX
get_bounds = true
end
@tracks = XPath.match(@xml, "/gpx/trk").collect do |trk|
@tracks = []
@xml.find("//gpx:gpx/gpx:trk", NS).each do |trk|
trk = Track.new(:element => trk, :gpx_file => self)
update_meta_data(trk, get_bounds)
trk
@tracks << trk
end
@waypoints = XPath.match(@xml, "/gpx/wpt").collect { |wpt| Waypoint.new(:element => wpt, :gpx_file => self) }
@routes = XPath.match(@xml, "/gpx/rte").collect { |rte| Route.new(:element => rte, :gpx_file => self) }
@waypoints = []
@xml.find("//gpx:gpx/gpx:wpt", NS).each { |wpt| @waypoints << Waypoint.new(:element => wpt, :gpx_file => self) }
@routes = []
@xml.find("//gpx:gpx/gpx:rte", NS).each { |rte| @routes << Route.new(:element => rte, :gpx_file => self) }
@tracks.delete_if { |t| t.empty? }
@ -85,7 +89,7 @@ module GPX
def get_bounds_attr_value(el, possible_names)
result = nil
possible_names.each do |name|
result = el.attributes[name]
result = el[name]
break unless result.nil?
end
return (result.to_f rescue nil)
@ -184,32 +188,32 @@ module GPX
def write(filename)
doc = Document.new
gpx_elem = Element.new('gpx')
doc.add(gpx_elem)
gpx_elem.attributes['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
gpx_elem.attributes['xmlns'] = "http://www.topografix.com/GPX/1/1"
gpx_elem.attributes['version'] = "1.1"
gpx_elem.attributes['creator'] = "GPX RubyGem 0.1 Copyright 2006 Doug Fales -- http://walkingboss.com"
gpx_elem.attributes['xsi:schemaLocation'] = "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
doc.root = Node.new('gpx')
gpx_elem = doc.root
gpx_elem['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
gpx_elem['xmlns'] = "http://www.topografix.com/GPX/1/1"
gpx_elem['version'] = "1.1"
gpx_elem['creator'] = "GPX RubyGem 0.1 Copyright 2006 Doug Fales -- http://walkingboss.com"
gpx_elem['xsi:schemaLocation'] = "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
meta_data_elem = Element.new('metadata')
name_elem = Element.new('name')
name_elem.text = File.basename(filename)
meta_data_elem.elements << name_elem
meta_data_elem = Node.new('metadata')
name_elem = Node.new('name')
name_elem << File.basename(filename)
meta_data_elem << name_elem
time_elem = Element.new('time')
time_elem.text = Time.now.xmlschema
meta_data_elem.elements << time_elem
time_elem = Node.new('time')
time_elem << Time.now.xmlschema
meta_data_elem << time_elem
meta_data_elem.elements << bounds.to_xml
meta_data_elem << bounds.to_xml
gpx_elem.elements << meta_data_elem
gpx_elem << meta_data_elem
tracks.each { |t| gpx_elem.add_element t.to_xml } unless tracks.nil?
waypoints.each { |w| gpx_elem.add_element w.to_xml } unless waypoints.nil?
routes.each { |r| gpx_elem.add_element r.to_xml } unless routes.nil?
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?
File.open(filename, 'w') { |f| doc.write(f) }
doc.save(filename, true)
end
private

View File

@ -29,16 +29,16 @@ module GPX
# When you need to manipulate individual points, you can create a Point
# object with a latitude, a longitude, an elevation, and a time. In
# addition, you can pass a REXML element to this initializer, and the
# addition, you can pass an XML element to this initializer, and the
# relevant info will be parsed out.
def initialize(opts = {:lat => 0.0, :lon => 0.0, :elevation => 0.0, :time => Time.now } )
if (opts[:element])
elem = opts[:element]
@lat, @lon = elem.attributes["lat"].to_f, elem.attributes["lon"].to_f
@lat, @lon = elem["lat"].to_f, elem["lon"].to_f
@latr, @lonr = (D_TO_R * @lat), (D_TO_R * @lon)
#'-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
@time = (Time.xmlschema(elem.elements["time"].text) rescue nil)
@elevation = elem.elements["ele"].text.to_f if elem.elements["ele"]
@time = (Time.xmlschema(elem.find("gpx:time", NS).first.content) rescue nil)
@elevation = elem.find("gpx:ele", NS).first.content.to_f unless elem.find("gpx:ele", NS).empty?
else
@lat = opts[:lat]
@lon = opts[:lon]
@ -85,19 +85,19 @@ module GPX
@lon = longitude
end
# Convert this point to a REXML::Element.
# Convert this point to a XML::Node.
def to_xml(elem_name = 'trkpt')
pt = Element.new('trkpt')
pt.attributes['lat'] = lat
pt.attributes['lon'] = lon
pt = Node.new('trkpt')
pt['lat'] = lat.to_s
pt['lon'] = lon.to_s
unless time.nil?
time_elem = Element.new('time')
time_elem.text = time.xmlschema
pt.elements << time_elem
time_elem = Node.new('time')
time_elem << time.xmlschema
pt << time_elem
end
elev = Element.new('ele')
elev.text = elevation
pt.elements << elev
elev = Node.new('ele')
elev << elevation
pt << elev
pt
end

View File

@ -29,13 +29,13 @@ module GPX
attr_reader :points, :name, :gpx_file
# Initialize a Route from a REXML::Element.
# Initialize a Route from a XML::Node.
def initialize(opts = {})
rte_element = opts[:element]
@gpx_file = opts[:gpx_file]
@name = rte_element.elements["child::name"].text
@name = rte_element.find("child::gpx:name", NS).first.content
@points = []
XPath.each(rte_element, "child::rtept") do |point|
rte_element.find("child::gpx:rtept", NS).each do |point|
@points << Point.new(:element => point)
end
@ -51,13 +51,13 @@ module GPX
points.delete_if{ |pt| area.contains? pt }
end
# Convert this Route to a REXML::Element.
# Convert this Route to a XML::Node.
def to_xml
rte = Element.new('rte')
name_elem = Element.new('name')
name_elem.text = name
rte.elements << name_elem
points.each { |rte_pt| rte.elements << rte_pt.to_xml('rtept') }
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

View File

@ -31,7 +31,7 @@ module GPX
attr_reader :earliest_point, :latest_point, :bounds, :highest_point, :lowest_point, :distance
attr_accessor :points, :track
# If a REXML::Element object is passed-in, this will initialize a new
# If a XML::Node object is passed-in, this will initialize a new
# Segment based on its contents. Otherwise, a blank Segment is created.
def initialize(opts = {})
@track = opts[:track]
@ -45,8 +45,8 @@ module GPX
if(opts[:element])
segment_element = opts[:element]
last_pt = nil
unless segment_element.is_a?(Text)
XPath.each(segment_element, "child::trkpt") do |trkpt|
if segment_element.is_a?(XML::Node)
segment_element.find("child::gpx:trkpt", NS).each do |trkpt|
pt = TrackPoint.new(:element => trkpt, :segment => self)
unless pt.time.nil?
@earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time)
@ -131,10 +131,10 @@ module GPX
(points.nil? or (points.size == 0))
end
# Converts this Segment to a REXML::Element object.
# Converts this Segment to a XML::Node object.
def to_xml
seg = Element.new('trkseg')
points.each { |pt| seg.elements << pt.to_xml }
seg = Node.new('trkseg')
points.each { |pt| seg << pt.to_xml }
seg
end

View File

@ -32,7 +32,7 @@ module GPX
attr_reader :points, :bounds, :lowest_point, :highest_point, :distance
attr_accessor :segments, :name, :gpx_file
# Initialize a track from a REXML::Element, or, if no :element option is
# 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]
@ -41,8 +41,8 @@ module GPX
reset_meta_data
if(opts[:element])
trk_element = opts[:element]
@name = (trk_element.elements["child::name"].text rescue "")
XPath.each(trk_element, "child::trkseg") do |seg_element|
@name = (trk_element.find("child::gpx:name", NS).first.content rescue "")
trk_element.find("child::gpx:trkseg", NS).each do |seg_element|
seg = Segment.new(:element => seg_element, :track => self)
update_meta_data(seg)
@segments << seg
@ -101,14 +101,14 @@ module GPX
(points.nil? or points.size.zero?)
end
# Creates a new REXML::Element from the contents of this instance.
# Creates a new XML::Node from the contents of this instance.
def to_xml
trk= Element.new('trk')
name_elem = Element.new('name')
name_elem.text = name
trk.elements << name_elem
trk= Node.new('trk')
name_elem = Node.new('name')
name_elem << name
trk << name_elem
segments.each do |seg|
trk.elements << seg.to_xml
trk << seg.to_xml
end
trk
end

View File

@ -39,7 +39,7 @@ module GPX
def delete_area(area)
end
# Initializes a waypoint from a REXML::Element.
# Initializes a waypoint from a XML::Node.
def initialize(opts = {})
wpt_elem = opts[:element]
super(:element => wpt_elem)
@ -47,25 +47,25 @@ module GPX
@gpx_file = opts[:gpx_file]
end
# Converts a waypoint to a REXML::Element.
# Converts a waypoint to a XML::Node.
def to_xml
wpt = Element.new('wpt')
wpt = Node.new('wpt')
wpt.attributes['lat'] = lat
wpt.attributes['lon'] = lon
if self.respond_to? :name
name_elem = Element.new('name')
name_elem.text = self.name
wpt.elements << name_elem
name_elem = Node.new('name')
name_elem << self.name
wpt << name_elem
end
if self.respond_to? :sym
sym_elem = Element.new('sym')
sym_elem.text = self.sym
wpt.elements << sym_elem
sym_elem = Node.new('sym')
sym_elem << self.sym
wpt << sym_elem
end
if self.respond_to? :ele
elev_elem = Element.new('ele')
elev_elem.text = self.ele
wpt.elements << elev_elem
elev_elem = Node.new('ele')
elev_elem << self.ele
wpt << elev_elem
end
wpt
end

View File

@ -1,4 +1,5 @@
require 'test/unit'
require 'yaml'
require File.dirname(__FILE__) + '/../lib/gpx'
class TestSegment < Test::Unit::TestCase