Related
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
I have multiple GPS Coordinate Points I would like to create a line from in python. The points aren't in a straight line but are exact enough to connect them with straight lines.
I know how to connect one point to another, but not how I would connect multiple of these singular lines to a longer one and then get a point based on the percentage of the whole line.
I use this code to get the percentage of a singular line:
def pointAtPercent(p0, p1, percent):
if p0.x != p1.x:
x = p0.x + percent * (p1.x - p0.x)
else:
x = p0.x;
if p0.y != p1.y:
y = p0.y + percent * (p1.y - p0.y)
else:
y = p0.y
p = point()
p.x = x
p.y = y
return p;
Here is an example list:
[ 10.053417,
53.555737,
10.053206,
53.555748,
10.052497,
53.555763,
10.051125,
53.555757,
10.049193,
53.555756,
10.045511,
53.555762,
10.044863,
53.555767,
10.044319,
53.555763,
10.043685,
53.555769,
10.042765,
53.555759,
10.04201,
53.555756,
10.041919,
53.555757,
10.041904,
53.555766
]
You could create a list of x,y pairs and access the GPS point based on the length of the list:
points = [
10.053417, 53.555737, 10.053206, 53.555748, 10.052497, 53.555763, 10.051125,
53.555757, 10.049193, 53.555756, 10.045511, 53.555762, 10.044863, 53.555767,
10.044319, 53.555763, 10.043685, 53.555769, 10.042765, 53.555759, 10.04201,
53.555756, 10.041919, 53.555757, 10.041904, 53.555766
]
points = [(points[i], points[i + 1]) for i in range(0, len(points) - 1, 2)]
def pointAtPercent(points, percent):
lstIndex = int(
len(points) / 100. * percent
) # possibly creates some rounding issues !
print(points[lstIndex - 1])
pointAtPercent(points, 10)
pointAtPercent(points, 27.5)
pointAtPercent(points, 50)
pointAtPercent(points, 100)
Out:
(10.053417, 53.555737)
(10.052497, 53.555763)
(10.045511, 53.555762)
(10.041904, 53.555766)
The basic algorithm is this:
Determine the lengths of each segment. You are in spherical polar coordinates (assuming the Earth is a sphere, which it isn't (see WGS 84 if you need more precision)), so you can do this.
def great_circle_distance(lat_0, lon_0, lat_1, lon_1):
return math.acos(
math.sin(lat_0) * math.sin(lat_1)
+ math.cos(lat_0) * math.cos(lat_1) * math.cos(lon_1 - lon_0)
)
radian_points = [(math.radians(p.x), math.radians(p.y)) for p in points]
lengths = [
great_circle_distance(*p0, *p1) for p0, p1 in zip(radian_points, radian_points[1:])
]
path_length = sum(lengths)
Given a percentage, you can work out how far along the path it is.
distance_along = percentage * path_length
Find the index of the correct segment.
# Inefficient but easy (consider bisect.bisect with a stored list of sums)
index = max(
next(i for i in range(len(lengths) + 1) if sum(lengths[:i]) >= distance_along) - 1,
0,
)
Then use your original algorithm.
point = pointAtPercent(
points[index],
points[index + 1],
(distance_along - sum(lengths[:index])) / lengths[index],
)
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
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
I'm trying to convert a recursive function to an iterative one based on python limitations.
I am adapting an algorithm that I found in this answer from Javascript to Python. For a better explanation of the algorithm I'd suggest reading the answer I linked because it's much more concise. The high level purpose of this is to find equidistant points along a "line" made up of lat/lng pairs (points). However I'm running into issues in the recursive move_along_path function due to maximum recursion depth limitations in Python. After reading some similar questions, I found the best thing to do is to convert it to an iterative function. I am having trouble even beginning the conversion.
These are the two functions I have adapted, where move_along_path is the recursive function (only one that needs converting) that sometimes calls move_towards as well.
How can I begin this conversion and what are some basic steps to consider when converting?
# This is the base function that calls the recursive function
def get_equidistant_markers_from_polyline_points(self, points):
points = points[1::10]
# Get markers
next_marker_at = 0
markers = []
while True:
next_point = self.iterative_move_along_path(points, next_marker_at)
if next_point is not None:
markers.append({'lat': next_point[0], 'lng': next_point[1]})
next_marker_at += 80000 # About 50 miles
else:
break
print(markers)
return markers
# This function moves from point to point along a "path" of points.
# Once the "distance" threshold has been crossed then it adds the point
# to a list of equidistant markers.
def move_along_path(self, points, distance, index=0):
if index < len(points) - 1:
# There is still at least one point further from this point
# Turn points into tuples for geopy format
# point1_tuple = (points[index]['latitude'], points[index]['longitude'])
# point2_tuple = (points[index + 1]['latitude'], points[index + 1]['longitude'])
point1_tuple = (points[index]['lat'], points[index]['lng'])
point2_tuple = (points[index + 1]['lat'], points[index + 1]['lng'])
# Use geodesic method to get distance between points in meters
distance_to_next_point = geopy.distance.geodesic(point1_tuple, point2_tuple).m
if distance <= distance_to_next_point:
# Distance_to_next_point is within this point and the next
# Return the destination point with moveTowards()
return self.move_towards(point1_tuple, point2_tuple, distance)
else:
# The destination is further from the next point
# Subtract distance_to_next_point from distance and continue recursively
return self.move_along_path(points, distance - distance_to_next_point, index + 1)
else:
# There are no further points, the distance exceeds the length of the full path.
# Return None
return None
def move_towards(point1, point2, distance):
# Convert degrees to radians
lat1 = math.radians(point1[0])
lon1 = math.radians(point1[1])
lat2 = math.radians(point2[0])
d_lon = math.radians(point2[1] - point1[1])
# Find the bearing from point1 to point2
bearing = math.atan2(math.sin(d_lon) * math.cos(lat2),
math.cos(lat1) * math.sin(lat2) -
math.sin(lat1) * math.cos(lat2) *
math.cos(d_lon))
# Earth's radius
ang_dist = distance / 6371000.0
# Calculate the destination point, given the source and bearing
lat2 = math.asin(math.sin(lat1) * math.cos(ang_dist) +
math.cos(lat1) * math.sin(ang_dist) *
math.cos(bearing))
lon2 = lon1 + math.atan2(math.sin(bearing) * math.sin(ang_dist) *
math.cos(lat1),
math.cos(ang_dist) - math.sin(lat1) *
math.sin(lat2))
if math.isnan(lat2) or math.isnan(lon2):
return None
return [math.degrees(lat2), math.degrees(lon2)]
Assuming that your posted code are right, I think the following function works as an iterative approach to replace your current recursive function:
def iterative_move_along_path(self, points, distance, index=0):
while index < len(points) - 1:
# There is still at least one point further from this point
# Turn points into tuples for geopy format
# point1_tuple = (points[index]['latitude'], points[index]['longitude'])
# point2_tuple = (points[index + 1]['latitude'], points[index + 1]['longitude'])
point1_tuple = (points[index]['lat'], points[index]['lng'])
point2_tuple = (points[index + 1]['lat'], points[index + 1]['lng'])
# Use geodesic method to get distance between points in meters
distance_to_next_point = geopy.distance.geodesic(point1_tuple, point2_tuple).m
if distance <= distance_to_next_point:
# Distance_to_next_point is within this point and the next
# Return the destination point with moveTowards()
return self.move_towards(point1_tuple, point2_tuple, distance)
else:
# The destination is further from the next point
# Subtract distance_to_next_point from distance and continue recursively
distance -= distance_to_next_point
index += 1
# There are no further points, the distance exceeds the length of the full path.
# Return None
return None
As a step of your recursion doesn't seem to have a dependency relative to previously calculated values when returning from the recursion, a simple and right insertion of a while loop together with a proper update of the variables should work out.
I'm not the best with python so I'm sure you can optimize this but the general idea is that instead of calling a recursive function you can just do a while loop until your condition is met and in the loop you modify the variables in accordance to what you would've done had you sent them through as parameters to the recursive function.
def move_along_path(self, points, distance, index=0):
if index < len(points) - 1:
point1_tuple = (points[index]['lat'], points[index]['lng'])
point2_tuple = (points[index + 1]['lat'], points[index + 1]['lng'])
distance_to_next_point = geopy.distance.geodesic(point1_tuple, point2_tuple).m
while distance > distance_to_next_point:
point1_tuple = (points[index]['lat'], points[index]['lng'])
point2_tuple = (points[index + 1]['lat'], points[index + 1]['lng'])
# Use geodesic method to get distance between points in meters
distance_to_next_point = geopy.distance.geodesic(point1_tuple, point2_tuple).m
distance -= distance_to_next_point
index++
return self.move_towards(point1_tuple, point2_tuple, distance)
else
return None
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.