Related
Given a line with coordinates 'start' and 'end' and the coordinates of a point 'pnt' find the shortest distance from pnt to the line. I have tried the below code.
import math
def dot(v,w):
x,y,z = v
X,Y,Z = w
return x*X + y*Y + z*Z
def length(v):
x,y,z = v
return math.sqrt(x*x + y*y + z*z)
def vector(b,e):
x,y,z = b
X,Y,Z = e
return (X-x, Y-y, Z-z)
def unit(v):
x,y,z = v
mag = length(v)
return (x/mag, y/mag, z/mag)
def distance(p0,p1):
return length(vector(p0,p1))
def scale(v,sc):
x,y,z = v
return (x * sc, y * sc, z * sc)
def add(v,w):
x,y,z = v
X,Y,Z = w
return (x+X, y+Y, z+Z)
def pnt2line(pnt, start, end):
line_vec = vector(start, end)
pnt_vec = vector(start, pnt)
line_len = length(line_vec)
line_unitvec = unit(line_vec)
pnt_vec_scaled = scale(pnt_vec, 1.0/line_len)
t = dot(line_unitvec, pnt_vec_scaled)
if t < 0.0:
t = 0.0
elif t > 1.0:
t = 1.0
nearest = scale(line_vec, t)
dist = distance(nearest, pnt_vec)
nearest = add(nearest, start)
return (dist, nearest)
The resolution can be explained by the figure, which shows the locus of the points at a given distance from a segment. It is made of two half circles and two line segments, which are separated by the two perpendiculars at the endpoints.
We can simplify the discussion by ensuring that the segment is in a canonical position, with endpoints (0, 0) and (L, 0). For any segment, we can apply a similarity transformation to bring it in the canonical position (see below), and move the target point accordingly.
Now the computation of the distance amounts to
X < 0 -> √[X² + Y²]
0 ≤ X ≤ L -> |Y|
L < X -> √[(X-L)² + Y²]
Subtract the coordinates of one endpoint from all points to bring the segment to the origin.
Compute the length L.
Normalize the vector to the second endpoint to obtain a unit vector, let U.
Transform the target point with X' = Ux.X + Uy.Y, Y' = Ux.Y - Uy.X.
Technical remark:
The geometric analysis proves that the output function is the square root of a piecewise quadratic function and it takes one or two comparisons to tell the active piece and this cannot be avoided. If I am right, the algebraic expressions cannot be much simpified.
I'm trying to create a list of N random (x,y,z) points using python, in a way that each point is at least a distance r away from any other point.
I'm super new to programming and so far I'm only able to generate x, y, and z separately (then put together) by using
import random
def RandX(start, end, num):
res = []
for j in range(num):
res.append(random.randint(start, end))
return res
num = N
start = 0
end = 100
print(RandX(start, end, num))
but I have no idea how to control or check the positions of the points(x, y, z) so that the points are a distance away from each other.
To check the distance between two points (x,y,z) and (a,b,c) (stored as tuples), you can try:
def distance(p1,p2):
d=0
for i in range(3):
d+=(p1[i]-p2[i])**2
return d**(1/2)
Once you generate xyz randomly, you can set the following:
p1=x,y,z
p2=a,b,c
If your numbers are not too large, while this is inefficient, you can generate random numbers until they satisfy the distance condition.
Here is my solution: all we need is a distance function and a loop to generate random points and check for minimum distance criteria within our already-generated list:
def dist(new_point, points, r_threshold):
for point in points:
dist = np.sqrt(np.sum(np.square(new_point-point)))
if dist < r_threshold:
return False
return True
def RandX(N, r_threshold):
points = []
scope = np.arange(0,10,0.1)
while len(points) < N:
new_point = np.random.choice(scope, 3)
if dist(new_point, points, r_threshold):
points.append(new_point)
return points
For example:
RandX(5, 4)
[array([3.5, 2.6, 7.6]),
array([9.9, 0.1, 7.2]),
array([4. , 2.8, 0.3]),
array([0.2, 7.4, 5.1]),
array([7.4, 6.3, 5.2])]
Something like this. (It can be optimised but should serve you as very first version)
from collections import namedtuple
import random
import math
Point = namedtuple('Point', ' x y z')
MIN = 0
MAX = 1000
def fill_points_list(points, number_of_required_points, min_distance):
def _get_distance(p1, p2):
return math.sqrt(sum([(a - b) ** 2 for a, b in zip(p1, p2)]))
while len(points) < number_of_required_points:
temp = Point(random.randint(MIN, MAX), random.randint(MIN, MAX), random.randint(MIN, MAX))
count = 0
for p in points:
if _get_distance(temp, p) > min_distance:
count += 1
else:
break
if len(points) == count:
points.append(temp)
number_of_required_points = 9
min_distance = 51
points = []
fill_points_list(points, number_of_required_points, min_distance)
print(points)
output
[Point(x=771, y=590, z=226), Point(x=385, y=835, z=900), Point(x=551, y=294, z=800), Point(x=824, y=306, z=333), Point(x=892, y=548, z=879), Point(x=520, y=660, z=384), Point(x=409, y=193, z=331), Point(x=411, y=706, z=300), Point(x=272, y=116, z=719)]
You could try to generate randomly a number of points, and then filter them based on the distance criteria. The numpy and sklearn packages can be helpful make the process more efficient. You could imagine something like this:
import numpy as np
from sklearn.metrics.pairwise import euclidean_distances
r = 2
# Generate 100 points (3-tuples) between 0 and 10
points = np.random.randint(0,100,[1000,3])
# Pairwise distances between points
distances = euclidean_distances(points)
# "Remove" distance to itself by setting to a distance of r+1 (to discard it later)
distances += np.identity(len(distances)) * (r+1)
# Retrieve the distance to the closest point
min_dist = np.min(distances,axis=1)
# Filter your set of points
filtered_points = points[min_dist>r]
This should run pretty fast.
import numpy as np
import matplotlib.pylab as plt
class Buffon_needle_problem:
def __init__(self,x,y,n,m):
self.x = x #width of the needle
self.y = y #witdh of the space
self.r = []#coordinated of the centre of the needle
self.z = []#measure of the alingment of the needle
self.n = n#no of throws
self.m = m#no of simulations
self.pi_approx = []
def samples(self):
# throwing the needles
for i in range(self.n):
self.r.append(np.random.uniform(0,self.y))
self.z.append(np.random.uniform(0,self.x/2.0))
return [self.r,self.z]
def simulation(self):
self.samples()
# m simulation
for j in range(self.m):
# n throw
hits = 0 #setting the succes to 0
for i in range(self.n):
#condition for a hit
if self.r[i]+self.z[i]>=self.y or self.r[i]-self.z[i] <= 0.0:
hits += 1
else:
continue
hits = 2*(self.x/self.y)*float(self.n/hits)
self.pi_approx.append(hits)
return self.pi_approx
y = Buffon_needle_problem(1,2,40000,5)
print (y.simulation())
For those who unfamiliar with Buffon's problem, here is the http://mathworld.wolfram.com/BuffonsNeedleProblem.html
or
implementing the same idea (and output)
http://pythonfiddle.com/historically-accurate-buffons-needle/
My expected output should be the value of pi but my code give me around 4. Can anyone point out the logical error?
The sampling of the needle's alignment should be a uniform cosine. See the following link for the method: http://pdg.lbl.gov/2012/reviews/rpp2012-rev-monte-carlo-techniques.pdf
Also, there were a few logical problems with the program. Here is a working version.
#!/bin/python
import numpy as np
def sample_cosine():
rr=2.
while rr > 1.:
u1=np.random.uniform(0,1.)
u2=np.random.uniform(0,1.)
v1=2*u1-1.
rr=v1*v1+u2*u2
cc=(v1*v1-u2*u2)/rr
return cc
class Buffon_needle_problem:
def __init__(self,x,y,n,m):
self.x = float(x) #width of the needle
self.y = float(y) #witdh of the space
self.r = [] #coordinated of the centre of the needle
self.z = [] #measure of the alignment of the needle
self.n = n #no of throws
self.m = m #no of simulations
self.p = self.x/self.y
self.pi_approx = []
def samples(self):
# throwing the needles
for i in range(self.n):
self.r.append(np.random.uniform(0,self.y))
C=sample_cosine()
self.z.append(C*self.x/2.)
return [self.r,self.z]
def simulation(self):
# m simulation
for j in range(self.m):
self.r=[]
self.z=[]
self.samples()
# n throw
hits = 0 #setting the success to 0
for i in range(self.n):
#condition for a hit
if self.r[i]+self.z[i]>=self.y or self.r[i]-self.z[i]<0.:
hits += 1
else:
continue
est =self.p*float(self.n)/float(hits)
self.pi_approx.append(est)
return self.pi_approx
y = Buffon_needle_problem(1,2,80000,5)
print (y.simulation())
Buffon's needle work accurately only when the distance between the two lines is double the length of needle. Make sure to cross check it.
I have seen many baffon's online simulation which are doing this mistake. They just take the distance between two adjacent lines to be equal to the needle's length. That's their main logical errors.
I would say that the problem is that you are defining the alignment of the needle by a simple linear function, when in fact the effective length of the needle from its centre is defined by a sinusoidal function.
You want to calculate the effective length of the needle (at 90° to the lines) by using a function that will calculate it from its angle.
Something like:
self.z.append(np.cos(np.random.uniform(-np.pi/2, np.pi/2))*self.x)
This will give the cosine of a random angle between -90° and +90°, times the length of the needle.
For reference, cos(+/-90) = 0 and cos(0) = 1, so at 90°, the needle with have effectively zero length, and at 0°, its full length.
I have neither mathplotlib or numpy installed on this machine, so I can't see if this fixes it, but it's definitely necessary.
Looks like you were committing a simple rounding error. The code below works, though the results are not very close to pi...
import numpy as np
import matplotlib.pylab as plt
class Buffon_needle_problem:
def __init__(self,x,y,n,m):
self.x = x #width of the needle
self.y = y #witdh of the space
self.r = []#coordinated of the centre of the needle
self.z = []#measure of the alingment of the needle
self.n = n#no of throws
self.m = m#no of simulations
self.pi_approx = []
def samples(self):
# throwing the needles
for i in range(self.n):
self.r.append(np.random.uniform(0,self.y))
self.z.append(np.random.uniform(0,self.x/2.0))
return [self.r,self.z]
def simulation(self):
#self.samples()
# m simulations
for j in range(self.m):
self.r=[]
self.z=[]
for i in range(self.n):
self.r.append(np.random.uniform(0,self.y))
self.z.append(np.random.uniform(0,self.x/2.0))
# n throws
hits = 0 # setting the succes to 0
for i in range(self.n):
# condition for a hit
if self.r[i]+self.z[i]>=self.y or self.r[i]-self.z[i] <= 0.0:
hits += 1
else:
continue
hits = 2.0*(float(self.x)/self.y)*float(self.n)/float(hits)
self.pi_approx.append(hits)
return self.pi_approx
y = Buffon_needle_problem(1,2,40000,5)
print (y.simulation())
Also note that you were using the same sample for all simulations!
I used Python turtle to approximate the value of Pi:
from turtle import *
from random import *
setworldcoordinates(-100, -200, 200, 200)
ht(); speed(0); color('blue')
drops = 20 # increase number of drops for better approximation
hits = 0 # hits counter
# draw parallel lines with distance 20 between adjacent lines
for i in range(0, 120, 20):
pu(); setpos(0, i); pd()
fd(100) # length of line
# throw needles
color('red')
for j in range(drops):
pu()
goto(randrange(10, 90), randrange(0,100))
y1 = ycor() # keep ycor of start point
seth(360*random())
pd(); fd(20) # draw needle of length 20
y2 = ycor() # keep ycor of end point
if y1//20 != y2//20: # decisive test: if it is a hit then ...
hits += 1 # increase the hits counter by 1
print(2 * drops / hits)
Output samples
With 50 drops 3.225806451612903
with 200 drops 3.3057851239669422
with 1000 drops 3.1645569620253164
NOT answer to original question, if you just want the pi estimate, here's some simple code from I did in a computational revision exercise yesterday at Uni Sydney (Aust), against my early inclinations, to reduce complexity, we only modelled for a random point between zero and distance between lines and a random angle from zero to 90 degrees.
import random
from numpy import pi, sin
def buffon(L, D, N):
'''
BUFFON takes L (needle length),
D = distance between lines and N = number of drops,
returns probability of hitting line
generate random number 'd' between 0 and D
generate theta between 0 and pi/2
hit when L*sin(theta)) - d is great than D
'''
hit = 0;
for loop in range(N) :
theta = pi*random.random()/2
if L * sin(theta) > D - D*random.random(): # d = random*D
hit += 1
return hit/N
#% Prob_hit = 2*L/(D*pi) hence: Pi_est = 2*L / (P_hit*D);
L = 1
D = 4
N = int(1e8)
Pi_est = 2*L / (buffon(L,D,N)*D)
It was in MatLab, I wanted to try it in Python, see if I could use any comprehension lists, any ideas to speed this up WELCOME.
It should be noted that the Monte Carlo method is not the best for this kind of calculation (calculating the number pi). One way or another, it is necessary to throw quite a lot of needles (or points, in the case of a quarter circle) in order to get a more accurate pi. The main disadvantage of the Monte Carlo method is its unpredictability.
https://github.com/Battle-Of-Two-K/Buffon-s-Noodle-Problem
https://github.com/Battle-Of-Two-K/Calculating-Pi-by-Monte-Carlo-Method
enter image description here
I am trying to write some code that takes a user specified closed polygon, and then a user specified horizontal line to cut the polygon with the horizontal line to create a new shape. I also need to determine the area of the shape, and its perimeter. This is what I have so far:
I do not know how to create the new shape as I need to insert new points to form a new polygon?
PS.. Have tried to insert this as a code block, but for some reason it re-formats it all the time ???
I have found bits of code to do some bits on Stack Overflow... Any assistance much appreciated...
enter code here
"""
ROUTINE TO FIND MULTIPLE INTERSECTION POINTS FOR A HORIZONTAL LINE CUTTING THROUGH AN ARBITRARY CROSS SECTIONAL SHAPE
This routine should start with a poly defined by the user,
take the Y_specified, to adjust the polygon shape, (Effectively cut the top off)
and then calculate the area of the polygon
Since Y_Specified may be between points of the defined polygon, the intersection points need to be found
"""
import pylab as pl
import numpy as np
def line(p1, p2):
# This is how a line is defined
A = (p1[1] - p2[1])
B = (p2[0] - p1[0])
C = (p1[0]*p2[1] - p2[0]*p1[1])
return A, B, -C
def intersection(L1, L2):
# This finds the intersection of 2 lines
# Should I limit the Range here ???
D = L1[0] * L2[1] - L1[1] * L2[0]
Dx = L1[2] * L2[1] - L1[1] * L2[2]
Dy = L1[0] * L2[2] - L1[2] * L2[0]
if D != 0:
x = Dx / D
y = Dy / D
return x,y
else:
return False
# This defined a closed Hexagonal sort of shape... 30 wide and 15 high
poly = [[0,10],[10,0],[20,0],[30,10],[18,15],[12,15],[0,10]]
# This defines a W shape with a lid !!
poly = [[0,10],[10,0],[12,0],[15,13],[18,0],[20,0],[30,10],[18,15],[12,15],[0,10]]
x = tuple(x[0] for x in poly)
y = tuple(x[1] for x in poly)
print x
print y
pl.plot( x,y, 'go-')
pl.suptitle('CROSS SECTIONAL SHAPE', fontsize=14, fontweight='bold')
pl.title('To be cut and replot it')
pl.xlabel('X-Co-ord')
pl.ylabel('Y-Co-ord')
pl.show()
print len(poly)
# This is the cut line
Y_Specified = 12.0
Orig_ymax = max(poly, key=lambda x: x[1])
Orig_ymax = Orig_ymax[1]
Orig_xmin = min(poly, key=lambda x: x[0])
Orig_xmin = Orig_xmin[0]
Orig_xmax = max(poly, key=lambda x: x[0])
Orig_xmax = Orig_xmax[0]
Orig_max_width = Orig_xmax - Orig_xmin
# DEFINE THE HORIZONTAL LINE
L2_PT1 = [Orig_xmin,Y_Specified]
L2_PT2 = [Orig_xmax,Y_Specified]
L2 = line(L2_PT1, L2_PT2 )
x2=[]
y2=[]
for n,i in enumerate(poly):
if n+1< len(poly):
L1_PT1 = poly[n]
L1_PT2 = poly[n+1]
L1 = line(L1_PT1, L1_PT2)
R = intersection(L1, L2) # This correctly identifies the intersection points within the width... but also outside ???
if R:
print "Intersection detected:", R
print R[0],R[1]
x2.append(R[0])
y2.append(R[1])
else:
print "No single intersection point detected"
print x2
print y2
# Now need to remove points from the original poly above the Y_Specified and insert the new intersection points
# That is if Poly[1] > Y_Specified (Delete it) and insert new point to create the newly formed smaller hexagonal shape ???
pl.plot( x,y, 'go-')
pl.plot(x2,y2, 'ro-')
# The new polygon needs to be defined by the green line cut off by the red line...???
# HOW CAN THIS BE DONE ???
pl.show()
You had most of it worked out already. There are two significant things I noticed.
Lines vs. Segments
I believe you got the line + intersection system from here? If so, then my understanding is that it is modeling lines instead of segments as you need which would result in the phantom intersections you are getting.
You will need to fix this with a different or updated intersection method before you can trust your clipped polygons.
Construction of New Polygon
I guess there is a more automatic / built-in way to do this but I couldn't find one.
Here is what I did: Rather than finding all the intersection points at one time this builds a new polygon by going around the vertexes and:
include the vertex if it qualifies (for you, being over the line)
also include any intersection between the vertex and the next vertex
If you can get an alternate line/intersection implementation, then you should be fine. Here is the result:
Copy-Paste Code
import pylab as pl
# important note - this system is X-major (x-width first, y-height second)
# line and intersection system (I didn't touch this part)
# looks like it came from here: https://stackoverflow.com/a/20679579/377366
def line(p1, p2):
# This is how a line is defined
A = (p1[1] - p2[1])
B = (p2[0] - p1[0])
C = (p1[0]*p2[1] - p2[0]*p1[1])
return A, B, -C
def intersection(L1, L2):
# This finds the intersection of 2 lines
# Should I limit the Range here ???
D = L1[0] * L2[1] - L1[1] * L2[0]
Dx = L1[2] * L2[1] - L1[1] * L2[2]
Dy = L1[0] * L2[2] - L1[2] * L2[0]
if D != 0:
x = Dx / D
y = Dy / D
return x,y
else:
return False
# a polygon (cleaned up a little)
poly_original = [[0,10], [10,0], [12,0], [15,13], [18,0], [20,0], [30,10],
[18,15],[12,15],[0,10]]
x, y = zip(*poly_original)
# This is the cut line (I didn't touch this part)
Y_Specified = 12.0
Orig_ymax = max(poly_original, key=lambda x: x[1])
Orig_ymax = Orig_ymax[1]
Orig_xmin = min(poly_original, key=lambda x: x[0])
Orig_xmin = Orig_xmin[0]
Orig_xmax = max(poly_original, key=lambda x: x[0])
Orig_xmax = Orig_xmax[0]
Orig_max_width = Orig_xmax - Orig_xmin
L2_PT1 = [Orig_xmin, Y_Specified]
L2_PT2 = [Orig_xmax, Y_Specified]
L2 = line(L2_PT1, L2_PT2 )
# from here is a different algorithm than you used
# rather than finding all the intersection points at one time
# this builds a new polygon by going around the vertexes
# include each vertex if it qualifies (for you, being over the line)
# also include any intersection between the vertex and the next vertex
def is_qualified_vertex(v):
# this could be changed depending on requirements
# in your case, it is simply whether or not the y value is above (or equal?)
# the horizontal line
return v[1] >= Y_Specified
# I don't recommend including the same point twice to close a polygon since
# you are really defining vertices instead of edges, but this works with your
# original setup of duplicating the first/last point.
poly_new = list()
for i in range(0, len(poly_original)-1):
vertex_a, vertex_b = poly_original[i:i+2]
# decide to include the original vertex in the new poly or not
# for your requirements, this just needs to be on or above a horizontal line
if is_qualified_vertex(vertex_a):
poly_new.append(vertex_a)
# before moving on to the next vertex, check if there was an intersection
# with the divider to be included in the new poly
edge_line = line(vertex_a, vertex_b)
# problem - I think you are using a line intersection algorithm
# rather than a segment intersection algorithm.
# That would be why you are getting phantom intersections.
# Also how do you want to handle overlapping segments?
intersection_point = intersection(edge_line, L2)
if intersection_point:
# intersection - build new poly with it
print('------------------')
print(vertex_a)
print(intersection_point)
print(vertex_b)
poly_new.append(intersection_point)
x2, y2 = zip(*poly_new)
pl.suptitle('CROSS SECTIONAL SHAPE', fontsize=14, fontweight='bold')
pl.title('Original and cut.')
pl.xlabel('X-Co-ord')
pl.ylabel('Y-Co-ord')
pl.plot(x, y, 'go-')
pl.plot(x2, y2, 'ro-')
pl.show()
I have a class describing a Point (has 2 coordinates x and y) and a class describing a Polygon which has a list of Points which correspond to corners (self.corners)
I need to check if a Point is in a Polygon
Here is the function that is supposed to check if the Point is in the Polygon. I am using the Ray Casting Method
def in_me(self, point):
result = False
n = len(self.corners)
p1x = int(self.corners[0].x)
p1y = int(self.corners[0].y)
for i in range(n+1):
p2x = int(self.corners[i % n].x)
p2y = int(self.corners[i % n].y)
if point.y > min(p1y,p2y):
if point.x <= max(p1x,p2x):
if p1y != p2y:
xinters = (point.y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
print xinters
if p1x == p2x or point.x <= xinters:
result = not result
p1x,p1y = p2x,p2y
return result
I run a test with following shape and point:
PG1 = (0,0), (0,2), (2,2), (2,0)
point = (1,1)
The script happily returns False even though the point it within the line. I am unable to find the mistake
I would suggest using the Path class from matplotlib
import matplotlib.path as mplPath
import numpy as np
poly = [190, 50, 500, 310]
bbPath = mplPath.Path(np.array([[poly[0], poly[1]],
[poly[1], poly[2]],
[poly[2], poly[3]],
[poly[3], poly[0]]]))
bbPath.contains_point((200, 100))
(There is also a contains_points function if you want to test for multiple points)
I'd like to suggest some other changes there:
def contains(self, point):
if not self.corners:
return False
def lines():
p0 = self.corners[-1]
for p1 in self.corners:
yield p0, p1
p0 = p1
for p1, p2 in lines():
... # perform actual checks here
Notes:
A polygon with 5 corners also has 5 bounding lines, not 6, your loop is one off.
Using a separate generator expression makes clear that you are checking each line in turn.
Checking for an empty number of lines was added. However, how to treat zero-length lines and polygons with a single corner is still open.
I'd also consider making the lines() function a normal member instead of a nested utility.
Instead of the many nested if structures, you could also check for the inverse and then continue or use and.
I was trying to solve the same problem for my project and I got this code from someone in my network.
#!/usr/bin/env python
#
# routine for performing the "point in polygon" inclusion test
# Copyright 2001, softSurfer (www.softsurfer.com)
# This code may be freely used and modified for any purpose
# providing that this copyright notice is included with it.
# SoftSurfer makes no warranty for this code, and cannot be held
# liable for any real or imagined damage resulting from its use.
# Users of this code must verify correctness for their application.
# translated to Python by Maciej Kalisiak <mac#dgp.toronto.edu>
# a Point is represented as a tuple: (x,y)
#===================================================================
# is_left(): tests if a point is Left|On|Right of an infinite line.
# Input: three points P0, P1, and P2
# Return: >0 for P2 left of the line through P0 and P1
# =0 for P2 on the line
# <0 for P2 right of the line
# See: the January 2001 Algorithm "Area of 2D and 3D Triangles and Polygons"
def is_left(P0, P1, P2):
return (P1[0] - P0[0]) * (P2[1] - P0[1]) - (P2[0] - P0[0]) * (P1[1] - P0[1])
#===================================================================
# cn_PnPoly(): crossing number test for a point in a polygon
# Input: P = a point,
# V[] = vertex points of a polygon
# Return: 0 = outside, 1 = inside
# This code is patterned after [Franklin, 2000]
def cn_PnPoly(P, V):
cn = 0 # the crossing number counter
# repeat the first vertex at end
V = tuple(V[:])+(V[0],)
# loop through all edges of the polygon
for i in range(len(V)-1): # edge from V[i] to V[i+1]
if ((V[i][1] <= P[1] and V[i+1][1] > P[1]) # an upward crossing
or (V[i][1] > P[1] and V[i+1][1] <= P[1])): # a downward crossing
# compute the actual edge-ray intersect x-coordinate
vt = (P[1] - V[i][1]) / float(V[i+1][1] - V[i][1])
if P[0] < V[i][0] + vt * (V[i+1][0] - V[i][0]): # P[0] < intersect
cn += 1 # a valid crossing of y=P[1] right of P[0]
return cn % 2 # 0 if even (out), and 1 if odd (in)
#===================================================================
# wn_PnPoly(): winding number test for a point in a polygon
# Input: P = a point,
# V[] = vertex points of a polygon
# Return: wn = the winding number (=0 only if P is outside V[])
def wn_PnPoly(P, V):
wn = 0 # the winding number counter
# repeat the first vertex at end
V = tuple(V[:]) + (V[0],)
# loop through all edges of the polygon
for i in range(len(V)-1): # edge from V[i] to V[i+1]
if V[i][1] <= P[1]: # start y <= P[1]
if V[i+1][1] > P[1]: # an upward crossing
if is_left(V[i], V[i+1], P) > 0: # P left of edge
wn += 1 # have a valid up intersect
else: # start y > P[1] (no test needed)
if V[i+1][1] <= P[1]: # a downward crossing
if is_left(V[i], V[i+1], P) < 0: # P right of edge
wn -= 1 # have a valid down intersect
return wn
Steps:
Iterate over all the segments in the polygon
Check whether they intersect with a ray going in the increasing-x direction
Using the intersect function from This SO Question
def ccw(A,B,C):
return (C.y-A.y) * (B.x-A.x) > (B.y-A.y) * (C.x-A.x)
# Return true if line segments AB and CD intersect
def intersect(A,B,C,D):
return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D)
def point_in_polygon(pt, poly, inf):
result = False
for i in range(len(poly.corners)-1):
if intersect((poly.corners[i].x, poly.corners[i].y), ( poly.corners[i+1].x, poly.corners[i+1].y), (pt.x, pt.y), (inf, pt.y)):
result = not result
if intersect((poly.corners[-1].x, poly.corners[-1].y), (poly.corners[0].x, poly.corners[0].y), (pt.x, pt.y), (inf, pt.y)):
result = not result
return result
Please note that the inf parameter should be the maximum point in the x axis in your figure.