Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deterministic algorithm to determine whether a point is in a polygon #17

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 16 additions & 25 deletions lib/geometry/algorithms/point_in_polygon.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Geometry
class PointInPolygon < Struct.new(:point, :polygon)
extend Memoist

def inside?
point_location == :inside
end
Expand All @@ -18,7 +18,7 @@ def point_location
return :outside unless bounding_box.contains?(point)
return :on_the_boundary if point_is_vertex? || point_on_edge?

intersection_count(choose_good_ray).odd? ? :inside : :outside
point_inside_polygon? ? :inside : :outside
end

delegate :vertices, :edges, :bounding_box, :to => :polygon
Expand All @@ -34,31 +34,22 @@ def point_on_edge?
edges.any? { |edge| edge.contains_point?(point) }
end

def choose_good_ray
ray = random_ray
while ! good_ray?(ray) do
ray = random_ray
end
ray
end
def point_inside_polygon?
# Algorithm source:
# https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html

def good_ray?(ray)
edges.none? { |edge| !edge.length.zero? && edge.parallel_to?(ray) } && vertices.none? { |vertex| ray.contains_point?(vertex) }
end

def intersection_count(ray)
edges.select { |edge| edge.intersects_with?(ray) }.size
end

def random_ray
random_direction = rand * (2 * Math::PI)

ray_endpoint = Point sufficient_ray_radius * Math.cos(random_direction), sufficient_ray_radius * Math.sin(random_direction)
Segment point, ray_endpoint
end
result = false

vertices.each_with_index do |vertex, i|
previous_vertex = vertices[i - 1] || vertex.last
if ((vertex.y > point.y) != (previous_vertex.y > point.y)) &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use next to skip iteration.

(point.x < (previous_vertex.x - vertex.x) * (point.y - vertex.y) /
(previous_vertex.y - vertex.y) + vertex.x)
result = !result
end
end

def sufficient_ray_radius
@sufficient_ray_radius ||= bounding_box.diagonal.length * 2
result
end
end
end