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
Related
Assume a d-dimensional integer grid, containing n^d (n >= 1) points.
I am trying to write a function that takes the number of domain points n and the number of dimensions d and returns a set that contains all the coordinate points in the grid, as tuples.
Example: intGrid (n=2, dim=2) should return the set:
{(0,0), (0,1), (1,0), (1,1)}
Note: I cannot use numpy or any external imports.
Python has a good set of built-in modules that provides most of the basic functionality you will probably need to start getting your things done.
One of such good modules is itertools, where you will find all sorts of functions related to iterations and combinatorics. The perfect function for you is product, that you can use as below:
from itertools import product
def grid(n, dim):
return set(product(range(n), repeat=dim))
print(grid(2, 2))
# {(0, 0), (0, 1), (1, 0), (1, 1)}
print(grid(2, 3))
# {(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)}
I am filtering a subset of edges so I can iterate through them. In this case, I am excluding the "end edges", which are the final edges along a chain:
import networkx as nx
graph = nx.Graph()
graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)])
end_nodes = [n for n in graph.nodes if nx.degree(graph, n) == 1]
end_edges = graph.edges(end_nodes)
print(f"end edges: {end_edges}")
for edge in graph.edges:
if edge not in end_edges:
print(f"edge {edge} is not an end edge.")
else:
print(f"edge {edge} is an end edge.")
However, when you run this code, you get the following output:
end edges: [(0, 1), (4, 3)]
edge (0, 1) is an end edge.
edge (1, 2) is an end edge.
edge (2, 3) is an end edge.
edge (3, 4) is an end edge.
Edges (1, 2) and (2, 3) are not in end_edges, yet it returns False when the conditional edge not in end_edges is checked (seeming to imply that it is in fact included, when it seems to not be).
What is going on, and how can I filter this properly?
Python version is 3.7, NetworkX is 2.4.
You can convert end_nodes to a set of edges and keep the edges unordered.
>>> graph = nx.Graph()
>>> graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)])
>>> end_nodes = [n for n in graph.nodes if nx.degree(graph, n) == 1]
>>> end_edges = set(map(frozenset, graph.edges(end_nodes)))
>>> end_edges
{frozenset({3, 4}), frozenset({0, 1})}
>>> for edge in graph.edges:
... print(edge, frozenset(edge) in end_edges)
...
(0, 1) True
(1, 2) False
(2, 3) False
(3, 4) True
import networkx as nx
graph = nx.Graph()
graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)])
end_nodes = [n for n in graph.nodes if nx.degree(graph, n) == 1]
end_edges = graph.edges(end_nodes)
print(f"end edges: {end_edges}")
for edge in graph.edges:
if edge not in list(end_edges):
print(f"edge {edge} is not an end edge.")
else:
print(f"edge {edge} is an end edge.")
This should return what you ask for.
How can I order a list of orthogonal polygon points?
For example, I have a list of orthorgonal polygon points
data = [(2, 0), (5, 0), (5, 7), (4, 7), (4, 5), (3, 5),(3, 3), (2, 3), (2, 2), (3, 2), (3, 7), (2, 7)]
Not in order.
I want to order it in a counter-clockwise way like this:
out = [(2,0),(5,0),(5,7),(4,7),(4,5),(3,5),(3,7),(2,7),(2,3),(3,3),(3,2),(2,2)]
I had tried to use deflate _hull already but it did not correct.
Is there any algorithm to solve this problem ?
I get this:
But expected :
You can use the following recursive function:
def sort_ortho_poly(points, current=None, start=None, go_x=True):
# initialize the starting point at the bottom left, which should have the least sum of x and y
if not current:
start = current = min(points, key=sum)
# if we're going x-wards, v would be the y index (1), h would be the x index (0), and vice versa
v, h = go_x, not go_x
# remove the current point from the list of points so the next recursion would be processing the remaining points
remaining = points[:]
remaining.remove(current)
# if there is no more remaining point
if not remaining:
# we've found a path if we are able to connect back to the starting point, or else we don't
return [current] if start[v] == current[v] else []
# try each point in the remaining points that goes in the right direction from the current point
for next in [p for p in remaining if p[v] == current[v]]:
# recursively find a valid path from the remaining points after flipping the direction
path = sort_ortho_poly(remaining, next, start, not go_x)
# if we get a path that does go back to the starting point, we have to make sure the path is valid
if path:
# the current edge (e1, e2)
e1, e2 = current, next
# make sure e1 is lower than or left of e2
if e1[h] > e2[h]:
e1, e2 = e2, e1
# for each edge (p1, p2) in the path, including the final edge connecting to the starting point
for p1, p2 in zip(path, path[1:] + [start]):
# make sure p1 is lower than or left of p2
if p1[0] == p2[0] and p1[1] > p2[1] or p1[1] == p2[1] and p1[0] > p2[0]:
p1, p2 = p2, p1
# if the edge is in the same line as the current edge
if p1[v] == p2[v] == e1[v]:
# make sure the two edges don't overlap
if e1[h] < p1[h] < e2[h] or e1[h] < p2[h] < e2[h] or p1[h] < e1[h] < p2[h] or p1[h] < e2[h] < p2[h]:
break
# if the edge is perpendicular to the current edge, make sure they don't cross over
elif p1[h] == p2[h] and e1[h] < p1[h] < e2[h] and p1[v] < e1[v] < p2[v]:
break
else:
# the path is valid! we append the path to the current point and return
return [current, *path]
# return empty if it's a dead end
return []
so that:
data = [(2, 0), (5, 0), (5, 7), (4, 7), (4, 5), (3, 5),(3, 3), (2, 3), (2, 2), (3, 2), (3, 7), (2, 7)]
print(sort_ortho_poly(data))
would output:
[(2, 0), (5, 0), (5, 7), (4, 7), (4, 5), (3, 5), (3, 7), (2, 7), (2, 3), (3, 3), (3, 2), (2, 2)]
Im trying to arrange line segments to create a closed polygon with python. At the moment I've managed to solve it but is really slow when the number of segments increase (its like a bubble sort but for the end point of segments). I'm attaching a sample file of coordinates (the real ones are really complex but is useful for testing purposes). The file contains the coordinates for the segments of two separetes closed polygons. The image below is the result of the coordinates I've attached.
This is my code for joining the segments. The file 'Curve' is in the dropbox link above:
from ast import literal_eval as make_tuple
from random import shuffle
from Curve import Point, Curve, Segment
def loadFile():
print 'Loading File'
file = open('myFiles/coordinates.txt','r')
for line in file:
pairs.append(make_tuple(line))
file.close()
def sortSegment(segPairs):
polygons = []
segments = segPairs
while (len(segments) > 0):
counter = 0
closedCurve = Curve(Point(segments[0][0][0], segments[0][0][1]), Point(segments[0][1][0], segments[0][1][1]))
segments.remove(segments[0])
still = True
while (still):
startpnt = Point(segments[counter][0][0], segments[counter][0][1])
endpnt = Point(segments[counter][1][0], segments[counter][1][1])
seg = Segment(startpnt, endpnt)
val= closedCurve.isAppendable(seg)
if(closedCurve.isAppendable(seg)):
if(closedCurve.isClosed(seg)):
still =False
polygons.append(closedCurve.vertex)
segments.remove(segments[counter])
else:
closedCurve.appendSegment(Segment(Point(segments[counter][0][0], segments[counter][0][1]), Point(segments[counter][1][0], segments[counter][1][1])))
segments.remove(segments[counter])
counter = 0
else:
counter+=1
if(len(segments)<=counter):
counter = 0
return polygons
def toTupleList(list):
curveList = []
for curve in list:
pointList = []
for point in curve:
pointList.append((point.x,point.y))
curveList.append(pointList)
return curveList
def convertPolyToPath(polyList):
path = []
for curves in polyList:
curves.insert(1, 'L')
curves.insert(0, 'M')
curves.append('z')
path = path + curves
return path
if __name__ == '__main__':
pairs =[]
loadFile();
polygons = sortSegment(pairs)
polygons = toTupleList(polygons)
polygons = convertPolyToPath(polygons)
Assuming that you are only looking for the approach and not the code, here is how I would attempt it.
While you read the segment coordinates from the file, keep adding the coordinates to a dictionary with one coordinate (string form) of the segment as the key and the other coordinate as the value. At the end, it should look like this:
{
'5,-1': '5,-2',
'4,-2': '4,-3',
'5,-2': '4,-2',
...
}
Now pick any key-value pair from this dictionary. Next, pick the key-value pair from the dictionary where the key is same as the value in the previous key-value pair. So if first key-value pair is '5,-1': '5,-2', next look for the key '5,-2' and you will get '5,-2': '4,-2'. Next look for the key '4,-2' and so on.
Keep removing the key-value pairs from the dictionary so that once one polygon is complete, you can check if there are any elements left which means there might be more polygons.
Let me know if you need the code as well.
I had to do something similar. I needed to turn coastline segments (that were not ordered properly) into polygons. I used NetworkX to arrange the segments into connected components and order them using this function.
It turns out that my code will work for this example as well. I use geopandas to display the results, but that dependency is optional for the original question here. I also use shapely to turn the lists of segments into polygons, but you could just use CoastLine.rings to get the lists of segments.
I plan to include this code in the next version of PyRiv.
from shapely.geometry import Polygon
import geopandas as gpd
import networkx as nx
class CoastLine(nx.Graph):
def __init__(self, *args, **kwargs):
"""
Build a CoastLine object.
Parameters
----------
Returns
-------
A CoastLine object
"""
self = super(CoastLine, self).__init__(*args, **kwargs)
#classmethod
def read_shp(cls, shp_fn):
"""
Construct a CoastLine object from a shapefile.
"""
dig = nx.read_shp(shp_fn, simplify=False)
return cls(dig)
def connected_subgraphs(self):
"""
Get the connected component subgraphs. See the NetworkX
documentation for `connected_component_subgraphs` for more
information.
"""
return nx.connected_component_subgraphs(self)
def rings(self):
"""
Return a list of rings. Each ring is a list of nodes. Each
node is a coordinate pair.
"""
rings = [list(nx.dfs_preorder_nodes(sg)) for sg in self.connected_subgraphs()]
return rings
def polygons(self):
"""
Return a list of `shapely.Polygon`s representing each ring.
"""
return [Polygon(r) for r in self.rings()]
def poly_geodataframe(self):
"""
Return a `geopandas.GeoDataFrame` of polygons.
"""
return gpd.GeoDataFrame({'geometry': self.polygons()})
With this class, the original question can be solved:
edge_list = [
((5, -1), (5, -2)),
((6, -1), (5, -1)),
((1, 0), (1, 1)),
((4, -3), (2, -3)),
((2, -2), (1, -2)),
((9, 0), (9, 1)),
((2, 1), (2, 2)),
((0, -1), (0, 0)),
((5, 0), (6, 0)),
((2, -3), (2, -2)),
((6, 0), (6, -1)),
((4, 1), (5, 1)),
((10, -1), (8, -1)),
((10, 1), (10, -1)),
((2, 2), (4, 2)),
((5, 1), (5, 0)),
((8, -1), (8, 0)),
((9, 1), (10, 1)),
((8, 0), (9, 0)),
((1, -2), (1, -1)),
((1, 1), (2, 1)),
((5, -2), (4, -2)),
((4, 2), (4, 1)),
((4, -2), (4, -3)),
((1, -1), (0, -1)),
((0, 0), (1, 0)) ]
eG = CoastLine()
for e in edge_list:
eG.add_edge(*e)
eG.poly_geodataframe().plot()
This will be the result:
inp[0][0] = shadow[3][0]
inp[0][3] = shadow[0][0]
inp[3][3] = shadow[0][3]
inp[3][0] = shadow[3][3]
I want to turn this code into a for loop, because this is disgusting! I can't figure out how though.
You are basically picking two sets of coordinates from the series (0, 0), (0, 3), (3, 3), (3, 0), in a ring fashion. You can do so by iteration over that series with an index to use for the second point:
points = [(0, 0), (0, 3), (3, 3), (3, 0)]
for index, (x, y) in enumerate(points, -1):
shadow_x, shadow_y = points[index]
inp[x][y] = shadow[shadow_x][shadow_y]
By giving the enumerate() function a starting point of -1 we create an offset that'll find the right matching point in points.
You could also use the zip() function:
points = [(0, 0), (0, 3), (3, 3), (3, 0)]
for (x, y), (shadow_x, shadow_y) in zip(points, [points[-1]] + points):
inp[x][y] = shadow[shadow_x][shadow_y]
Pick whichever you feel fits your usecase best.
Demo (replacing the actual assignment with a print() statement to show what would be executed):
>>> points = [(0, 0), (0, 3), (3, 3), (3, 0)]
>>> for index, (x, y) in enumerate(points, -1):
... shadow_x, shadow_y = points[index]
... print(f"inp[{x}][{y}] = shadow[{shadow_x}][{shadow_y}]")
...
inp[0][0] = shadow[3][0]
inp[0][3] = shadow[0][0]
inp[3][3] = shadow[0][3]
inp[3][0] = shadow[3][3]
>>> for (x, y), (shadow_x, shadow_y) in zip(points, [points[-1]] + points):
... print(f"inp[{x}][{y}] = shadow[{shadow_x}][{shadow_y}]")
...
inp[0][0] = shadow[3][0]
inp[0][3] = shadow[0][0]
inp[3][3] = shadow[0][3]
inp[3][0] = shadow[3][3]