Skip to content

Instantly share code, notes, and snippets.

@jarhill0
Created June 28, 2018 20:15
Show Gist options
  • Save jarhill0/b80418a420729c30726fb44617df84a0 to your computer and use it in GitHub Desktop.
Save jarhill0/b80418a420729c30726fb44617df84a0 to your computer and use it in GitHub Desktop.
Determine whether a point lies within a polygon. I think the edge and corner cases work properly.
from random import random
def contains_point(polygon, point):
"""Determine whether point is contained within polygon.
:param polygon: An ordered sequence of length-2 tuples that represent the (x, y) coordinates of points that,
when connected, form a polygon that does not intersect itself. The last point will connect with the first
one.
:param point: A length-2 tuple that represents the (x, y) coordinates of a point.
"""
x_min = min(vertex[0] for vertex in polygon)
y_min = min(vertex[1] for vertex in polygon)
x_max = max(vertex[0] for vertex in polygon)
y_max = max(vertex[1] for vertex in polygon)
if not (x_min <= point[0] <= x_max and y_min <= point[1] <= y_max):
return False
if any(point == vertex for vertex in polygon):
return True
start = x_min - 0.5, y_min - 0.5 # an arbitrary point outside the polygon
# if the segment would pass through a vertex
while any(on_line(start, point, vertex) for vertex in polygon):
start = start[0] - random(), start[1] - random()
intersection_count = 0
last_vertex = polygon[-1]
for vertex in polygon:
if segments_intersect(start, point, vertex, last_vertex):
intersection_count += 1
last_vertex = vertex
return intersection_count % 2 == 1
def segments_intersect(c, d, p, q):
"""Determine whether segment cd and segment pq intersect.
:param c: A length-2 tuple with the (x, y) coordinates of point c, which connects to point d.
:param d: A length-2 tuple with the (x, y) coordinates of point d, which connects to point c.
:param p: A length-2 tuple with the (x, y) coordinates of point p, which connects to point q.
:param q: A length-2 tuple with the (x, y) coordinates of point q, which connects to point p.
:returns: True if the segments intersect, including at an endpoint.
"""
if d[0] == c[0] and p[0] == q[0]: # two vertical lines
return False
if d[0] == c[0]: # vertical line
x = d[0]
elif p[0] == q[0]:
x = p[0]
else:
m1 = (d[1] - c[1]) / (d[0] - c[0]) # slope of segment 1: rise over run
m2 = (q[1] - p[1]) / (q[0] - p[0]) # slope of segment 2: rise over run
b1 = c[1] - m1 * c[0] # intercept of segment 1
b2 = p[1] - m2 * p[0] # intercept of segment 2
if m1 == m2: # parallel or collinear
return False
# find the x-coordinate where the lines intersect, using m1 * x + b1 = m2 * x + b2
x = (b2 - b1) / (m1 - m2)
# the x coordinate must be within the range of both segments
return is_between(c[0], d[0], x) and is_between(p[0], q[0], x)
def is_between(a, b, w):
"""Determine whether w is in between a and b, inclusive.
:param a: A number.
:param b: A number.
:param w: A number.
"""
if a < b:
return a <= w <= b
else: # b <= a
return b <= w <= a
def on_line(a, b, p):
"""Determine whether point p is on the line through points a and b.
:param a: A length-2 tuple describing the (x, y) coordinates of point a.
:param b: A length-2 tuple describing the (x, y) coordinates of point b.
:param p: A length-2 tuple describing the (x, y) coordinates of point p.
"""
if b[0] == a[0]:
return p[0] == a[0]
elif p[0] == a[0]:
return p[1] == a[1]
return (p[1] - a[1]) / (p[0] - a[0]) == (b[1] - a[1]) / (b[0] - a[0])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment