优化ActiveRecord Point-in-Polygon搜索

时间:2009-09-15 16:44:12

标签: ruby-on-rails activerecord geometry nokogiri polygon

以下PiP搜索是为一个项目构建的,该项目允许用户通过地址或纬度/经度(http://staging.placeanddisplaced.org)找到他们的纽约市政府区域。它有效,但它有点慢,特别是在搜索具有复杂多边形的区域时。谁能给我一些关于优化这段代码的指示?

我有一个想法是运行point_in_polygon?每个多边形的简化版本的方法,即更少的坐标。这意味着更少的处理时间,但也降低了准确性..想法?

class DistrictPolygonsController < ApplicationController

  def index

    ...

    if coordinates?
      @district_polygons = DistrictPolygon.
      coordinates_within_bounding_box(params[:lat], params[:lng]).
      find(:all, :include => :district, :select => select).
      select { |dp| dp.contains_coordinates?(params[:lat], params[:lng]) }
    else
      @district_polygons = DistrictPolygon.find(:all, :include => :district, :select => select)
    end

    ...

  end

end

class DistrictPolygon < ActiveRecord::Base

  named_scope :coordinates_within_bounding_box, lambda { |lat,lng| { :conditions => ['min_lat<? AND max_lat>? AND min_lng<? AND max_lng>?', lat.to_f, lat.to_f, lng.to_f, lng.to_f] } }
  named_scope :with_district_type, lambda { |t| { :conditions => ['district_type=?', t] } }

  before_save :get_bounding_box_from_geometry

  def get_bounding_box_from_geometry
    # return true unless self.new_record? || self.geometry_changed? || self.bounds_unknown?
    self.min_lat = all_lats.min
    self.max_lat = all_lats.max
    self.min_lng = all_lngs.min
    self.max_lng = all_lngs.max
    true # object won't save without this return
  end

  def bounds_unknown?
    %w(min_lat max_lat min_lng max_lng).any? {|b| self[b.to_sym].blank? }
  end

  def bounds_known?; !bounds_unknown?; end

  # Returns an array of XML objects containing each polygons coordinates
  def polygons
    Nokogiri::XML(self.geometry).search("Polygon/outerBoundaryIs/LinearRing/coordinates")
  end

  def multi_geometric?
    Nokogiri::XML(self.geometry).search("MultiGeometry").size > 0
  end

  # Returns an array of [lng,lat] arrays
  def all_coordinates
    pairs = []
    polygons.map do |polygon|
      polygon.content.split("\n").map do |coord|
        # Get rid of third 'altitude' param from coordinate..
        pair = coord.strip.split(",")[0..1].map(&:to_f)
        # Don't let any nils, blanks, or zeros through..
        pairs << pair unless pair.any? {|point| point.blank? || point.zero? }
      end
    end
    pairs
  end

  # All latitudes, regardless of MultiPolygonal geometry
  def all_lats
    all_coordinates.map(&:last).reject{|lat| lat.blank? || lat.zero?}    
  end

  # All longitudes, regardless of MultiPolygonal geometry  
  def all_lngs
    all_coordinates.map(&:first).reject{|lng| lng.blank? || lng.zero?}
  end

  # Check to see if coordinates are in the rectangular bounds of this district
  def contains_coordinates?(lat, lng)
    return false unless coordinates_within_bounding_box?(lat.to_f, lng.to_f)
    polygons.any? { |polygon| DistrictPolygon.point_in_polygon?(all_lats, all_lngs, lat.to_f, lng.to_f) }
  end

  def coordinates_within_bounding_box?(lat, lng)
    return false if (max_lat > lat.to_f == min_lat > lat.to_f) # Not between lats
    return false if (max_lng > lng.to_f == min_lng > lng.to_f) # Not between lngs
    true
  end

  # This algorithm came from http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
  def self.point_in_polygon?(x_points, y_points, x_target, y_target)
    num_points = x_points.size
    j = num_points-1
    c = false
    for i in 0...num_points do
      c = !c if ( ((y_points[i]>y_target) != (y_points[j]>y_target)) && (x_target < (x_points[j]-x_points[i]) * (y_target-y_points[i]) / (y_points[j]-y_points[i]) + x_points[i]) )
      j = i
    end
    return c
  end

end

3 个答案:

答案 0 :(得分:1)

如果您的运行时间更长,更复杂的形状,它表明性能是在point_in_polygon的O(n)循环中?

是否回顾了这个假设?

如果性能至关重要,请考虑实施与本机代码完全相同的算法。

答案 1 :(得分:0)

我怀疑你可以把大部分工作都推到数据库中。 PostgreSQL具有PostGIS插件,可以执行空间感知查询。

PostGIS:http://postgis.refractions.net/ 文档:http://postgis.refractions.net/documentation/manual-1.4/

这打破了数据库可移植性的概念,但如果性能至关重要,则可能是值得的。

答案 2 :(得分:0)

除了算法之外,将多边形数据保留在本地内存中并使用静态类型编译语言重新编码它可能会导致100x-1000x increase in speed