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
parent
fcf2b9e9a9
commit
022c7c6813
2
README
2
README
|
@ -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.
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'test/unit'
|
||||
require 'yaml'
|
||||
require File.dirname(__FILE__) + '/../lib/gpx'
|
||||
|
||||
class TestSegment < Test::Unit::TestCase
|
||||
|
|
Loading…
Reference in New Issue