Finding the area of intersection of multiple overlapping rectangles in Python - python

I tried to using the algorithm shown here: https://discuss.leetcode.com/topic/15733/my-java-solution-sum-of-areas-overlapped-area
However, that algorithm only deals with finding the areas of only TWO overlapped rectangles.
How would I go on about finding the area of the intersection of say 3, or 4 or 5, etc number of overlapping rectangles, if I know the length, breadth of each rectangle?

Shapely is a good library for stuff like this.
from shapely.geometry import box
# make some rectangles (for demonstration purposes and intersect with each other)
rect1 = box(0,0,5,2)
rect2 = box(0.5,0.5,3,3)
rect3 = box(1.5,1.5,4,6)
rect_list = [rect1, rect2, rect3]
# find intersection of rectangles (probably a more elegant way to do this)
for rect in rect_list[1:]:
rect1 = rect1.intersection(rect)
intersection = rect1
To visualize what's happening here. I plot the rectangles and their intersection:
from matplotlib import pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Polygon
# plot the rectangles before and after merging
patches = PatchCollection([Polygon(a.exterior) for a in rect_list], facecolor='red', linewidth=.5, alpha=.5)
intersect_patch = PatchCollection([Polygon(intersection.exterior)], facecolor='red', linewidth=.5, alpha=.5)
# make figure
fig, ax = plt.subplots(1,2, subplot_kw=dict(aspect='equal'))
ax[0].add_collection(patches, autolim=True)
ax[0].autoscale_view()
ax[0].set_title('separate polygons')
ax[1].add_collection(intersect_patch, autolim=True)
ax[1].set_title('intersection = single polygon')
ax[1].set_xlim(ax[0].get_xlim())
ax[1].set_ylim(ax[0].get_ylim())
plt.show()

Related

Plotting Centroids From Strings With .centroid.wkt in Shapely

I am working in Shapely and have come across a simple issue that I have spent too long trying to figure out.
The code below starts with 6 points, buffers them into circular polygons, unions and polygonizes them to find overlap. Each overlapping region becomes its own polygon and can be colored independently.
What I'm trying to do next is plot the centroids of all of the polygons in the result object. I calculate the centroids with
centroidCoords = poly.centroid.wkt
which gives the points in this format:
POINT (7.210123936676805 -0.0014481952154823)
POINT (5.817327756517152 -1.0513260084561042)
POINT (5.603133733696165 -2.7765635631249412)
...
They say they are points, but they're actually strings and I can't figure out how to convert them to points, like: (7.210123936676805, -0.0014481952154823). I've tried converting them to tuples but ran into errors. I have found lots of suggestions using geopandas, but I am having trouble getting geopandas to install on my machine. I don't need the coordinates changed to lat/long or anything, can't I just plot them ?! They're so close!
Here's the rest of the code. Your help is greatly appreciated.
import matplotlib.pyplot as plt
from shapely.geometry import Point, LineString
from shapely.ops import unary_union, polygonize
from matplotlib.pyplot import cm
import numpy as np
def plot_coords(coords, color):
pts = list(coords)
x, y = zip(*pts)
# print(color)
plt.plot(x,y, color=color)
plt.fill_between(x, y, facecolor=color)
def plot_polys(polys, colors):
for poly, color in zip(polys, colors):
plot_coords(poly.exterior.coords, color)
points = [Point(6, 0),
Point(5, 1.72),
Point(3, 1.73),
Point(2, 0),
Point(3, -1.73),
Point(5, -1.73)]
# buffer points to create circle polygons
circles = []
for point in points:
circles.append(point.buffer(2))
# unary_union and polygonize to find overlaps
rings = [LineString(list(pol.exterior.coords)) for pol in circles]
union = unary_union(rings)
result = [geom for geom in polygonize(union)]
# plot resulting polygons
colors = cm.rainbow(np.linspace(0, 1, len(result)))
centroidCoords = []
for poly in result:
centroidCoords = poly.centroid.wkt
# print(centroidCoords)
plot_polys(result, colors)
plt.show()

How to change color of particular edge for polygon in python?

There is a polygon and I am wondering how can I change color of particular edge of it like figure below.
import matplotlib.pyplot as plt
import numpy as np
## -----------------------Initialize Geometry-----------------------
pixels = 600
my_dpi = 100
coord = [[-150,-200],[300,-200],[300,0],[150,200],[-150,200]]
fig = plt.figure(figsize=( pixels/my_dpi, pixels/my_dpi), dpi=my_dpi)
plt.axes([0,0,1,1])
rectangle = plt.Rectangle((-300, -300), 600, 600, fc='k')
plt.gca().add_patch(rectangle)
polygon = plt.Polygon(coord,fc='w')
plt.gca().add_patch(polygon)
plt.axis('off')
plt.axis([-300,300,-300,300])
plt.savefig('figure1/5.jpg',dpi=my_dpi)
The easiest way to do this would be to simply plot a line between the two relevant vertices of the polygon, i.e.
plt.plot([coords[0,0], coords[-1,0]], [coords[0,1], coords[-1,1]], color='r', lw=5)
Would give you
Although I recommend adding a border to the polygon with the same width as this line of the same color as the facecolor:
polygon = plt.Polygon(coord,fc='w',ec='w',lw=5)
As a way to make the red line appear flush. You can change which edge is colored you simply change the indices of coords[i,j] in plt.plot() and as long as the indices are adjacent (with wrapping - so last index and first index are adjacent) the line drawn will be an edge and not a diagonal.
Also note you can shorten the plotting command by using slices or a helper function but I have neglected this for the sake of being explicit.

Matplotlib to plot hundreds of Rectangle contours

I want to plot a few Rectangle outlines using matplotlib. The thing is, I need tons of them, so the "normal" way of drawing rectangles is pretty slow. I solved this using How to speed up plot.... The thing is, I am not able anymore to just plot the edge of the rectangles using fill=None or edgecolor=... and facecolor=None.
See a toy example of my code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle
def plot_rectangle_from_area(area, color):
"""Plot a rectangle for a given area with color"""
return Rectangle(xy=(area["min_x"], area["min_y"]), width=area["max_x"] - area["min_x"],
height=area["max_y"] - area["min_y"],
fill=None) # linewidth=0, edgecolor=color, facecolor=None, zorder=100, alpha=0,
sample_areas = [{"min_x": -1, "max_x": 0.4, "min_y": 0.7, "max_y": 1},
{"min_x": 0.5, "max_x": 1, "min_y": 0.1, "max_y": 0.5}]
rectangles = []
fig, ax = plt.subplots()
# ... print some contour via meshgrid
if sample_areas:
for area_i in sample_areas:
rectangles.append(plot_rectangle_from_area(area_i, color="r"))
# ... some more cases
# Append the rectangles all at once instead of on their own, see:
# https://stackoverflow.com/questions/33905305/how-to-speed-up-the-plot-of-a-large-number-of-rectangles-with-matplotlib
ax.add_collection(PatchCollection(rectangles))
# ... legend, save, grid, ...
plt.show()
At first I create a array for all the Rectangles, the append to it and use PatchCollection to plot it 1. The following example does exactly the same, just without the PatchCollection and is working fine.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle
def plot_rectangle_from_area(area, color):
"""Plot a rectangle from given area"""
return Rectangle(xy=(area["min_x"], area["min_y"]), width=area["max_x"] - area["min_x"],
height=area["max_y"] - area["min_y"],
fill=None) # linewidth=0, edgecolor=color, facecolor=None, zorder=100, alpha=0,
sample_areas = [{"min_x": -1, "max_x": 0.4, "min_y": 0.7, "max_y": 1},
{"min_x": 0.5, "max_x": 1, "min_y": 0.1, "max_y": 0.5}]
fig, ax = plt.subplots()
if sample_areas:
for area_i in sample_areas:
ax.add_patch(plot_rectangle_from_area(area_i, color="r"))
plt.show()
Here are some plots I created with both of these codes. On the left the wished result using the slow method, on the right the result I get with PatchCollection:
I tried multiple combinations of fill, edgecolor, facecolor and even the suggestion with zorder from here.
Is it possible to use the "fast" way of creating rectangles and have just the borders shown?
Yes - looking at the documentation for PatchCollection, there is a parameter called match_original, which - when True - will set the properties of the patches to match those of the original rectangles.
So simply change
ax.add_collection(PatchCollection(rectangles))
to
ax.add_collection(PatchCollection(rectangles, match_original=True))

Overlap area of 2 ellipses using matplotlib

Does anyone know if it is possible to calculate the overlapping area of two ellipses using matplotlib.patches.Ellipse.
I have to ellipses like this:
And i would like to calculate the ratio between the overlap area and the are of the individual ellipses.
Is this possible using only the Ellipse from matplotlib.patches
You cannot compute the area of the intersect with matplotlib (at least not to my knowledge), but you can use shapely to do so and then use matplotlib to visualise the result. Here a quick demo:
from matplotlib import pyplot as plt
from shapely.geometry.point import Point
from shapely import affinity
from matplotlib.patches import Polygon
import numpy as np
def create_ellipse(center, lengths, angle=0):
"""
create a shapely ellipse. adapted from
https://gis.stackexchange.com/a/243462
"""
circ = Point(center).buffer(1)
ell = affinity.scale(circ, int(lengths[0]), int(lengths[1]))
ellr = affinity.rotate(ell, angle)
return ellr
fig,ax = plt.subplots()
##these next few lines are pretty important because
##otherwise your ellipses might only be displayed partly
##or may be distorted
ax.set_xlim([-5,5])
ax.set_ylim([-5,5])
ax.set_aspect('equal')
##first ellipse in blue
ellipse1 = create_ellipse((0,0),(2,4),10)
verts1 = np.array(ellipse1.exterior.coords.xy)
patch1 = Polygon(verts1.T, color = 'blue', alpha = 0.5)
ax.add_patch(patch1)
##second ellipse in red
ellipse2 = create_ellipse((1,-1),(3,2),50)
verts2 = np.array(ellipse2.exterior.coords.xy)
patch2 = Polygon(verts2.T,color = 'red', alpha = 0.5)
ax.add_patch(patch2)
##the intersect will be outlined in black
intersect = ellipse1.intersection(ellipse2)
verts3 = np.array(intersect.exterior.coords.xy)
patch3 = Polygon(verts3.T, facecolor = 'none', edgecolor = 'black')
ax.add_patch(patch3)
##compute areas and ratios
print('area of ellipse 1:',ellipse1.area)
print('area of ellipse 2:',ellipse2.area)
print('area of intersect:',intersect.area)
print('intersect/ellipse1:', intersect.area/ellipse1.area)
print('intersect/ellipse2:', intersect.area/ellipse2.area)
plt.show()
The resulting plot looks like this:
And the computed areas (printed out to the terminal) are:
area of ellipse 1: 25.09238792436751
area of ellipse 2: 18.81929094327563
area of intersect: 13.656608779925698
intersect/ellipse1: 0.5442530547945023
intersect/ellipse2: 0.7256707397260032
Note that I adapted the code to generate the ellipse-shaped polygon from this post. Hope this helps.

How do I put a circle with annotation in matplotlib?

I plot a graph created by networkx with matplotlib. Now, I'd like to add annotation to a specific node with a circle around. For instance,
I use plt.annotate(*args, **kwargs) with the following code,
# add annotate text
pos = nx.get_node_attributes(G, 'pos')
pos_annotation_node = pos['Medici']
ax2.annotate('Midici',
xy=pos_annotation_node,
xytext=(i+0.2 for i in pos_annotation_node),
color='blue',
arrowprops=dict(facecolor='blue', shrink=0.01)
)
And I got this ugly graph,
I have two questions:
how do I draw a circle around the node 6 as shown in first figure.
to get a nice looking figure, I need manually set the value of xytext many times. Is there a better way?
If you use the fancyarrow arrowprops syntax as demonstrated in annotation_demo2, there is a shrinkA and shrinkB option that lets you shrink your arrow tail (shrinkA) and tip (shrinkB) independently, in points units.
Here's some arbitrary setup code:
import matplotlib.pyplot as plt
import numpy as np
# Some data:
dat = np.array([[5, 3, 4, 4, 6],
[1, 5, 3, 2, 2]])
# This is the point you want to point out
point = dat[:, 2]
# Make the figure
plt.figure(1, figsize=(4, 4))
plt.clf()
ax = plt.gca()
# Plot the data
ax.plot(dat[0], dat[1], 'o', ms=10, color='r')
ax.set_xlim([2, 8])
ax.set_ylim([0, 6])
And here is the code that puts a circle around one of these points and draws an arrow that is shrunk-back at the tip only:
circle_rad = 15 # This is the radius, in points
ax.plot(point[0], point[1], 'o',
ms=circle_rad * 2, mec='b', mfc='none', mew=2)
ax.annotate('Midici', xy=point, xytext=(60, 60),
textcoords='offset points',
color='b', size='large',
arrowprops=dict(
arrowstyle='simple,tail_width=0.3,head_width=0.8,head_length=0.8',
facecolor='b', shrinkB=circle_rad * 1.2)
)
Note here that:
1) I've made the marker face color of the circle transparent with mfc='none', and set the circle size (diameter) to twice the radius.
2) I've shrunk the arrow by 120% of the circle radius so that it backs off of the circle just a bit. Obviously you can play with circle_rad and the value of 1.2 until you get what you want.
3) I've used the "fancy" syntax that defines several of the arrow properties in a string, rather than in the dict. As far as I can tell the shrinkB option is not available if you don't use the fancy arrow syntax.
4) I've used the textcoords='offset points' so that I can specify the position of the text relative to the point, rather than absolute on the axes.
how do I draw a circle around the node 6 as shown in first figure.
You get a center of node #6 (tuple pos). Use this data to set the blue circle position.
to get a nice looking figure, I need manually set the value of xytext many times. Is there a better way?
Make a list of your labels and iterate in it and in tuples of coordinates of nodes to post annotate text. Look to comments of a code.
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from matplotlib.patches import Circle
import matplotlib.patches as patches
import numpy as np
from matplotlib.font_manager import FontProperties
font = FontProperties()
font.set_weight('bold')
font.set_size('medium')
labels = ["Midici","Firenze"]
image = mpimg.imread("g.png") # just a image of your graph
plt.imshow(image)
ax = plt.gca()
# set your own radius and centers of circles in loop, like here
r = 11; c = (157,177)
circ1 = patches.Circle(c,2*r,lw=3.,ec='b',fill=False)
ax.add_artist(circ1)
circ1.set_clip_box(ax.bbox)
# annotate circles
# I have one circle but for your array pos_annotation_node
# you need 'i' to extract proper position
for i,label in enumerate(labels):
annot_array_end = (c[0], c[1]+2*(-1)**i*r)
annot_text_pos = (c[0]+3,c[1]+5*(-1)**i*r)
ax.annotate(label,
xy= annot_array_end,
xytext=annot_text_pos,
color='b',
fontproperties=font,
arrowprops=dict(fc='b', shrink=.005)
)
plt.show()
Just an observation for other people finding this thread. Not everything has to be done in Matplotlib.
It might well be easier to use a drawing package, with your network chart saved as a PDF (or PNG) in the background...

Categories

Resources