Skip to content

Instantly share code, notes, and snippets.

@tsnow
Last active August 29, 2015 14:01
Show Gist options
  • Save tsnow/7fcf8fdd5670a67f7961 to your computer and use it in GitHub Desktop.
Save tsnow/7fcf8fdd5670a67f7961 to your computer and use it in GitHub Desktop.

Uses https://github.com/tsnow/ruby-geometry

For: https://github.com/ridecharge/rc/pull/779

INSTALL: gem 'ruby-geometry', :git => 'https://github.com/tsnow/ruby-geometry.git'

BB=RGeo::Cartesian::BoundingBox.create_from_geometry(Zone.last.area); 
def BB.each_square(step)
  (BB.min_y .. BB.max_y).step(step){|y| 
    (BB.min_x .. BB.max_x).step(step){|x| 
      yield x, y 
    } 
  }
end
def test(resolution = 0.0001) # 10-meter resolution == 13k test points
loads = []
inmems = []
marshals = []
geoms = []
a = Zone.select('area as a').last.a
Benchmark.bm do |x|
  x.report('rgeo-inmem') { 
    @zone_last ||= Zone.last.reload; 
    BB.each_square(resolution) do |x,y|
      inmems.push @zone_last.contains?(RGEO_FACTORY.point(x,y)) 
    end 
  }
  x.report('ruby-geometry') { 
    BB.each_square(resolution) do |x,y|
      geoms.push ZonePointInPolygon.mysql_wkb_contains?(a,Point(x,y)) 
    end
  }
  x.report('rgeo-marshal') { 
    @zone_marshal ||= Marshal.dump(Zone.last); 
    BB.each_square(resolution) do |x,y|
      marshals.push Marshal.load(@zone_marshal).contains?(RGEO_FACTORY.point(x,y)) 
    end
  }
  x.report('rgeo-load') { 
    BB.each_square(resolution) do |x,y|
      loads.push Zone.last.reload.contains?(RGEO_FACTORY.point(x,y)) 
    end 
  }
end
lc = loads.group_by{|i| i}.map{|a,b| [a,b.length]}
ic = inmems.group_by{|i| i}.map{|a,b| [a,b.length]}
mc = marshals.group_by{|i| i}.map{|a,b| [a,b.length]}
gc = geoms.group_by{|i| i}.map{|a,b| [a,b.length]}
[loads == inmems, inmems == marshals, marshals == geoms, lc,ic,mc,gc]
end
=> [true, true, true]

count=0; BB.each_square(0.1){ count+= 1}; count
 => 1 
count=0; BB.each_square(0.01){ count+= 1}; count
 => 21 
count=0; BB.each_square(0.001){ count+= 1}; count
 => 1403 
count=0; BB.each_square(0.0001){ count+= 1}; count
 => 136278 
count=0; BB.each_square(0.00001){ count+= 1}; count
 => 13599168 

1.9.3-p545 :153 > test(0.01)
       user     system      total        real
rgeo-inmem  0.150000   0.010000   0.160000 (  0.164758)
ruby-geometry  0.010000   0.000000   0.010000 (  0.013450)
rgeo-marshal  0.970000   0.000000   0.970000 (  0.979673)
rgeo-load  1.130000   0.010000   1.140000 (  1.141370)
 => [true, true, true, [[false, 11], [true, 10]], [[false, 11], [true, 10]], [[false, 11], [true, 10]], [[false, 11], [true, 10]]] 
 

The ruby-geometry point-in-polygon is 27x faster than rgeo geos-ffi with loading, and 25x slower than rgeo geos-ffi once geos-ffi has loaded the geometry.

 
(for 10000 tests) # done seperately, using the same point for each test
               user        system      total        real
rgeo-load      53.530000   0.280000  53.810000 ( 54.276759)
rgeo-inmem     0.070000    0.000000   0.070000 (  0.080444)
rgeo-marshal   49.150000   0.110000  49.260000 ( 49.267587)
ruby-geometry  1.790000    0.000000   1.790000 (  1.787872)

1.9.3-p545 :184 > 1.79 / 0.07
 => 25.57142857142857 
1.9.3-p545 :185 > 49.0 / 1.79
 => 27.37430167597765 

1.9.3-p545 :154 > test(0.001)
       user     system      total        real
rgeo-inmem  0.110000   0.000000   0.110000 (  0.113264)
ruby-geometry  1.090000   0.010000   1.100000 (  1.094457)
rgeo-marshal 70.730000   0.220000  70.950000 ( 71.048172)
rgeo-load 75.940000   0.430000  76.370000 ( 77.029119)
 => [true, true, true, [[false, 432], [true, 971]], [[false, 432], [true, 971]], [[false, 432], [true, 971]], [[false, 432], [true, 971]]] 

1.9.3-p545 :155 > test(0.0001)
       user     system      total        real
rgeo-inmem 15.150000   0.030000  15.180000 ( 15.200548)
ruby-geometry 100.740000   0.200000 100.940000 (101.170947)
require 'ruby-geometry'
require 'rgeo'
class ZonePointInPolygon
# Use this.
def self.mysql_wkb_contains?(wkb, point)
from_mysql_wkb(wkb).any?{|i| i.contains?(point)}
end
def self.from_mysql_wkb(wkb)
::RGeo::WKRep::WKBParser.new(self).parse(wkb[4..-1])
end
# These constitute RGeo::WKRep::WKBParser's FactoryGenerator interface.
def self.call(*args)
self
end
# These constitute RGeo::Features::Factory's interface.
def self.polygon(outer_points, inner_points)
::Geometry::Polygon.new(outer_points)
end
def self.multi_polygon(polygons)
polygons
end
def self.line_string(points)
points
end
def self.point(x,y)
::Geometry::Point.new(x,y)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment