Checking if a point is inside a polygon - python

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.

Related

I have 2 points (x1,y1) & (x2,y2) and a circle, suppose there is a line between the 2 points I need to check if there is collision with the circle

As I mentioned in the title, suppose I have a line segment from point 1 to point 2 and there is a circle with a center and radius I need to check if there is going to be a collision with the circle using code. This is how far I got.
However, there is an issue with closestX and closestY since I need to check if they are on the line segment from point 1 to point 2 because if they are not on the line segment then there will be No collision. Sadly though Im stuck here and I cannot figure out a way to check if they are on the line segment or not. Please help thank you.
import math
p=2
obsHeight=200
DroneHeight=150
cx=3
cy=3
r=1
x1=1
y1=1
x2=1.5
y2=1.5
if DroneHeight<=obsHeight:
distX= x1 - x2
distY= y1 - y2
length=math.sqrt((distX*distX) + (distY*distY ))
dot= (((cx-x1)*(x2-x1)) + ((cy-y1)*(y2-y1)) )/(math.pow(length,p))
closestX=x1+( dot * (x2-x1))
closestY=y1+( dot * (y2-y1))
print(" Closest x: ",closestX)
print(" Closest y: ",closestY)
distX=closestX-cx
distY= closestY-cy
distance= math.sqrt((distX*distX) + (distY*distY ))
print("The distance is: ", distance)
print("The length is: ", length)
if (r==distance):
print("Touching")
elif (distance<r):
print("COLLIDING")
else:
print("Will not collide")
else:
print(" Will not collide, the drone is higher than the obstacle")
Ignoring the specificity of your code, let's say that you have a line segment, a center and a radius. Let's write a general solution to whether a line segment in N-dimensions intersects a hyper-sphere in N-dimensions. This will give us the correct solution for your problem in the special case of 2D.
Your function signature would look like this:
def intersects(p1, p2, c, r):
p1 and p2 are vectors of length N. In your case, p1 = np.array([1, 1]), and p2 = np.array([1.5, 1.5]). c is a vector of the same length (c = np.array([3, 3])), and r is a scalar radius (r = 1). I strongly recommend using numpy arrays for your math because it is much faster if you use it right, and you can apply element-wise operations to arrays (e.g. p2 - p1) without using a loop.
A line passing through p1 and p2 can be parametrized as p = p1 + t * (p2 - p1). Every point on the line p corresponds some value of the parameter t. Specifically, t == 0 corresponds to p = p1 and t == 1 corresponds to p = p2. That means that you can know if a point is on the line segment by checking if its parameter is in the range [0, 1].
The problem then becomes finding the value of t such that p is closest to c. If t < 0 or t > 1, then you know that the extrema for the line segment are at the endpoints. Otherwise, you need to compare the distances of both the endpoints and the p you found.
There are a couple of different ways of coming up with the solution. The geometric approach uses the fact that the nearest approach happens at the perpendicular from c to the line. The differential approach finds where the derivative of the length is zero. I will show the former here.
Looking at the diagram, you have the following equation:
(c - p).dot(p2 - p1) == 0
(c - p1 - t * (p2 - p1)).dot(p2 - p1) == 0
(c - p1).dot(p2 - p1) - t * (p2 - p1).dot(p2 - p1) == 0
t == (c - p1).dot(p2 - p1) / (p2 - p1).dot(p2 - p1)
You can now write your function like this:
def intersects(p1, p2, c, r):
c1 = np.subtract(c, p1)
c2 = np.subtract(c, p2)
dist1 = np.linalg.norm(c1)
dist2 = np.linalg.norm(c2)
# If point are on opposite sides of circle, intersects
if (r - dist1) * (r - dist2) < 0:
return True
# If both on inside, does not intersect
if r > dist1:
return False
dp = np.subtract(p2, p1)
t = dp.dot(c1) / dp.dot(dp)
# If closest approach is outside segment, does not intersect
# convince yourself of this (use symmetry about the line c-p)
if t < 0 or t > 1:
return False
cp = np.subtract(p1 + t * dp, c)
distp = np.linalg.norm(cp)
# only other possibility of approach is when closest point is inside radius
return distp <= r
The problem of finding the distance between a point and a line has cropped up a number of times on Stack Overflow, and in my applications as well, so I recently added it to a library of utilities that I maintain, haggis. You can build a solution using haggis.math.segment_distance with very similar logic. I specifically made the function operate in line segment or full-line mode for this purpose:
def intersects(p1, p2, c, r):
dist1 = np.linalg.norm(c1 := np.subtract(c, p1))
dist2 = np.linalg.norm(c2 := np.subtract(c, p2))
if (r - dist1) * (r - dist2) < 0: # Opposite sides of circle
return True
if r > dist1: # Both inside circle
return False
d = segment_distance(c, p1, p2)
return d < r
You could rewrite the last two lines as follows:
d, t = segment_distance(c, p1, p2, segment=False, return_t=True)
return d < r and 0 <= t <= 1
You can calculate the squared distance of the center of the circle to the line by
d2 = ((y1-y2)*(cx-x1)+(x2-x1)*(cy-y1))**2/((x2-x1)**2+(y2-y1)**2)
Now just compare that value to the squared radius. If d2<r**2 then the line cuts the circle.
Edit:
I think you already are pretty close to a correct solution. In the line where you calculate the dot product, you should divide by the length of the line segment and not by its squared length. Just putting a (...)**0.5 around the math.pow expression should give the correct value.
Note:
Phython has a builtin power operator called **, so no need to use math.pow

Generating random dots within an outer circle that have a certain distance

I'm currently really stuck with some of my code and I can't seem to find the issue. Here is what I am trying to do:
I have a big outer circle in which I want to display smaller dots. These dots should be randomly distributed but should not overlap, thus they should have a minimum distance to each other.
What I have tried is to first randomly generate a point, check wether it is in the outer circle and if it is, append it to the final list of dot positions. Then another point is created, checked if in circle and then it should be checked if the dot has a minimum distance to the other dot(s) in the final list.
However, I seem to have some issues with my code as it will not run through whenever I set the required distances higher than 1. I have changed multiple things, but I cannot make it work.
Does anyone have an idea about what the problem might be?
Here's what I have been trying:
import random
import numpy as np
import math
#Variables
radiusOC = 57
size_obj = 7
required_dist = 5
no_stimuli = 3
def CreatePos(radiusOC, size_obj, required_dist, no_stimuli):
final_list = []
def GenRandPos(radiusOC,size_obj):
"""
Takes the radius of the outer circle and generates random dots within this radius. Then checks if the the dots are located
within the outer circle.
"""
while True:
xPos = random.randint(-radiusOC,radiusOC)
yPos = random.randint(-radiusOC,radiusOC)
# check if in Circle
on_circle = (xPos- 0)**2 + (yPos-0)**2
if (radiusOC-size_obj)**2 >= on_circle:
print("Still in circle",on_circle, xPos, yPos )
position = [xPos, yPos]
break
else:
print("Not in circle",on_circle, xPos, yPos )
continue
return position
def CheckSurrounding(position, final_list, required_dist):
"""
Takes dot positions that are in the visual field, the list of positions, and the distances dots are required to have from each other.
It is checked if there are dots close by or not.
"""
X1 = position[0]
Y1 = position[1]
dist_list = []
for elem in final_list:
for i in elem:
X2 = elem[0]
Y2 = elem[1]
dist = math.sqrt((X1-X2)**2 + (Y1-Y2)**2)
dist_list.append(dist)
if all(dist_list) >= required_dist:
return position
else:
return None
# append the first dot to the list
position = GenRandPos(radiusOC, size_obj)
final_list.append(position)
# now append the rest of the dots if they have a certain distance to each other
while len(final_list) < no_stimuli:
position = GenRandPos(radiusOC, size_obj)
if CheckSurrounding(position, final_list, required_dist) != None:
position = CheckSurrounding(position, final_list, required_dist)
final_list.append(position)
else:
continue
return final_list
ยดยดยด
In the line
if all(dist_list) >= required_dist:
all(dist_list) will be either True or False, which is numerically equivalent to either 1 or 0. If required_dist is greater than 1 the inequality will never be satisfied. I think that you intended this to be
if all(dist_list >= required_dist):
but this will not work since you cannot compare a list dist_list to a number required_dist. To fix it, convert dist_list to a numpy array:
if np.all(np.array(dist_list) >= required_dist):
By the way, the random points you are selecting will always have integer coordinates since you are using random.randint(), I am not sure if this is intentional.
The whole code can be made more efficient by using numpy arrays. For example:
import numpy as np
def CreatePos(radiusOC, size_obj, required_dist, no_stimuli):
final_list = []
def GenRandPos(radiusOC, size_obj):
"""
Takes the radius of the outer circle and generates
random dots within this radius. Then checks if the dots are
located within the outer circle.
"""
while True:
position = (2 * np.random.random(2) - 1) * radiusOC
# check if in Circle
if (radiusOC - size_obj)**2 >= (position**2).sum():
return position
def CheckSurrounding(position, final_list, required_dist):
"""
Takes dot positions that are in the visual field,
the list of positions, and the distances dots are
required to have from each other.
It is checked if there are dots close by or not.
"""
final_arr = np.array(final_list)
dist = ((np.array(final_list) - position)**2).sum(axis=1)
if np.all(np.array(dist) >= required_dist**2):
return position
# append the first dot to the list
position = GenRandPos(radiusOC, size_obj)
final_list.append(position)
# now append the rest of the dots if they have a certain distance to each other
while len(final_list) < no_stimuli:
position = GenRandPos(radiusOC, size_obj)
if CheckSurrounding(position, final_list, required_dist) is not None:
final_list.append(position)
return final_list
Note that this returns a list of points with coordinates given by floats, not integers.
Sample usage:
#Variables
radiusOC = 57
size_obj = 7
required_dist = 3
no_stimuli = 400
final_list = np.array(CreatePos(radiusOC, size_obj, required_dist, no_stimuli))
Plot the resulting points:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(7,7))
ax = fig.add_subplot(111)
ax.set_aspect("equal")
plt.scatter(f[:, 0], f[:, 1])
plt.show()
This gives:
I would add a condition in the final while loop so it can break if a new point cannot be found after some number of attempts. Otherwise, it may end up running indefinitely.
You need to figure out a way to estimate the max number of points given the distance. This can be extrapolated from the circle packing problem. https://planetcalc.com/7473/ I will comment if I have an easy check to do.
import numpy as np
from matplotlib import pyplot as plt
from scipy.spatial.distance import cdist
def random_in_circle(num_points=1000, R=1, min_dist=0.2):
assert min_dist < R, "Min distance between points must be smaller than the radius of outer circle"
assert R / (2 ** (num_points - 1)) < min_dist, "Min dist is too large"
points = []
while len(points) < num_points:
a = np.random.rand() * 2 * np.pi # random angle
r = R * np.random.rand() # random radius
point = r * np.array([np.cos(a), np.sin(a)])
if len(points) == 0:
points.append(point)
elif np.all(cdist([point], points) > min_dist):
points.append(point)
return np.vstack(points)
points = random_in_circle(num_points=1000, min_dist=0.01)
plt.scatter(points[:, 0], points[:, 1])
plt.show()

Bowyer-Watson triangulates incorrectly when trying to implement circumcircle calculation for vertices at infinity

Quick primer on Bowyer-Watson:
function BowyerWatson (pointList)
// pointList is a set of coordinates defining the points to be triangulated
triangulation := empty triangle mesh data structure
add super-triangle to triangulation // must be large enough to completely contain all the points in pointList
for each point in pointList do // add all the points one at a time to the triangulation
badTriangles := empty set
for each triangle in triangulation do // first find all the triangles that are no longer valid due to the insertion
if point is inside circumcircle of triangle
add triangle to badTriangles
polygon := empty set
for each triangle in badTriangles do // find the boundary of the polygonal hole
for each edge in triangle do
if edge is not shared by any other triangles in badTriangles
add edge to polygon
for each triangle in badTriangles do // remove them from the data structure
remove triangle from triangulation
for each edge in polygon do // re-triangulate the polygonal hole
newTri := form a triangle from edge to point
add newTri to triangulation
for each triangle in triangulation // done inserting points, now clean up
if triangle contains a vertex from original super-triangle
remove triangle from triangulation
return triangulation
This was implemented into python 3.7.2 and pygame 1.7~ with relative ease.
After this, I found this comment on another post stating that most Bowyer-Watson algorithms lack calculations for vertices at infinity and implementations to overcome this:
Check if any of triangles vertices lies at infinity. In other words:
check if triangle is sharing some vertices with bounding triangle.
If it's sharing all three vertices: trivial.
If it's sharing zero vertices: classical approach - check if
distance from point to circumcenter is shorter than circumradius.
If it's sharing one vertex: check if point lies to the left/right of
line defined by the other two vertices. one vertex in infinity
If it's sharing two vertices: check if point lies to the left/right
of line defined by these two vertices but shifted to the third
point. In other words: you take only the slope vector from line
between these shared vertices and shift it so that the line passes
through the third point. two vertices in infinity
Therefore, I created a new Point() class instead of the built-in pygame.Vector2(), with an added isInfinite boolean element.
For the Triangle() class, it takes in 3 Point() and sorts then into 2 lists: finiteVertices and infiniteVertices based on the isInfinite element in each Point and executes the procedure stated above depending on how many elements are in each list.
This would be great if it didn't turn my triangulation into spagetti.
My code is:
import pygame
import pygame.gfxdraw
import math
import random
pygame.init()
def Sign(value):
if value < 0:
return -1
if value > 0:
return 1
if value == 0:
return 0
def SideOfLineOfPoint(x1,y1,x2,y2,posX,posY):
d = (posX-x1)*(y2-y1) - (posY-y1)*(x2-x1)
return Sign(d)
def LineIsEqual(line1,line2): # Detect congruence of line, no matter which end is defined first
if (line1[0] == line2[0] and line1[1] == line2[1]) or (line1[0] == line2[1] and line1[1] == line2[0]):
return True
return False
class Point:
def __init__(self,x,y,isInfinite):
self.x = x
self.y = y
self.isInfinite = isInfinite
def distanceTo(self,other):
return math.sqrt( (self.x-other.x)**2 + (self.y-other.y)**2 )
class Triangle:
def __init__(self,a,b,c):
self.vertices = [a,b,c] # a,b,c are vertices defining the triangle
self.edges = [[a,b],
[b,c],
[c,a]] # Manually defining all edges of triangle ([])
self.CalculateCircumcenter()
self.infiniteVertices = []
self.finiteVertices = []
for vertex in self.vertices:
if vertex.isInfinite:
self.infiniteVertices.append(vertex)
else:
self.finiteVertices.append(vertex)
def CalculateCircumcenter(self): # Copied from Delaunator page
a = [self.vertices[0].x , self.vertices[0].y]
b = [self.vertices[1].x , self.vertices[1].y]
c = [self.vertices[2].x , self.vertices[2].y]
ad = a[0] * a[0] + a[1] * a[1]
bd = b[0] * b[0] + b[1] * b[1]
cd = c[0] * c[0] + c[1] * c[1]
D = 2 * (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1]))
self.circumcenter = Point(1 / D * (ad * (b[1] - c[1]) + bd * (c[1] - a[1]) + cd * (a[1] - b[1])),
1 / D * (ad * (c[0] - b[0]) + bd * (a[0] - c[0]) + cd * (b[0] - a[0])),
False)
def IsPointInCircumcircle(self,point):
if len(self.infiniteVertices) == 3:
return True # Any point is within the circumcircle if all therr vertices are infinite
elif len(self.infiniteVertices) == 2: # If two infinite vertices: check if point lies to the left/right of line defined by these two vertices but shifted to the third point.
x1 = self.finiteVertices[0].x
y1 = self.finiteVertices[0].y
x2 = self.infiniteVertices[0].x - self.infiniteVertices[1].x + x1
y2 = self.infiniteVertices[0].y - self.infiniteVertices[1].y + y1
sideOfLineOfVertex = SideOfLineOfPoint(x1,y1,x2,y2,point.x,point.y)
sideOfLineOfPoint = SideOfLineOfPoint(x1,y1,x2,y2,self.infiniteVertices[0].x,self.infiniteVertices[0].x)
if sideOfLineOfVertex == sideOfLineOfPoint:
return False
else:
return True
elif len(self.infiniteVertices) == 1: # If one infinite vertex: check if point lies to the left/right of line defined by the other two vertices.
x1 = self.finiteVertices[0].x
y1 = self.finiteVertices[0].y
x2 = self.finiteVertices[1].x
y2 = self.finiteVertices[1].y
sideOfLineOfVertex = SideOfLineOfPoint(x1,y1,x2,y2,point.x,point.y)
sideOfLineOfPoint = SideOfLineOfPoint(x1,y1,x2,y2,self.infiniteVertices[0].x,self.infiniteVertices[0].x)
if sideOfLineOfVertex == sideOfLineOfPoint:
return False
else:
return True
elif len(self.infiniteVertices) == 0: # For triangle with finite vertices
if self.vertices[0].distanceTo(self.circumcenter) > point.distanceTo(self.circumcenter):
return True # If point is closer to circumcenter than any vertices, point is in circumcircle
else:
return False
def HasVertex(self,point):
if point in self.vertices:
return True
return False
def Show(self,screen,colour):
for edge in self.edges:
pygame.draw.aaline(screen,colour,(edge[0].x,edge[0].y),(edge[1].x,edge[1].y))
class DelaunayTriangulation:
def __init__(self,points,width,height):
self.triangulation = [] # Create empty list
self.superTriangleA = Point(-100,-100,True)
self.superTriangleB = Point(2*width+100,-100,True)
self.superTriangleC = Point(-100,2*height+100,True)
superTriangle = Triangle(self.superTriangleA,self.superTriangleB,self.superTriangleC)
self.triangulation.append(superTriangle) # Create super-triangle
for point in points: # For every single point to be triangulated
self.addPoint(point)
def addPoint(self,point):
invalidTriangles = [] # Invalid triangle list
for triangle in self.triangulation: # For every existing triangle
if triangle.IsPointInCircumcircle(point): # If new point is in the circumcenter of triangle
invalidTriangles.append(triangle) # Triangle is invalid and added to invalid triangle list
polygon = [] # List for remaining edges after removal of invalid triangles
for triangle in invalidTriangles: # For every invalid triangle
for triangleEdge in triangle.edges: # For each invalid triangle's edges
isShared = False # Assume no edges are shared
for other in invalidTriangles: # For every other invalid triangle
if triangle == other: # If both are the same triangle
continue
for otherEdge in other.edges: # For every edge in other triangle
if LineIsEqual(triangleEdge,otherEdge):
isShared = True # Congruent edges are shared
if isShared == False: # Only append edges not shared by invalid triangles to polygon hole
polygon.append(triangleEdge)
for triangle in invalidTriangles: # Remove invalid triangles
self.triangulation.remove(triangle)
for edge in polygon:
newTriangle = Triangle(edge[0],edge[1],point) # Retriangulate based on edges of polygon hole and point
self.triangulation.append(newTriangle)
def Show(self,screen,colour):
self.shownTriangulation = self.triangulation
superTriangles = [] # List for triangles that are part of super-triangle
for triangle in self.triangulation:
if (triangle.HasVertex(self.superTriangleA) or triangle.HasVertex(self.superTriangleB) or triangle.HasVertex(self.superTriangleC)) and (triangle in self.triangulation):
superTriangles.append(triangle) # Add triangles that have any super-triangle vertex
for triangle in superTriangles:
self.triangulation.remove(triangle) # Remove super-triangles
for triangle in self.shownTriangulation:
triangle.Show(screen,colour)
background = 20,40,100
red = 255,0,0
white = 255,255,255
width = int(500)
height = int(500)
amount = int(5)
screen = pygame.display.set_mode((width,height))
screen.fill(background)
points = []
for i in range(amount):
x = random.randint(1,width-1)
y = random.randint(1,height-1)
points.append(Point(x,y,False))
delaunay = DelaunayTriangulation(points,width,height)
delaunay.Show(screen,white)
pygame.display.update()
In my opinion, the functions that might be causing this problem would be Triangle.IsPointInCircumcircle() and SideOfLineOfPoint(), though it is also equally likely that the original algorithm isn't meant to support infinite-vertices calculation to begin with.
The code works if the whole infinite-vertices calculation is scrapped and normal circumcircle detection is used, though it would be a step backwards from my goal.
I hope someone could find any mistake in my code that would fix this or even just point me in the right direction to begin debugging this mess.
Thanks in advance.
Probably the greatest improvement of performance can be gained by avoiding the expensive math.sqrt operation.
Instead of comparing the Euclidean distances between the points, compare the square of the distance:
class Point:
# [...]
def distanceToSquare(self,other):
dx, dy = self.x-other.x, self.y-other.y
return dx*dx + dy*dy
class Triangle:
# [...]
def IsPointInCircumcircle(self,point):
return (self.vertices[0].distanceToSquare(self.circumcenter) >
point.distanceToSquare(self.circumcenter))
Apart from that there are simple typos in your code. The x component of infiniteVertices[0] is passed twice to the method SideOfLineOfPoint, but the y component is missed (in both cases):
SideOfLineOfPoint(...,self.infiniteVertices[0].x,self.infiniteVertices[0].x)
Further the method IsPointInCircumcircle has to return True, in case when the points are on the same side. You do the opposite:
if sideOfLineOfVertex == sideOfLineOfPoint:
return False
else:
return True
I recommend to reverse the order of the cases in the method IsPointInCircumcircle. The case len(self.infiniteVertices) == 3 occur only once, when the 1st point is added. In compare len(self.infinite Vertices) == 0 is the most common case, especially when the number of points increase.
See the result of the corrected method (for 20 random points):
class Triangle:
# [...]
def IsPointInCircumcircle(self,point):
if len(self.infiniteVertices) == 0: # For triangle with finite vertices
if self.vertices[0].distanceToSquare(self.circumcenter) > point.distanceToSquare(self.circumcenter):
return True # If point is closer to circumcenter than any vertices, point is in circumcircle
else:
return False
elif len(self.infiniteVertices) == 1: # If one infinite vertex: check if point lies to the left/right of line defined by the other two vertices.
x1 = self.finiteVertices[0].x
y1 = self.finiteVertices[0].y
x2 = self.finiteVertices[1].x
y2 = self.finiteVertices[1].y
sideOfLineOfVertex = SideOfLineOfPoint(x1,y1,x2,y2,point.x,point.y)
sideOfLineOfPoint = SideOfLineOfPoint(x1,y1,x2,y2,self.infiniteVertices[0].x,self.infiniteVertices[0].y)
if sideOfLineOfVertex == sideOfLineOfPoint:
return True
else:
return False
elif len(self.infiniteVertices) == 2: # If two infinite vertices: check if point lies to the left/right of line defined by these two vertices but shifted to the third point.
x1 = self.finiteVertices[0].x
y1 = self.finiteVertices[0].y
x2 = self.infiniteVertices[0].x - self.infiniteVertices[1].x + x1
y2 = self.infiniteVertices[0].y - self.infiniteVertices[1].y + y1
sideOfLineOfVertex = SideOfLineOfPoint(x1,y1,x2,y2,point.x,point.y)
sideOfLineOfPoint = SideOfLineOfPoint(x1,y1,x2,y2,self.infiniteVertices[0].x,self.infiniteVertices[0].y)
if sideOfLineOfVertex == sideOfLineOfPoint:
return True
else:
return False
return True # Any point is within the circumcircle if all there vertices are infinite

Python: How to define a new cross section from an arbitrary closed shape cut by a horizontal line

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()

Gift Wrapping Algorithm in Python never terminating

I've been trying to implement the gift wrapping algorithm in Python, I currently have the following code:
def createIslandPolygon(particleCoords):
startPoint = min(particleCoords.iteritems(),key = lambda x: x[1][1])[1]
check = 1
islandPolygon = []
particleList = []
for key in particleCoords:
particleList.append(particleCoords[key])
currentPoint = startPoint
while(currentPoint != startPoint or check == 1):
islandPolygon.append(currentPoint)
check = 0
angleDict = {}
angleList = []
for point in particleList:
if point != currentPoint:
angleDict[(angleBetweenTwoPoints(currentPoint, point))] = point
angleList.append(angleBetweenTwoPoints(currentPoint, point))
smallestAngle = min(angleList)
currentPoint = angleDict[smallestAngle]
return islandPolygon
and for calculating the polar coordinates:
def angleBetweenTwoPoints(p1, p2):
p3 = (p1[0], p1[1] + 2)
a = (p1[0] - p2[0], p1[1] - p2[1])
b = (p1[0] - p3[0], p1[1] - p3[1])
theta = ((a[0]*b[0]) + (a[1]*b[1]))
theta = theta / (sqrt((a[0]*a[0]) + (a[1]*a[1])) * sqrt((b[0]*b[0]) + (b[1]*b[1])))
theta = math.acos(theta)
return theta
The problem is that the code never seems to leave the while loop, and I'm not sure why. Does anyone have any idea?
Thanks.
(yeah, the codes pretty shoddy, I just threw it together quickly)
Edit: Printing out the coordinates seems to show them jumping between just two coordinates.
According to http://en.wikipedia.org/wiki/Gift_wrapping_algorithm you need to do this:
pointOnHull = leftmost point in S
i = 0
repeat
P[i] = pointOnHull
endpoint = S[0] // initial endpoint for a candidate edge on the hull
for j from 1 to |S|-1
if (endpoint == pointOnHull) or (S[j] is on left of line from P[i] to endpoint)
endpoint = S[j] // found greater left turn, update endpoint
i = i+1
pointOnHull = endpoint
until endpoint == P[0] // wrapped around to first hull point
You have this correct:
pointOnHull = leftmost point in S
And this:
P[i] = pointOnHull
But here's the part I'm not sure about:
(S[j] is on left of line from P[i] to endpoint)
Instead you find the smallest angle out of all the angles it makes with all other points. But according to wikipedia what you want is the leftmost angle out of all the angles it makes with all other points. I have some code for working with angles:
def normalizeangle(radians):
return divmod(radians, math.pi*2)[1]
def arclength(radians1, radians2 = 0):
radians1, radians2 = normalizeangle(radians1), normalizeangle(radians2)
return min(normalizeangle(radians1 - radians2), normalizeangle(radians2 - radians1))
def arcdir(radians1, radians2 = 0):
radians1, radians2 = normalizeangle(radians1), normalizeangle(radians2)
return cmp(normalizeangle(radians1 - radians2), normalizeangle(radians2 - radians1))
arcdir will tell you if an angle is on the left or on the right of another, so you can use it to see if an angle is more left and so should be used.
If you move along the points always picking the leftmost angle to the next point, you'll go around the perimeter of your polygon and reach the beginning again (since you picked the leftmost point, you know it has to be on the perimeter and will be reached again)

Categories

Resources