While working with shapely I faced a strange issue. There're 2 points p1 and p2, where the first one belongs to polygon and the second is not. When I tried to find intersection between the LineString containing these 2 points as endpoints with boundary lines of the Polygon, I received message that no intersection was found. I wonder, how is it possible?
from shapely.geometry import Polygon as SPolygon, Point, LineString
p1 = Point(5.414213562373095, 2.585786437626905)
p2 = Point(15.17279752753168, -7.172797527531679)
l = LineString([p1, p2])
l1 = LineString([(2, 2), (2, 6)])
l2 = LineString([(2, 6), (6, 6)])
l3 = LineString([(6, 6), (6, 2)])
l4 = LineString([(6, 2), (2, 2)])
sp = SPolygon([(2, 2), (2, 6), (6, 6), (6, 2)])
print "Polygon contains p1:", sp.contains(p1)
print "Polygon contains p2:", sp.contains(p2)
for i, line in enumerate((l1, l2, l3, l4)):
res = l.intersects(line)
print "Line {0} intersects l1: {1}".format(i, res)
And here are output:
Polygon contains p1: True
Polygon contains p2: False
Line 0 intersects l1: False
Line 1 intersects l1: False
Line 2 intersects l1: False
Line 3 intersects l1: False
I changed l.intersects(line) to l.intersection(line) and I did get an intersection at
LINESTRING (6 6, 6 2) at Point (6, 2)
Not sure why .instersects() is acting differently.
Then I rounded p1 and p2
p1 = Point(round(5.414213562373095, 2), round(2.585786437626905, 2))
p2 = Point(round(15.17279752753168, 2), round(-7.172797527531679, 2))
And I got 2 intersections at
LINESTRING (6 6, 6 2) at POINT (6 2)
LINESTRING (6 2, 2 2) at POINT (6 2)
This fix also worked with .intersects() (2 True's)
Shapely can be a bit touchy with floating point precision, and I usually can fix an issue by rounding. Though this may not be acceptable for you.
Related
This question already has answers here:
Python: Draw line between two coordinates in a matrix
(5 answers)
Closed 2 years ago.
Say that I have a simple meshgrid:
xx,yy = np.meshgrid(10,10)
and two data points within that meshgrid, e.g. (2,3) and (8,8). What's the easiest way to get an array of all the points in the meshgrid between the two data points forming the shortest distance (i.e. approximating a straight line within the meshgrid)?
Check this, you can also control tolerance value
import numpy as np
xx, yy = np.meshgrid(range(1, 11), range(1, 11))
# points
p1 = [2, 3]
p2 = [8, 8]
# slope
m = (p2[1] - p1[1])/(p2[0] - p1[0])
# 2D line equation
through_line = (yy - m*xx) - (p1[1] - m*p1[0])
# to get only the exact points
mask = np.isclose(through_line, 0)
print(list(zip(xx[mask], yy[mask])))
# to get approximate points
tol = 0.2
mask = np.less(np.abs(through_line), tol)
print(list(zip(xx[mask], yy[mask])))
[(2, 3), (8, 8)]
[(1, 2), (2, 3), (3, 4), (7, 7), (8, 8), (9, 9)]
I created a list of vertices of a polygon, using a 'Point' class and 'Polygon' class.. Now, to check the type of the polygon, that whether it is a 'concave; or 'convex' polygon, I want to calculate the cross-products of every three consecutive points present in the list of vertices. As an example, consider a list : vertices = [p1, p2, p3, p4, p5, p6]. Considering this list, the sequence should be p1,p2,p3...p2,p3,p4...p3,p4,p5...p4,p5,p6...and the p5,p6,p1. I mean, the last cross product will be between last 2 elements and 1st element of the list because polygon is a closed figure.
Then after calculating, the program should check whether all cross products are -ive (negative) or all are +ive (positive) because these are the conditions for the polygon to be convex.
class Polygon:
def __init__(self,*vertices):
self.vertices=[Polygon.Point(v[0],v[1]) for v in vertices]
def CrossProduct(self, A, B, C):
return (B.x - A.x) * (C.y - B.y) -(B.y - A.y) * (C.x - B.x)
#property
def shape(self): #Method for determining the type of polygon i.e. Convex or concave
# if (all cross product >=0 or all cross products <=0):
#return 'Convex'
# return 'Concave'
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
### MAIN PROGRAM ###
poly1 = Polygon((3,4), (5,11), (12,8), (9,5), (5,6)) #Concave Polygon
poly2 = Polygon((5.09,5.80), (1.68,4.90), (1.48,1.38), (4.76,0.10), (7.00,2.83)) #Convex Polygon
print(poly1.shape)
print(poly2.shape)
Create all needed 3-tuples using zip(..) and check them with all(..):
class Polygon:
def __init__(self,*vertices):
self.vertices=[Polygon.Point(v[0],v[1]) for v in vertices]
def CrossProduct(self, A, B, C):
return (B.x - A.x) * (C.y - B.y) -(B.y - A.y) * (C.x - B.x)
#property
def shape(self): #Method for determining the type of polygon i.e. Convex or concave
p0 = self.vertices[0:1]
# debugging printout of points that are going to be checked
points = list(zip(self.vertices, self.vertices[1:], self.vertices[2:] + p0))
print(*points)
print ([self.CrossProduct(*p) for p in points])
if all(self.CrossProduct(*p) >= 0 for p in points) or all(
self.CrossProduct(*p) < 0 for p in points):
return "Convex"
return "Concave"
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return str(self)
poly1 = Polygon((3,4), (5,11), (12,8), (9,5), (5,6)) # Concave
poly2 = Polygon((5.09,5.80), (1.68,4.90), (1.48,1.38), (4.76,0.10), (7.00,2.83))
print(poly1.shape) # Concave
print(poly2.shape) # Convex
Output:
((3, 4), (5, 11), (12, 8)) ((5, 11), (12, 8), (9, 5)) ((12, 8), (9, 5), (5, 6)) ((9, 5), (5, 6), (3, 4))
[-55, -30, -15, 10]
Concave
((5.09, 5.8), (1.68, 4.9), (1.48, 1.38)) ((1.68, 4.9), (1.48, 1.38), (4.76, 0.1)) ((1.48, 1.38), (4.76, 0.1), (7.0, 2.83)) ((4.76, 0.1), (7.0, 2.83), (5.09, 5.8))
[11.823200000000002, 11.8016, 11.8216, 11.8671]
Convex
After isolating the problem a bit, something like that came to my mind:
pp = [(3,4), (5,11), (12,8), (9,5), (5,6)]
pp = pp + pp[:(len(pp) % 3 - 1)]
print(pp)
c = list(zip(pp, pp[1:], pp[2:]))
def cross_product(p):
print(p)
pass
for pt in c:
cross_product(pt)
Which yields:
[(3, 4), (5, 11), (12, 8), (9, 5), (5, 6), (3, 4)]
((3, 4), (5, 11), (12, 8))
((5, 11), (12, 8), (9, 5))
((12, 8), (9, 5), (5, 6))
((9, 5), (5, 6), (3, 4))
So, first of all you have to 'pad' the initial list a bit so that it wraps properly - the length has to be divisible by 3 so that we can pack it into groups of 3 points.
After that, it's a matter of simple zip between 3 consecutive elements and a cross-product calculation.
I've got two lists containing a series of tuples (x,y), representing different points on a Cartesian plane:
a = [(0, 0), (1, 2), (1, 3), (2, 4)]
b = [(3, 4), (4, 1), (5, 3)]
I'd like to find the two points (one for each list, not within the same list) at the smaller distance, in this specific case:
[((2, 4), (3, 4))]
whose distance is equal to 1. I was using list comprehension, as:
[(Pa, Pb) for Pa in a for Pb in b \
if math.sqrt(math.pow(Pa[0]-Pb[0],2) + math.pow(Pa[1]-Pb[1],2)) <= 2.0]
but this uses a threshold value. Is there a way to append an argmin() somewhere or something like that and get only the pair [((xa, ya), (xb, yb))] smallest distance? Thanks.
import numpy
e = [(Pa, Pb) for Pa in a for Pb in b]
e[numpy.argmin([math.sqrt(math.pow(Pa[0]-Pb[0],2) + math.pow(Pa[1]-Pb[1],2)) for (Pa, Pb) in e])]
Will use argmin as you suggested and return ((2, 4), (3, 4))
Just use list comprehension and min as follows:
dist = [(Pa, Pb, math.sqrt(math.pow(Pa[0]-Pb[0],2) + math.pow(Pa[1]-Pb[1],2)))
for Pa in a for Pb in b]
print min(dist, key=lambda x:x[2])[0:2]
Solution similar to DevShark's one with a few optimization tricks:
import math
import itertools
import numpy as np
def distance(p1, p2):
return math.hypot(p2[0] - p1[0], p2[1] - p1[1])
a = [(0, 0), (1, 2), (1, 3), (2, 4)]
b = [(3, 4), (4, 1), (5, 3)]
points = [tup for tup in itertools.product(a, b)]
print(points[np.argmin([distance(Pa, Pb) for (Pa, Pb) in points])])
You could also use the scipy.spatial library with the following :
import scipy.spatial as spspat
import numpy as np
distanceMatrix = spspat.distance_matrix(a,b)
args = np.argwhere(distanceMatrix==distanceMatrix.min())
print(args)
This will return you the following : array([[3, 0]]) , being the position of the points in each list.
This should also work in any dimension.
I'm trying to write an algorithm, that can check if a polygon is within another, but they share a border.
from shapely.geometry import Polygon
poly = Polygon(((0, 0), (0, 2), (2, 2), (2, 0)))
poly2 = Polygon(((0, 0), (0, 1), (1, 1), (1, 0)))
# poly.contains(poly2) will return False
Is there a other way than checking if at least one point from poly2 is within poly and they don't cross (points within and outside poly)?
from shapely.geometry import Polygon
poly = Polygon(((0, 0), (0, 2), (2, 2), (2, 0)))
poly2 = Polygon(((0, 0), (0, 1), (1, 1), (1, 0)))
poly3 = Polygon(((0, 0), (0, 1), (-1, 1), (-1, 0)))
# desired result poly.func(poly2) == True poly.func(poly3) == False
You can examine detailed spatial relationships using the DE-9IM model, which "intersects", "contains", etc., are based on. This data can be obtained from relate:
A = poly
B = poly2
rel = A.relate(B)
print(rel) # 212F11FF2
And as viewed in JTS TestBuilder:
So to find "at least one point from poly2 is within poly and they don't cross (points within and outside poly)" and if they "share a border", this is spatial-predicate-speak this requires all conditions to be true:
I(A) ∩ I(B) = 2 or rel[0] == '2' to find if at least one point from B is within A resulting with an areal intersection
B(A) ∩ B(B) = {0, 1} or rel[5] in '01' to see if the two share a border (point or line)
E(A) ∩ I(B) = F or rel[6] == 'F' to find if B don't cross outside A
So, make your own spatial predicate:
def my_criteria(A, B):
rel = A.relate(B)
return rel[0] == '2' and rel[5] in '01' and rel[6] == 'F'
my_criteria(poly, poly2) # True
my_criteria(poly2, poly) # False
I am dealing with polygons composed of square tiles on a 2D grid. A polygon is simply stored as a list of tuples, with each tuple representing the coordinates of a tile. The polygons are always contiguous and have no holes.
What I want to be able to do is determine which of the tiles represent vertices along the border of the polygon, such that later I could trace between each one to produce the polygon's border, or determine the distance between two consecutive vertices to find the length of a side, etc.
Here is an example of a polygon (a 5x4 rectangle with a 3x2 rectangle subtracted from the top left, producing a backward 'L'):
polygon_tiles = [(3, 0), (4, 0), (3, 1), (4, 1), (0, 2), (1, 2), (2, 2), (3, 2),
(4, 2), (0, 3), (1, 3), (2, 3), (3, 3), (4, 3)]
Ideally the algorithm I am seeking would produce a result that looked like this:
polygon_verts = [(3, 0), (4, 0), (4, 3), (0, 3), (0, 2), (3, 2)]
with the vertices listed in order tracing around the border clockwise.
Just fiddling around with some test cases, this problem seems to be much more complicated than I would have thought, especially in weird circumstances like when a polygon has a 1-tile-wide extrusion (in this case one of the tiles might have to be stored as a vertex twice??).
I'm working in Python, but any insight is appreciated, even if it's in pseudocode.
Assuming your shape has no internal holes.
Find the topmost row. Pick the leftmost tile of this row. This guarantees we begin on a corner.
From this tile, attempt to go straight right If you can't, go straight downright, straight down, etc until you have picked a direction. This guarnatees we can trace a clockwise perimeter of the polygon
Continue to take steps in your chosen direction. After each step:
If the next step would be onto a tile, rotate counterclockwise and look again.
If the next step would be onto an empty space, rotate clockwise and look again.
Stop rotating once you have moved onto empty space and back onto a tile again.
If we rotated from the initial direction, we must be standing on a vertex. Mark it as such.
Mark every other tile you traverse as being part of the edge.
Keep walking the edge until you arrive at your initial tile. You may walk over tiles more than once in the case of 1 tile extrusions.
If this algorithm doesn't make sense in your head, try getting out some paper and following it by hand :)
This problem is a convex hull variation, for which e.g. the gift wrapping algorithm could be applied. The constraints of discrete coordinates and line directions lead to simplifications. Here is some python code that gives the desired answer (Patashu's answer is in the same spirit):
#!/usr/bin/python
import math
def neighbors(coord):
for dir in (1,0):
for delta in (-1,1):
yield (coord[0]+dir*delta, coord[1]+(1-dir)*delta)
def get_angle(dir1, dir2):
angle = math.acos(dir1[0] * dir2[0] + dir1[1] * dir2[1])
cross = dir1[1] * dir2[0] - dir1[0] * dir2[1]
if cross > 0:
angle = -angle
return angle
def trace(p):
if len(p) <= 1:
return p
# start at top left-most point
pt0 = min(p, key = lambda t: (t[1],t[0]))
dir = (0,-1)
pt = pt0
outline = [pt0]
while True:
pt_next = None
angle_next = 10 # dummy value to be replaced
dir_next = None
# find leftmost neighbor
for n in neighbors(pt):
if n in p:
dir2 = (n[0]-pt[0], n[1]-pt[1])
angle = get_angle(dir, dir2)
if angle < angle_next:
pt_next = n
angle_next = angle
dir_next = dir2
if angle_next != 0:
outline.append(pt_next)
else:
# previous point was unnecessary
outline[-1]=pt_next
if pt_next == pt0:
return outline[:-1]
pt = pt_next
dir = dir_next
polygon_tiles = [(3, 0), (4, 0), (3, 1), (4, 1), (0, 2), (1, 2), (2, 2), (3, 2),
(4, 2), (0, 3), (1, 3), (2, 3), (3, 3), (4, 3)]
outline = trace(polygon_tiles)
print(outline)
I would just calculate the slopes of the lines between the vertices
# Do sort stuff
vertices = []
for position, polygon in enumerate(polygon_tiles):
# look for IndexErrors
try:
polygon_tiles[position+1]
except IndexError:
break
try:
polygon_tiles[position+2]
except IndexError:
# Bad practice
position = position - 1
# calculate the slope of the line between of vertex 1 and vertex 2
s1 = (polygon_tiles[position+1][1] - polygon[1]) / (polygon_tiles[position+1][0] - polygon[0])
# calculate the slope of vertex 2 and vertex 3
s2 = (polygon_tiles[position+2][1] - polygon_tiles[position+1][1]) / (polygon_tiles[position+2][0] - polygon_tiles[position+1][0])
# if the slopes differ then you have a vertex
if d1 != d2:
vertices.append(polygon_tiles[position+1])