Rework GPX import daemon to use libxml2 for speed.
authorTom Hughes <tom@compton.nu>
Wed, 30 Jul 2008 08:37:05 +0000 (08:37 +0000)
committerTom Hughes <tom@compton.nu>
Wed, 30 Jul 2008 08:37:05 +0000 (08:37 +0000)
app/models/trace.rb
config/initializers/libxml.rb
lib/gpx.rb [new file with mode: 0644]
lib/osm.rb

index d28c2c6f42f98a1bf6bff40b769c9707b0e71515..cb49ba62cbc8a8e38878aa5fce76b4422c389799 100644 (file)
@@ -174,7 +174,7 @@ class Trace < ActiveRecord::Base
   def import
     logger.info("GPX Import importing #{name} (#{id}) from #{user.email}")
 
-    gpx = OSM::GPXImporter.new(self.xml_file)
+    gpx = GPX::File.new(self.xml_file)
 
     f_lat = 0
     f_lon = 0
@@ -189,18 +189,18 @@ class Trace < ActiveRecord::Base
 
     gpx.points do |point|
       if first
-        f_lat = point['latitude']
-        f_lon = point['longitude']
+        f_lat = point.latitude
+        f_lon = point.longitude
         first = false
       end
 
       tp = Tracepoint.new
-      tp.lat = point['latitude'].to_f
-      tp.lon = point['longitude'].to_f
-      tp.altitude = point['altitude'].to_f
-      tp.timestamp = point['timestamp']
+      tp.lat = point.latitude
+      tp.lon = point.longitude
+      tp.altitude = point.altitude
+      tp.timestamp = point.timestamp
       tp.gpx_id = id
-      tp.trackid = point['segment'].to_i
+      tp.trackid = point.segment
       tp.save!
     end
 
@@ -217,8 +217,8 @@ class Trace < ActiveRecord::Base
 
       self.latitude = f_lat
       self.longitude = f_lon
-      self.large_picture = gpx.get_picture(min_lat, min_lon, max_lat, max_lon, gpx.actual_points)
-      self.icon_picture = gpx.get_icon(min_lat, min_lon, max_lat, max_lon)
+      self.large_picture = gpx.picture(min_lat, min_lon, max_lat, max_lon, gpx.actual_points)
+      self.icon_picture = gpx.icon(min_lat, min_lon, max_lat, max_lon)
       self.size = gpx.actual_points
       self.inserted = true
       self.save!
index 8725215a2c4b6b323dead55afc72fb64746204f4..a1870dbab8b4aaaacfe5e5169bcb40e622f3dc11 100644 (file)
@@ -1,5 +1,5 @@
 require 'rubygems'
-gem 'libxml-ruby', '>= 0.8.1'
+gem 'libxml-ruby', '>= 0.8.3'
 require 'libxml'
 
 LibXML::XML::Parser.register_error_handler do |message|
diff --git a/lib/gpx.rb b/lib/gpx.rb
new file mode 100644 (file)
index 0000000..76f0af1
--- /dev/null
@@ -0,0 +1,168 @@
+module GPX
+  class File
+    require "libxml"
+
+    include LibXML
+
+    attr_reader :possible_points
+    attr_reader :actual_points
+    attr_reader :tracksegs
+
+    def initialize(file)
+      @file = file
+    end
+
+    def points
+      @possible_points = 0
+      @actual_points = 0
+      @tracksegs = 0
+
+      @file.rewind
+
+      reader = XML::Reader.io(@file)
+
+      point = nil
+
+      while reader.read > 0
+        if reader.node_type == XML::Reader::TYPE_ELEMENT
+          if reader.name == "trkpt"
+            point = TrkPt.new(@tracksegs, reader["lat"].to_f, reader["lon"].to_f)
+            @possible_points += 1
+          elsif reader.name == "ele" and point
+            point.altitude = reader.read_string.to_f
+          elsif reader.name == "time" and point
+            point.timestamp = DateTime.parse(reader.read_string)
+          end
+        elsif reader.node_type == XML::Reader::TYPE_END_ELEMENT
+          if reader.name == "trkpt" and point and point.valid?
+            point.altitude ||= 0
+            yield point
+            @actual_points += 1
+          elsif reader.name == "trkseg"
+            @tracksegs += 1
+          end
+        end
+      end
+    end
+
+    def picture(min_lat, min_lon, max_lat, max_lon, num_points)
+      frames = 10
+      width = 250
+      height = 250
+      proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, width, height)
+
+      linegc = Magick::Draw.new
+      linegc.stroke_linejoin('miter')
+      linegc.stroke_width(1)
+      linegc.stroke('#BBBBBB')
+      linegc.fill('#BBBBBB')
+
+      highlightgc = Magick::Draw.new
+      highlightgc.stroke_linejoin('miter')
+      highlightgc.stroke_width(3)
+      highlightgc.stroke('#000000')
+      highlightgc.fill('#000000')
+
+      images = []
+
+      frames.times do
+        image = Magick::Image.new(width, height) do |image|
+          image.background_color = 'white'
+          image.format = 'GIF'
+        end
+
+        images << image
+      end
+
+      oldpx = 0.0
+      oldpy = 0.0
+
+      first = true
+
+      m = 0
+      mm = 0
+      points do |p|
+        px = proj.x(p.longitude)
+        py = proj.y(p.latitude)
+
+        if m > 0
+          frames.times do |n|
+            if n == mm
+              gc = highlightgc.dup
+            else
+              gc = linegc.dup
+            end
+
+            gc.line(px, py, oldpx, oldpy)
+
+            gc.draw(images[n])
+          end
+        end
+
+        m += 1
+        if m > num_points.to_f / frames.to_f * (mm+1)
+          mm += 1
+        end
+
+        oldpy = py
+        oldpx = px
+      end
+
+      il = Magick::ImageList.new
+
+      images.each do |f|
+        il << f
+      end
+
+      il.delay = 50
+      il.format = 'GIF'
+
+      return il.to_blob
+    end
+
+    def icon(min_lat, min_lon, max_lat, max_lon)
+      width = 50
+      height = 50
+      proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, width, height)
+
+      gc = Magick::Draw.new
+      gc.stroke_linejoin('miter')
+      gc.stroke_width(1)
+      gc.stroke('#000000')
+      gc.fill('#000000')
+
+      image = Magick::Image.new(width, height) do |image|
+        image.background_color = 'white'
+        image.format = 'GIF'
+      end
+
+      oldpx = 0.0
+      oldpy = 0.0
+
+      first = true
+
+      points do |p|
+        px = proj.x(p.longitude)
+        py = proj.y(p.latitude)
+
+        gc.dup.line(px, py, oldpx, oldpy).draw(image) unless first
+
+        first = false
+        oldpy = py
+        oldpx = px
+      end
+
+      return image.to_blob
+    end
+  end
+
+private
+
+  class TrkPt < Struct.new(:segment, :latitude, :longitude, :altitude, :timestamp)
+    def valid?
+      self.latitude and self.longitude and self.timestamp and
+      self.latitude >= -90 and self.latitude <= 90 and
+      self.longitude >= -180 and self.longitude <= 180
+    end
+  end
+end
index bd935102658576295b2670ca46a8b28de89e94b4..9c271607dc0160d1d7e5b2b78138dc04ed2dedf0 100644 (file)
@@ -70,199 +70,6 @@ module OSM
     end
   end
 
-
-  # This piece of magic reads a GPX with SAX and spits out
-  # lat/lng and stuff
-  #
-  # This would print every latitude value:
-  #
-  # gpx = OSM::GPXImporter.new('somefile.gpx')
-  # gpx.points {|p| puts p['latitude']}
-  class GPXImporter
-    # FIXME swap REXML for libXML
-    attr_reader :possible_points
-    attr_reader :actual_points
-    attr_reader :tracksegs
-
-    def initialize(file)
-      @file = file
-    end
-
-    def points
-      @possible_points = 0
-      @actual_points = 0
-      @tracksegs = 0
-
-      lat = -1
-      lon = -1
-      ele = -1
-      date = DateTime.now();
-      gotlatlon = false
-      gotele = false
-      gotdate = false
-
-      @file.rewind
-
-      parser = REXML::Parsers::SAX2Parser.new(@file)
-
-      parser.listen( :start_element,  %w{ trkpt }) do |uri,localname,qname,attributes| 
-        lat = attributes['lat'].to_f
-        lon = attributes['lon'].to_f
-        gotlatlon = true
-        gotele = false
-        gotdate = false
-        @possible_points += 1
-      end
-
-      parser.listen( :characters, %w{ ele } ) do |text|
-        ele = text
-        gotele = true
-      end
-
-      parser.listen( :characters, %w{ time } ) do |text|
-        if text && text != ''
-          begin
-            date = DateTime.parse(text)
-            gotdate = true
-          rescue
-          end
-        end
-      end
-
-      parser.listen( :end_element, %w{ trkseg } ) do |uri, localname, qname|
-        @tracksegs += 1
-      end
-
-      parser.listen( :end_element, %w{ trkpt } ) do |uri,localname,qname|
-        if gotlatlon && gotdate
-          ele = '0' unless gotele
-          if lat < 90 && lat > -90 && lon > -180 && lon < 180
-            @actual_points += 1
-            yield Hash['latitude' => lat, 'longitude' => lon, 'timestamp' => date, 'altitude' => ele, 'segment' => @tracksegs]
-          end
-        end
-        gotlatlon = false
-        gotele = false
-        gotdate = false
-      end
-
-      parser.parse
-    end
-
-    def get_picture(min_lat, min_lon, max_lat, max_lon, num_points)
-      #puts "getting picfor bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
-      frames = 10
-      width = 250
-      height = 250
-      proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, width, height)
-
-      linegc = Magick::Draw.new
-      linegc.stroke_linejoin('miter')
-      linegc.stroke_width(1)
-      linegc.stroke('#BBBBBB')
-      linegc.fill('#BBBBBB')
-
-      highlightgc = Magick::Draw.new
-      highlightgc.stroke_linejoin('miter')
-      highlightgc.stroke_width(3)
-      highlightgc.stroke('#000000')
-      highlightgc.fill('#000000')
-
-      images = []
-
-      frames.times do
-        image = Magick::Image.new(width, height) do |image|
-          image.background_color = 'white'
-          image.format = 'GIF'
-        end
-
-        images << image
-      end
-
-      oldpx = 0.0
-      oldpy = 0.0
-
-      first = true
-
-      m = 0
-      mm = 0
-      points do |p|
-        px = proj.x(p['longitude'])
-        py = proj.y(p['latitude'])
-
-        if m > 0
-          frames.times do |n|
-            if n == mm
-              gc = highlightgc.dup
-            else
-              gc = linegc.dup
-            end
-
-            gc.line(px, py, oldpx, oldpy)
-
-            gc.draw(images[n])
-          end
-        end
-
-        m += 1
-        if m > num_points.to_f / frames.to_f * (mm+1)
-          mm += 1
-        end
-
-        oldpy = py
-        oldpx = px
-      end
-
-      il = Magick::ImageList.new
-
-      images.each do |f|
-        il << f
-      end
-
-      il.delay = 50
-      il.format = 'GIF'
-
-      return il.to_blob
-    end
-
-    def get_icon(min_lat, min_lon, max_lat, max_lon)
-      #puts "getting icon for bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}"
-      width = 50
-      height = 50
-      proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, width, height)
-
-      gc = Magick::Draw.new
-      gc.stroke_linejoin('miter')
-      gc.stroke_width(1)
-      gc.stroke('#000000')
-      gc.fill('#000000')
-
-      image = Magick::Image.new(width, height) do |image|
-        image.background_color = 'white'
-        image.format = 'GIF'
-      end
-
-      oldpx = 0.0
-      oldpy = 0.0
-
-      first = true
-
-      points do |p|
-        px = proj.x(p['longitude'])
-        py = proj.y(p['latitude'])
-
-        gc.dup.line(px, py, oldpx, oldpy).draw(image) unless first
-
-        first = false
-        oldpy = py
-        oldpx = px
-      end
-
-      return image.to_blob
-    end
-
-  end
-
   class GreatCircle
     include Math