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
|
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.
|
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
|
the fastest possible solution for working with GPX data, especially if you are
|
||||||
working with tracks from several days or weeks.
|
working with tracks from several days or weeks.
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
#++
|
#++
|
||||||
$:.unshift(File.dirname(__FILE__))
|
$:.unshift(File.dirname(__FILE__))
|
||||||
require 'rexml/document'
|
require 'rubygems'
|
||||||
|
require 'xml/libxml'
|
||||||
require 'date'
|
require 'date'
|
||||||
require 'time'
|
require 'time'
|
||||||
require 'csv'
|
require 'csv'
|
||||||
|
|
|
@ -44,11 +44,11 @@ module GPX
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_xml
|
def to_xml
|
||||||
bnd = REXML::Element.new('bounds')
|
bnd = XML::Node.new('bounds')
|
||||||
bnd.attributes['minlat'] = min_lat
|
bnd['minlat'] = min_lat.to_s
|
||||||
bnd.attributes['minlon'] = min_lon
|
bnd['minlon'] = min_lon.to_s
|
||||||
bnd.attributes['maxlat'] = max_lat
|
bnd['maxlat'] = max_lat.to_s
|
||||||
bnd.attributes['maxlon'] = max_lon
|
bnd['maxlon'] = max_lon.to_s
|
||||||
bnd
|
bnd
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,10 @@ module GPX
|
||||||
# A common base class which provides a useful initializer method to many
|
# A common base class which provides a useful initializer method to many
|
||||||
# class in the GPX library.
|
# class in the GPX library.
|
||||||
class Base
|
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
|
# elements with the names given in the "text_elements" array. Each
|
||||||
# element found underneath "parent" with a name in "text_elements" causes
|
# element found underneath "parent" with a name in "text_elements" causes
|
||||||
# an attribute to be initialized on the instance. This means you don't
|
# an attribute to be initialized on the instance. This means you don't
|
||||||
|
@ -37,8 +38,8 @@ module GPX
|
||||||
# attributes to this method.
|
# attributes to this method.
|
||||||
def instantiate_with_text_elements(parent, text_elements)
|
def instantiate_with_text_elements(parent, text_elements)
|
||||||
text_elements.each do |el|
|
text_elements.each do |el|
|
||||||
unless parent.elements[el].nil?
|
unless parent.find(el).empty?
|
||||||
val = parent.elements[el].text
|
val = parent.find(el).first.content
|
||||||
code = <<-code
|
code = <<-code
|
||||||
attr_accessor #{ el }
|
attr_accessor #{ el }
|
||||||
#{el}=#{val}
|
#{el}=#{val}
|
||||||
|
|
|
@ -44,14 +44,15 @@ module GPX
|
||||||
@duration = 0
|
@duration = 0
|
||||||
if(opts[:gpx_file])
|
if(opts[:gpx_file])
|
||||||
gpx_file = opts[:gpx_file]
|
gpx_file = opts[:gpx_file]
|
||||||
case gpx_file
|
#case gpx_file
|
||||||
when String
|
#when String
|
||||||
gpx_file = File.open(gpx_file)
|
# gpx_file = File.open(gpx_file)
|
||||||
end
|
#end
|
||||||
|
gpx_file = gpx_file.name if gpx_file.is_a?(File)
|
||||||
reset_meta_data
|
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
|
if bounds_element
|
||||||
@bounds.min_lat = get_bounds_attr_value(bounds_element, %w{ min_lat minlat minLat })
|
@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})
|
@bounds.min_lon = get_bounds_attr_value(bounds_element, %w{ min_lon minlon minLon})
|
||||||
|
@ -61,13 +62,16 @@ module GPX
|
||||||
get_bounds = true
|
get_bounds = true
|
||||||
end
|
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)
|
trk = Track.new(:element => trk, :gpx_file => self)
|
||||||
update_meta_data(trk, get_bounds)
|
update_meta_data(trk, get_bounds)
|
||||||
trk
|
@tracks << trk
|
||||||
end
|
end
|
||||||
@waypoints = XPath.match(@xml, "/gpx/wpt").collect { |wpt| Waypoint.new(:element => wpt, :gpx_file => self) }
|
@waypoints = []
|
||||||
@routes = XPath.match(@xml, "/gpx/rte").collect { |rte| Route.new(:element => rte, :gpx_file => self) }
|
@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? }
|
@tracks.delete_if { |t| t.empty? }
|
||||||
|
|
||||||
|
@ -85,7 +89,7 @@ module GPX
|
||||||
def get_bounds_attr_value(el, possible_names)
|
def get_bounds_attr_value(el, possible_names)
|
||||||
result = nil
|
result = nil
|
||||||
possible_names.each do |name|
|
possible_names.each do |name|
|
||||||
result = el.attributes[name]
|
result = el[name]
|
||||||
break unless result.nil?
|
break unless result.nil?
|
||||||
end
|
end
|
||||||
return (result.to_f rescue nil)
|
return (result.to_f rescue nil)
|
||||||
|
@ -184,32 +188,32 @@ module GPX
|
||||||
def write(filename)
|
def write(filename)
|
||||||
|
|
||||||
doc = Document.new
|
doc = Document.new
|
||||||
gpx_elem = Element.new('gpx')
|
doc.root = Node.new('gpx')
|
||||||
doc.add(gpx_elem)
|
gpx_elem = doc.root
|
||||||
gpx_elem.attributes['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
|
gpx_elem['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
gpx_elem.attributes['xmlns'] = "http://www.topografix.com/GPX/1/1"
|
gpx_elem['xmlns'] = "http://www.topografix.com/GPX/1/1"
|
||||||
gpx_elem.attributes['version'] = "1.1"
|
gpx_elem['version'] = "1.1"
|
||||||
gpx_elem.attributes['creator'] = "GPX RubyGem 0.1 Copyright 2006 Doug Fales -- http://walkingboss.com"
|
gpx_elem['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"
|
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')
|
meta_data_elem = Node.new('metadata')
|
||||||
name_elem = Element.new('name')
|
name_elem = Node.new('name')
|
||||||
name_elem.text = File.basename(filename)
|
name_elem << File.basename(filename)
|
||||||
meta_data_elem.elements << name_elem
|
meta_data_elem << name_elem
|
||||||
|
|
||||||
time_elem = Element.new('time')
|
time_elem = Node.new('time')
|
||||||
time_elem.text = Time.now.xmlschema
|
time_elem << Time.now.xmlschema
|
||||||
meta_data_elem.elements << time_elem
|
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?
|
tracks.each { |t| gpx_elem << t.to_xml } unless tracks.nil?
|
||||||
waypoints.each { |w| gpx_elem.add_element w.to_xml } unless waypoints.nil?
|
waypoints.each { |w| gpx_elem << w.to_xml } unless waypoints.nil?
|
||||||
routes.each { |r| gpx_elem.add_element r.to_xml } unless routes.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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -29,16 +29,16 @@ module GPX
|
||||||
|
|
||||||
# When you need to manipulate individual points, you can create a Point
|
# When you need to manipulate individual points, you can create a Point
|
||||||
# object with a latitude, a longitude, an elevation, and a time. In
|
# 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.
|
# relevant info will be parsed out.
|
||||||
def initialize(opts = {:lat => 0.0, :lon => 0.0, :elevation => 0.0, :time => Time.now } )
|
def initialize(opts = {:lat => 0.0, :lon => 0.0, :elevation => 0.0, :time => Time.now } )
|
||||||
if (opts[:element])
|
if (opts[:element])
|
||||||
elem = 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)
|
@latr, @lonr = (D_TO_R * @lat), (D_TO_R * @lon)
|
||||||
#'-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
|
#'-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
|
||||||
@time = (Time.xmlschema(elem.elements["time"].text) rescue nil)
|
@time = (Time.xmlschema(elem.find("gpx:time", NS).first.content) rescue nil)
|
||||||
@elevation = elem.elements["ele"].text.to_f if elem.elements["ele"]
|
@elevation = elem.find("gpx:ele", NS).first.content.to_f unless elem.find("gpx:ele", NS).empty?
|
||||||
else
|
else
|
||||||
@lat = opts[:lat]
|
@lat = opts[:lat]
|
||||||
@lon = opts[:lon]
|
@lon = opts[:lon]
|
||||||
|
@ -85,19 +85,19 @@ module GPX
|
||||||
@lon = longitude
|
@lon = longitude
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convert this point to a REXML::Element.
|
# Convert this point to a XML::Node.
|
||||||
def to_xml(elem_name = 'trkpt')
|
def to_xml(elem_name = 'trkpt')
|
||||||
pt = Element.new('trkpt')
|
pt = Node.new('trkpt')
|
||||||
pt.attributes['lat'] = lat
|
pt['lat'] = lat.to_s
|
||||||
pt.attributes['lon'] = lon
|
pt['lon'] = lon.to_s
|
||||||
unless time.nil?
|
unless time.nil?
|
||||||
time_elem = Element.new('time')
|
time_elem = Node.new('time')
|
||||||
time_elem.text = time.xmlschema
|
time_elem << time.xmlschema
|
||||||
pt.elements << time_elem
|
pt << time_elem
|
||||||
end
|
end
|
||||||
elev = Element.new('ele')
|
elev = Node.new('ele')
|
||||||
elev.text = elevation
|
elev << elevation
|
||||||
pt.elements << elev
|
pt << elev
|
||||||
pt
|
pt
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -29,13 +29,13 @@ module GPX
|
||||||
|
|
||||||
attr_reader :points, :name, :gpx_file
|
attr_reader :points, :name, :gpx_file
|
||||||
|
|
||||||
# Initialize a Route from a REXML::Element.
|
# Initialize a Route from a XML::Node.
|
||||||
def initialize(opts = {})
|
def initialize(opts = {})
|
||||||
rte_element = opts[:element]
|
rte_element = opts[:element]
|
||||||
@gpx_file = opts[:gpx_file]
|
@gpx_file = opts[:gpx_file]
|
||||||
@name = rte_element.elements["child::name"].text
|
@name = rte_element.find("child::gpx:name", NS).first.content
|
||||||
@points = []
|
@points = []
|
||||||
XPath.each(rte_element, "child::rtept") do |point|
|
rte_element.find("child::gpx:rtept", NS).each do |point|
|
||||||
@points << Point.new(:element => point)
|
@points << Point.new(:element => point)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -51,13 +51,13 @@ module GPX
|
||||||
points.delete_if{ |pt| area.contains? pt }
|
points.delete_if{ |pt| area.contains? pt }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convert this Route to a REXML::Element.
|
# Convert this Route to a XML::Node.
|
||||||
def to_xml
|
def to_xml
|
||||||
rte = Element.new('rte')
|
rte = Node.new('rte')
|
||||||
name_elem = Element.new('name')
|
name_elem = Node.new('name')
|
||||||
name_elem.text = name
|
name_elem << name
|
||||||
rte.elements << name_elem
|
rte << name_elem
|
||||||
points.each { |rte_pt| rte.elements << rte_pt.to_xml('rtept') }
|
points.each { |rte_pt| rte << rte_pt.to_xml('rtept') }
|
||||||
rte
|
rte
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ module GPX
|
||||||
attr_reader :earliest_point, :latest_point, :bounds, :highest_point, :lowest_point, :distance
|
attr_reader :earliest_point, :latest_point, :bounds, :highest_point, :lowest_point, :distance
|
||||||
attr_accessor :points, :track
|
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.
|
# Segment based on its contents. Otherwise, a blank Segment is created.
|
||||||
def initialize(opts = {})
|
def initialize(opts = {})
|
||||||
@track = opts[:track]
|
@track = opts[:track]
|
||||||
|
@ -45,8 +45,8 @@ module GPX
|
||||||
if(opts[:element])
|
if(opts[:element])
|
||||||
segment_element = opts[:element]
|
segment_element = opts[:element]
|
||||||
last_pt = nil
|
last_pt = nil
|
||||||
unless segment_element.is_a?(Text)
|
if segment_element.is_a?(XML::Node)
|
||||||
XPath.each(segment_element, "child::trkpt") do |trkpt|
|
segment_element.find("child::gpx:trkpt", NS).each do |trkpt|
|
||||||
pt = TrackPoint.new(:element => trkpt, :segment => self)
|
pt = TrackPoint.new(:element => trkpt, :segment => self)
|
||||||
unless pt.time.nil?
|
unless pt.time.nil?
|
||||||
@earliest_point = pt if(@earliest_point.nil? or pt.time < @earliest_point.time)
|
@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))
|
(points.nil? or (points.size == 0))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Converts this Segment to a REXML::Element object.
|
# Converts this Segment to a XML::Node object.
|
||||||
def to_xml
|
def to_xml
|
||||||
seg = Element.new('trkseg')
|
seg = Node.new('trkseg')
|
||||||
points.each { |pt| seg.elements << pt.to_xml }
|
points.each { |pt| seg << pt.to_xml }
|
||||||
seg
|
seg
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ module GPX
|
||||||
attr_reader :points, :bounds, :lowest_point, :highest_point, :distance
|
attr_reader :points, :bounds, :lowest_point, :highest_point, :distance
|
||||||
attr_accessor :segments, :name, :gpx_file
|
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.
|
# passed, initialize a blank Track object.
|
||||||
def initialize(opts = {})
|
def initialize(opts = {})
|
||||||
@gpx_file = opts[:gpx_file]
|
@gpx_file = opts[:gpx_file]
|
||||||
|
@ -41,8 +41,8 @@ module GPX
|
||||||
reset_meta_data
|
reset_meta_data
|
||||||
if(opts[:element])
|
if(opts[:element])
|
||||||
trk_element = opts[:element]
|
trk_element = opts[:element]
|
||||||
@name = (trk_element.elements["child::name"].text rescue "")
|
@name = (trk_element.find("child::gpx:name", NS).first.content rescue "")
|
||||||
XPath.each(trk_element, "child::trkseg") do |seg_element|
|
trk_element.find("child::gpx:trkseg", NS).each do |seg_element|
|
||||||
seg = Segment.new(:element => seg_element, :track => self)
|
seg = Segment.new(:element => seg_element, :track => self)
|
||||||
update_meta_data(seg)
|
update_meta_data(seg)
|
||||||
@segments << seg
|
@segments << seg
|
||||||
|
@ -101,14 +101,14 @@ module GPX
|
||||||
(points.nil? or points.size.zero?)
|
(points.nil? or points.size.zero?)
|
||||||
end
|
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
|
def to_xml
|
||||||
trk= Element.new('trk')
|
trk= Node.new('trk')
|
||||||
name_elem = Element.new('name')
|
name_elem = Node.new('name')
|
||||||
name_elem.text = name
|
name_elem << name
|
||||||
trk.elements << name_elem
|
trk << name_elem
|
||||||
segments.each do |seg|
|
segments.each do |seg|
|
||||||
trk.elements << seg.to_xml
|
trk << seg.to_xml
|
||||||
end
|
end
|
||||||
trk
|
trk
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,7 +39,7 @@ module GPX
|
||||||
def delete_area(area)
|
def delete_area(area)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Initializes a waypoint from a REXML::Element.
|
# Initializes a waypoint from a XML::Node.
|
||||||
def initialize(opts = {})
|
def initialize(opts = {})
|
||||||
wpt_elem = opts[:element]
|
wpt_elem = opts[:element]
|
||||||
super(:element => wpt_elem)
|
super(:element => wpt_elem)
|
||||||
|
@ -47,25 +47,25 @@ module GPX
|
||||||
@gpx_file = opts[:gpx_file]
|
@gpx_file = opts[:gpx_file]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Converts a waypoint to a REXML::Element.
|
# Converts a waypoint to a XML::Node.
|
||||||
def to_xml
|
def to_xml
|
||||||
wpt = Element.new('wpt')
|
wpt = Node.new('wpt')
|
||||||
wpt.attributes['lat'] = lat
|
wpt.attributes['lat'] = lat
|
||||||
wpt.attributes['lon'] = lon
|
wpt.attributes['lon'] = lon
|
||||||
if self.respond_to? :name
|
if self.respond_to? :name
|
||||||
name_elem = Element.new('name')
|
name_elem = Node.new('name')
|
||||||
name_elem.text = self.name
|
name_elem << self.name
|
||||||
wpt.elements << name_elem
|
wpt << name_elem
|
||||||
end
|
end
|
||||||
if self.respond_to? :sym
|
if self.respond_to? :sym
|
||||||
sym_elem = Element.new('sym')
|
sym_elem = Node.new('sym')
|
||||||
sym_elem.text = self.sym
|
sym_elem << self.sym
|
||||||
wpt.elements << sym_elem
|
wpt << sym_elem
|
||||||
end
|
end
|
||||||
if self.respond_to? :ele
|
if self.respond_to? :ele
|
||||||
elev_elem = Element.new('ele')
|
elev_elem = Node.new('ele')
|
||||||
elev_elem.text = self.ele
|
elev_elem << self.ele
|
||||||
wpt.elements << elev_elem
|
wpt << elev_elem
|
||||||
end
|
end
|
||||||
wpt
|
wpt
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require 'test/unit'
|
require 'test/unit'
|
||||||
|
require 'yaml'
|
||||||
require File.dirname(__FILE__) + '/../lib/gpx'
|
require File.dirname(__FILE__) + '/../lib/gpx'
|
||||||
|
|
||||||
class TestSegment < Test::Unit::TestCase
|
class TestSegment < Test::Unit::TestCase
|
||||||
|
|
Loading…
Reference in New Issue