1

以下 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
4

3 回答 3

1

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

剖析是否支持该假设?

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

于 2009-09-15T16:46:42.813 回答
0

抛开算法不谈,将多边形数据保存在本地内存中并用静态类型的编译语言对其进行重新编码可能会导致速度提高 100 倍至 1000 倍

于 2009-09-15T17:30:52.927 回答
0

我怀疑您可能能够将大部分工作推入数据库。PostgreSQL 有 PostGIS 插件,可以执行空间感知查询。

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

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

于 2009-09-15T17:13:17.003 回答