Having list of rectangles parallel to axis in form (minx, miny, maxx, maxy):
rectangles = [
Rectangle(90,40,110,70),
Rectangle(10,40,40,70),
Rectangle(75,60,95,80),
Rectangle(30,20,60,50),
Rectangle(100,20,130,50),
Rectangle(70,10,85,40)
]
I need to get list of groups of rectangles, where each rectangle intersects with at least one other:
[
(Rectangle(10,40,40,70), Rectangle(30,20,60,50)),
(Rectangle(70,10,85,40)),
(Rectangle(75,60,95,80), Rectangle(90,40,110,70), Rectangle(100,20,130,50))
]
The algorithm can't be naive, it needs to be fast.
What I tried:
Find python interval tree implementation - I couldn't find anything good...
I tried this repo: https://github.com/booo/rectangleintersection/blob/master/rectangleIntersection.py, it works with the example above but fails with real world data.
I read through scikit image and Shapely documentation but didn't find algorithms for rectangle intersection.
Intersecting rectangles can be viewed as connected nodes in a graph, and sets of "transitively" intersecting rectangles as Connected Components. To find out which rectangles intersect, we first do a Plane Sweep. To make this reasonably fast we need an Interval Tree. Banyan provides one:
from collections import defaultdict
from itertools import chain
from banyan import SortedDict, OverlappingIntervalsUpdator
def closed_regions(rects):
# Sweep Line Algorithm to set up adjacency sets:
neighbors = defaultdict(set)
status = SortedDict(updator=OverlappingIntervalsUpdator)
events = sorted(chain.from_iterable(
((r.left, False, r), (r.right, True, r)) for r in set(rects)))
for _, is_right, rect in events:
for interval in status.overlap(rect.vertical):
neighbors[rect].update(status[interval])
if is_right:
status.get(rect.vertical, set()).discard(rect)
else:
status.setdefault(rect.vertical, set()).add(rect)
# Connected Components Algorithm for graphs:
seen = set()
def component(node, neighbors=neighbors, seen=seen, see=seen.add):
todo = set([node])
next_todo = todo.pop
while todo:
node = next_todo()
see(node)
todo |= neighbors[node] - seen
yield node
for node in neighbors:
if node not in seen:
yield component(node)
rect.vertical BTW is the tuple (rect.top, rect.bottom).
Time complexity is O(n log n + k), where n is the number of rectangles and k the number of actual intersections. So it's pretty close to optimal.
edit: Because there was some confusion, I need to add that the rectangles are expected to have left <= right and top <= bottom. IOW, the origin of the coordinate system they are in is in the upper left corner, not in the lower left corner as is usual in geometry.
Related
Context
(minimum working example code at the end)
I am trying to create a line smoothing algorithm: From the first line vertex, a circle of a specified radius is created and this circle is intersected with the line (LineString). This results into 1 to many intersection results (Points). The farthest intersection point along the line is then taken, a new circle is created, intersected with the line and so on until the line endpoint is approached.
Visualized result with an oddly-passing parameters is in the image below. The blue line is the input, the green line is the smoothed result. The line is in the UTM Projection, but it doesn't matter.
The issue
Now I roughly know how to design most of the algorithm (I can pass it on full here if successful).
What I don't know is how to always choose the farthest intersection point along the line from the current intersection results. Shapely utilizes Point or MultiPoint as an interserciton result in this case. If I reach MultiPoint parts through geoms, they're probably ordered by ascending x coordinate (not sure though).
Attempted solution
I first thought I'd always take the last member from the circle_intersections.geoms. As you probably guess, if I apply this to the algorithm, this wouldn't work once the line changes its direction against the ascending x coordinate. Then, the intersection point I need is usually the first (or other) from the list. And thus, it gets into infinite loop, passing the intersection points between two circles.
In the image above, it worked just because the radius was set such that the searched intersection point was always further in x coordinate.
Secondly, I tried to iterate through the line and filter out the intersection point which splits the line farthest along. Here I must be making some kind of mistake I don't see.
You find both attempts in the find_farthest_intersection function.
Code
Imports:
import shapely
from shapely.geometry import Point, LineString
from shapely.ops import split
import matplotlib.pyplot as plt
The algorithm:
def smooth(point, geometry, r, first_pass=True):
circle = point.buffer(r)
circle_intersections = geometry.intersection(circle.exterior)
# It's always a single point at the first iteration
if isinstance(circle_intersections, Point):
next_point = circle_intersections
else:
next_point = find_farthest_intersection(
geometry,
circle_intersections
)
# If you want to visualize in Jupyter
# plt.plot(
# *point.buffer(6).exterior.xy,
# *circle.exterior.xy,
# *next_point.buffer(5).exterior.xy,
# )
if not first_pass and Point(geometry.coords[-1]).within(circle):
return [geometry.coords[-1]]
return [point.coords[0]] + smooth(
next_point,
geometry,
r,
first_pass=False
)
def find_farthest_intersection(geometry, intersections):
# How to return the right intersection furthest along the line?
# Naive and wrong solution
# When the line heads against X this becomes the opposite point than I want
# return circle_intersections.geoms[-1]
# Other attempted solution
# Not working, results in the same problem as with the solution above.
# Find each intersection point's distance from the
# line beginning
intersection_pnt_dsts_from_line_start = [
split(geometry, point).geoms[0].length
for point in intersections.geoms
]
# Return the intersection point with the maximum
# distance from the line beginning
return max(
zip(
intersection_pnt_dsts_from_line_start,
intersections.geoms
),
key=lambda item: item[0]
)[1]
If you want to visualize in eg. Jupyter (also uncomment pyplots in the smooth function).
# it's in UTM btw
line = shapely.wkt.loads('LINESTRING (478098.5964211893 5442495.543688663, 478229.0423690955 5442525.981076508, 478242.0869638861 5442558.592563484, 478198.6049812507 5442595.552248725, 478129.033809034 5442649.904727018, 478168.1675934059 5442691.212610522, 478209.4754769095 5442665.123420941, 478209.4754769095 5442628.163735701, 478213.8236751731 5442615.11914091, 478231.2164682273 5442599.900446988, 478252.957459545 5442593.378149592, 478305.1358387075 5442602.07454612, 478322.5286317617 5442652.078826151, 478329.0509291569 5442732.520494026, 478383.4034074512 5442762.957881871, 478353.5095443893 5442726.541721413, 478421.4501422573 5442696.37609596, 478471.7261846794 5442718.117087278, 478434.7664994393 5442728.987582936, 478399.9809133308 5442732.248731634, 478458.6815898888 5442757.250871649, 478526.0786629738 5442780.078912533, 478532.6009603692 5442848.563035184, 478579.3440917023 5442883.348621292)')
r = 100 # radius parameter
plt.plot(*line.xy)
result = smooth(
Point(line.coords[0]), # first time it's the first line node
line,
r # smaller number or more complicated lines cause fail
)
# Would then do
# plt.plot(*LineString(result).xy)
So I played around more with geometric operations and with help of #50194077 found the solution is rather simple. I can split the line by each intersection point and measure the distance from the line beginning of the first split result. The longest distance points to the farthest intersection point, i.e. the intersection point I want.
The key to facilitate the split is to create a buffer of the intersection point. Its size should addresses shapely precision issues and be as low as possible (eg 1Oe-9).
Revised function:
def find_farthest_intersection(geometry, intersection_points):
intersection_points_from_geom_distance = []
for intersection_point in intersection_points:
# Create a little buffer
precision_buffer = intersection_point.buffer(10e-9)
# Split line on buffer and take the first line fragment
intersection_points_from_geom_distance.append([
intersection_point,
split(line, precision_buffer)[0].length]
)
# Return the intersection point with the maximum
# distance from the line beginning
return max(
intersection_points_from_geom_distance,
key=lambda item: item[1]
)[0]
I am trying to pack hard-spheres in a unit cubical box, such that these spheres cannot overlap on each other. This is being done in Python.
I am given some packing fraction f, and the number of spheres in the system is N.
So, I say that the diameter of each sphere will be
d = (p*6/(math.pi*N)**)1/3).
My box has periodic boundary conditions - which means that there is a recurring image of my box in all direction. If there is a particle who is at the edge of the box and has a portion of it going beyond the wall, it will stick out at the other side.
My attempt:
Create a numpy N-by-3 array box which holds the position vector of each particle [x,y,z]
The first particle is fine as it is.
The next particle in the array is checked with all the previous particles. If the distance between them is more than d, move on to the next particle. If they overlap, randomly change the position vector of the particle in question. If the new position does not overlap with the previous atoms, accept it.
Repeat steps 2-3 for the next particle.
I am trying to populate my box with these hard spheres, in the following manner:
for i in range(1,N):
mybool=True
print("particles in box: " + str(i))
while (mybool): #the deal with this while loop is that if we place a bad particle, we need to change its position, and restart the process of checking
for j in range(0,i):
displacement=box[j,:]-box[i,:]
for k in range(3):
if abs(displacement[k])>L/2:
displacement[k] -= L*np.sign(displacement[k])
distance = np.linalg.norm(displacement,2) #check distance between ith particle and the trailing j particles
if distance<diameter:
box[i,:] = np.random.uniform(0,1,(1,3)) #change the position of the ith particle randomly, restart the process
break
if j==i-1 and distance>diameter:
mybool = False
break
The problem with this code is that if p=0.45, it is taking a really, really long time to converge. Is there a better method to solve this problem, more efficiently?
I think what you are looking for is either the hexagonal closed-packed (HCP or sometime called face-centered cubic, FCC) lattice or the cubic closed-packed one (CCP). See e.g. Wikipedia on Close-packing of equal spheres.
Since your space has periodic conditions, I believe it doesn't matter which one you chose (hcp or ccp), and they both achieve the same density of ~74.04%, which was proved by Gauss to be the highest density by lattice packing.
Update:
For the follow-up question on how to generate efficiently one such lattice, let's take as an example the HCP lattice. First, let's create a bunch of (i, j, k) indices [(0,0,0), (1,0,0), (2,0,0), ..., (0,1,0), ...]. Then, get xyz coordinates from those indices and return a DataFrame with them:
def hcp(n):
dim = 3
k, j, i = [v.flatten()
for v in np.meshgrid(*([range(n)] * dim), indexing='ij')]
df = pd.DataFrame({
'x': 2 * i + (j + k) % 2,
'y': np.sqrt(3) * (j + 1/3 * (k % 2)),
'z': 2 * np.sqrt(6) / 3 * k,
})
return df
We can plot the result as scatter3d using plotly for interactive exploration:
import plotly.graph_objects as go
df = hcp(12)
fig = go.Figure(data=go.Scatter3d(
x=df.x, y=df.y, z=df.z, mode='markers',
marker=dict(size=df.x*0 + 30, symbol="circle", color=-df.z, opacity=1),
))
fig.show()
Note: plotly's scatter3d is not a very good rendering of spheres: the marker sizes are constant (so when you zoom in and out, the "spheres" will appear to change relative size), and there is no shading, limited z-ordering faithfulness, etc., but it's convenient to interact with the plot.
Resize and clip to the unit box:
Here, a strict clipping (each sphere needs to be completely inside the unit box). Your "periodic boundary condition" is something you will need to address separately (see further below for ideas).
def hcp_unitbox(r):
n = int(np.ceil(1 / (np.sqrt(3) * r)))
df = hcp(n) * r
df += r
df = df[(df <= 1 - r).all(axis=1)]
return df
With this, you find that a radius of 0.06 gives you 608 fully enclosed spheres:
hcp_unitbox(.06).shape # (608, 3)
Where you would go next:
You may dig deeper into the effect of your so-called "periodic boundary conditions", and perhaps play with some rotations (and small translations).
To do so, you may try to generate an HCP-lattice that is large enough that any rotation will still fully enclose your unit cube. For example:
r = 0.2 # example
n = int(np.ceil(2 / r))
df = hcp(n) * r - 1
Then rotate it (by any amount) and translate it (by up to 1 radius in any direction) as you wish for your research, and clip. The "periodic boundary conditions", as you call them, present a bit of extra challenge, as the clipping becomes trickier. First, clip any sphere whose center is outside your box. Then select spheres close enough to the boundaries, or even partition the regions of interest into overlapping regions along the walls of your cube, then check for collisions among the spheres (as per your periodic boundary conditions) that fall in each such region.
I have a part of perimeter of a polygon and need to close it.Please refer this image
As I can see there is only one unique way to close the polygon without dividing the polygon and without the edges intersecting.
And the closing edges would be b->c,d->e,f->g,h->a
Is there any algo to achieve this?
I can think of only one brute force method, try every possible combination and check if it forms a closed polygon(Any good algos to check if it is closed polygon?)
Is there any better way or a known algorithm?
Note: The vertices should be connected by single straight lines only and polygon is not necessarily convex
Also, You can safely assume that these segments always form a polygon because I get these line segments from a polygon and Im trying to recreate the polygon
I think that in "well-behaved" (small gaps, not too irregular shape, etc.) cases, one might get away with following approach. The idea is to assume that the solution (particular permutation of the input line segments which are then assumed to be connected with straight lines) minimizes the length of the resulting MultiLineString defining the boundary of the polygon of interest.
To tackle this problem, the implementation below uses the 2-opt heuristic to the traveling salesman problem. It proceeds in following steps:
the set of vertices is defined as the union of the endpoints of all input line segments
it tries to connect these points in order to minimize the total length of the resulting MultiLineString under the constraint that the points belonging to the same input line segment are always connected (i.e., the 2-opt algorithm is allowed to split only edges connecting different line segments - this is handled by the extra if condition in the main double for-loop).
The result is then:
import logging
import random
import sys
from shapely.geometry import LineString, Polygon
from shapely.ops import polygonize, linemerge
#prevent shapely from showing an error message on is_valid tests
logger = logging.getLogger()
logger.setLevel(logging.ERROR)
#input lines (LineStrings)
lines = [
[(3.15,3.94), (4.06,3.91), (4.27,3.49)],
[(0.84,2.99), (0.97,3.67), (1.68,3.91), (2.68,3.92)],
[(4.46,3.23), (5.12,2.97), (4.60,2.00)],
[(4.13,1.44), (4.41,0.68), (1.14,1.99)]
]
random.shuffle(lines)
N, pnts = 0, []
pnt2line = {}
for line_id, line in enumerate(lines):
#for each line, save its endpoints and remember
#to which line each point belongs
for pnt in [line[0], line[-1]]:
pnt2line[N] = line_id
pnts.append(pnt)
N += 1
#as initial guess, try to connect these points sequentially
route = [i for i in range(0, N)]
def nrm_idx(N, idx):
return (N + idx) % N
def get_polygon(route):
#for given route, attempt to construct the resulting polygon
segments = []
m = len(route)
for idx in range(0, m):
i, j = route[idx], route[nrm_idx(m, idx+1)]
if pnt2line[i] == pnt2line[j]:
#if two consecutive points belong to the same line, consider this line
segments.append(lines[pnt2line[i]])
else:
#otherwise, connect these points with a straight line
segments.append([pnts[i], pnts[j]])
return Polygon(linemerge(segments))
def get_weight(route):
P = get_polygon(route)
return P.length if P.is_valid else sys.maxsize
def edge_is_fixed(pnt_i, pnt_j):
#check if an edge specified by point pnt_i/pnt_j can be dissected or not
#in the latter case, the points belong to the same line/line segment
return (pnt2line[pnt_i] == pnt2line[pnt_j])
def opt_swap(route, i, k):
#perform 2-opt swap
return route[0:i] + route[i:k+1][::-1] + route[k+1:]
flag = True
while flag:
flag = False
best_weight = get_weight(route)
for i in range(0, N-1):
for k in range(i+1, N):
if edge_is_fixed(route[nrm_idx(N, i-1)], route[i]) or edge_is_fixed(route[k], route[nrm_idx(N, k+1)]):
continue
new_route = opt_swap(route, i, k)
weight = get_weight(new_route)
if weight < best_weight:
route = new_route[:]
best_weight = weight
flag = True
P = get_polygon(route)
for x, y in P.exterior.coords:
print(x, y)
For your input (approximated), the result is then indeed:
Here's something that might work:
- Make a set containing only the open points (points that are only on one edge, i.e. the labelled ones in your diagram)
- Run a convex hull algorithm on that set
- Use the edges of the convex hull to complete the polygon with the existing edges. (I.e if the convex hull contains A->B, but A and B are already indirectly connected via adjacent edges in your pre-existing set of edges, discard the edge A->B in the convex hull)
EDIT
I previously suggested co-opting convex hull algorithms but that approach has shortcomings, including the case where the points would not make a convex shape.
Note that, according to your stipulations, there are sets that would not have solutions, such as:
(it's not possible to complete this into a polygon with no crossing lines, using only single straight lines between open points)
I've written a bit of code to do a simple spatial join in QGIS 2 and 2.2 (points that lie within a buffer to take attribute of the buffer). However, I'd like to employ a QgsSpatialIndex in order to speed things up a bit. Where can I go from here:
pointProvider = self.pointLayer.dataProvider()
rotateProvider = self.rotateBUFF.dataProvider()
all_point = pointProvider.getFeatures()
point_spIndex = QgsSpatialIndex()
for feat in all_point:
point_spIndex.insertFeature(feat)
all_line = rotateProvider.getFeatures()
line_spIndex = QgsSpatialIndex()
for feat in all_line:
line_spIndex.insertFeature(feat)
rotate_IDX = self.rotateBUFF.fieldNameIndex('bearing')
point_IDX = self.pointLayer.fieldNameIndex('bearing')
self.pointLayer.startEditing()
for rotatefeat in self.rotateBUFF.getFeatures():
for pointfeat in self.pointLayer.getFeatures():
if pointfeat.geometry().intersects(rotatefeat.geometry()) == True:
pointID = pointfeat.id()
bearing = rotatefeat.attributes()[rotate_IDX]
self.pointLayer.changeAttributeValue(pointID, point_IDX, bearing)
self.pointLayer.commitChanges()
To do this kind of spatial join, you can use the QgsSpatialIndex (http://www.qgis.org/api/classQgsSpatialIndex.html) intersects(QgsRectangle) function to get a list of candidate featureIDs or the nearestNeighbor (QgsPoint,n) function to get the list of the n nearest neighbours as featureIDs.
Since you only want the points that lie within the buffer, the intersects function seems most suitable. I have not tested if a degenerate bbox (point) can be used. If not, just make a very small bounding box around your point.
The intersects function returns all features that have a bounding box that intersects the given rectangle, so you will have to test these candidate features for a true intersection.
Your outer loop should be on the points (you want to to add attribute values to each point from their containing buffer).
# If degenerate rectangles are allowed, delta could be 0,
# if not, choose a suitable, small value
delta = 0.1
# Loop through the points
for point in all_point:
# Create a search rectangle
# Assuming that all_point consist of QgsPoint
searchRectangle = QgsRectangle(point.x() - delta, point.y() - delta, point.x() + delta, point.y() + delta)
# Use the search rectangle to get candidate buffers from the buffer index
candidateIDs = line_index.intesects(searchRectangle)
# Loop through the candidate buffers to find the first one that contains the point
for candidateID in candidateIDs:
candFeature == rotateProvider.getFeatures(QgsFeatureRequest(candidateID)).next()
if candFeature.geometry().contains(point):
# Do something useful with the point - buffer pair
# No need to look further, so break
break
Would anyone with Python experience be able to take a look at this for me?
I'm using this code:
https://bitbucket.org/mozman/geoalg/src/5bbd46fa2270/geoalg/voronoi.py
to perform voronoi tesselation on a group of points.
It works, but the problem is that the code only provides a list of all the vertices used to create the polygons, and which pairs must be joined together. It doesn't provide any information as to what points are used to make up each polygon, which I need.
Thanks.
context.triangles says in which Delaunay triangles input point participate. Each triangle is related to a Voronoi vertex. Triangles and vertices are stored parallel in triangles and vertices arrays.
To find Voronoi cell for a given point p, it is needed to find all vertices (triangles) in which input point is used. And than find order how these vertices are connected by edges array.
Here is a simple (not quite tested) code to do that:
from voronoi import voronoi
import random
from collections import defaultdict
num_points = 50
points = [(random.uniform(0,10), random.uniform(0,10)) for i in xrange(num_points)]
c = voronoi(points)
# For each point find triangles (vertices) of a cell
point_in_triangles = defaultdict(set)
for t_ind, ps in enumerate(c.triangles):
for p in ps:
point_in_triangles[p].add(t_ind)
# Vertex connectivity graph
vertex_graph = defaultdict(set)
for e_ind, (_, r, l) in enumerate(c.edges):
vertex_graph[r].add(l)
vertex_graph[l].add(r)
def cell(point):
if point not in point_in_triangles:
return None
vertices = set(point_in_triangles[point]) # copy
v_cell = [vertices.pop()]
vertices.add(-1) # Simulate infinity :-)
while vertices:
neighbours = vertex_graph[v_cell[-1]] & vertices
if not neighbours:
break
v_cell.append(neighbours.pop())
vertices.discard(v_cell[-1])
return v_cell
for p in xrange(num_points):
print p, cell(p)