Created
June 28, 2018 20:15
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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