Color Overlapping Polygons in Shapely Python - python

I am working with a set of overlapping circles in Shapely. I am trying to figure out how to color each circle fragment in my results list.
Here's my code:
import matplotlib.pyplot as plt
from shapely.geometry import Point, LineString, Polygon, MultiPoint, MultiPolygon
from shapely.ops import unary_union, polygonize
def plot_coords(coords):
pts = list(coords)
x, y = zip(*pts)
plt.plot(x,y)
def plot_polys(polys):
for poly in polys:
plot_coords(poly.exterior.coords)
plt.fill_between(*poly.exterior.xy, alpha=.5)
points = [Point(0, 0),
Point(2,0),
Point(1,2),
Point(-1,2),
Point(-2,0),
Point(-1,-2),
Point(1,-2)]
# buffer points to create circle polygons
circles = []
for point in points:
circles.append(point.buffer(2.25))
# 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
plot_polys(result)
plt.show()
Here's the plot:
In this example, 7 points buffered by 2.25 results in a total of 43 polygons due to all of the overlap. I want to choose the colors for each of the 43 segments. Results is a list object, so I am wondering if I can add a variable for color to each list item, or if I need to add the color in the plot_coords or plot_polys functions.
I have tried changing the "facecolor" and "linewidth" in the plt.fill_between line, from this tutorial, but it isn't working right, so I'm unsure where the instructions for color are actually coming from.
Any help would be greatly appreciated!

I don't know if this is what you tried to do, but here I assign one color to every Polygon
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(0, 0),
Point(2,0),
Point(1,2),
Point(-1,2),
Point(-2,0),
Point(-1,-2),
Point(1,-2)]
# buffer points to create circle polygons
circles = []
for point in points:
circles.append(point.buffer(2.25))
# 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)))
plot_polys(result, colors)
plt.show()

Related

Fill facecolor in convex hulls for custom seaborn mapping function

I'm trying to overlay shaded convex hulls to the different groups in a scatter seaborn.relplot using Matplotlib. Based on this question and the Seaborn example, I've been able to successfully overlay the convex hulls for each sex in the penguins dataset.
# Import libraries
import pandas as pd
import numpy as np
from scipy.spatial import ConvexHull
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('whitegrid')
data = sns.load_dataset("penguins")
xcol = 'bill_length_mm'
ycol = 'bill_depth_mm'
g = sns.relplot(data = data, x=xcol, y = ycol,
hue = "sex", style = "sex",
col = 'species', palette="Paired",
kind = 'scatter')
def overlay_cv_hull_dataframe(x, y, color, **kwargs):
data = kwargs.pop('data')
# Get the Convex Hull for each group based on hue
for _, group in data.groupby('hue'):
points = group[['x', 'y']].values
hull = ConvexHull(points)
for simplex in hull.simplices:
plt.fill(points[simplex, 0], points[simplex, 1],
facecolor = color, alpha=0.5,
edgecolor = color)
# Overlay convex hulls
g.map_dataframe(overlay_cv_hull_dataframe, x=xcol, y=ycol,
hue='sex')
g.set_axis_labels(xcol, ycol)
However, the convex hulls are not filled in with the same color as the edge, even though I specified that
plt.fill(points[simplex, 0], points[simplex, 1],
facecolor = color, alpha=0.5,
edgecolor = color, # color is an RGB tuple like (0.12, 0.46, 0.71)
)
I've also tried setting facecolor='lightsalmon' like this example and removing the alpha parameter, but get the same plot. I think I'm really close but I'm not sure what else to try.
How can I get the convex hulls to be filled with the same color as edgecolor and the points?
(Your code seems to have some typos, writing 'x', 'y' and 'hue' instead of x, y and hue).
simplex contains the indices of the coordinates of one edge of the convex hull. To fill a polygon, you need all edges, or hull.vertices.
g.map_dataframe only calls the function once per subplot. As such, color is only usable if you wouldn't be using hue. Instead, you'll need to store the individual colors in a palette dictionary. In plt.fill, alpha applies both to the face and the edge color. You can use to_rgba to give a transparrency to the face color while using the edge color without alpha.
import pandas as pd
import numpy as np
from scipy.spatial import ConvexHull
import matplotlib.pyplot as plt
from matplotlib.colors import to_rgba
import seaborn as sns
sns.set_style('whitegrid')
data = sns.load_dataset("penguins")
xcol = 'bill_length_mm'
ycol = 'bill_depth_mm'
hues = data["sex"].unique()
colors = sns.color_palette("Paired", len(hues))
palette = {hue_val: color for hue_val, color in zip(hues, colors)}
g = sns.relplot(data=data, x=xcol, y=ycol, hue="sex", style="sex", col='species', palette=palette, kind='scatter')
def overlay_cv_hull_dataframe(x, y, color, data, hue):
# Get the Convex Hull for each group based on hue
for hue_val, group in data.groupby(hue):
hue_color = palette[hue_val]
points = group[[x, y]].values
hull = ConvexHull(points)
plt.fill(points[hull.vertices, 0], points[hull.vertices, 1],
facecolor=to_rgba(hue_color, 0.2),
edgecolor=hue_color)
# Overlay convex hulls
g.map_dataframe(overlay_cv_hull_dataframe, x=xcol, y=ycol, hue='sex')
g.set_axis_labels(xcol, ycol)
plt.show()

How to prevent scatterplot from getting outside of a shapefile map? [duplicate]

I am drawing a map using basemap from matplotlib. The data are spreaded all over the world, but I just want to retain all the data on the continent and drop those on the ocean. Is there a way that I can filter the data, or is there a way to draw the ocean again to cover the data?
There's method in matplotlib.basemap: is_land(xpt, ypt)
It returns True if the given x,y point (in projection coordinates) is over land, False otherwise. The definition of land is based upon the GSHHS coastline polygons associated with the class instance. Points over lakes inside land regions are not counted as land points.
For more information, see here.
is_land() will loop all the polygons to check whether it's land or not. For large data size, it's very slow. You can use points_inside_poly() from matplotlib to check an array of points quickly. Here is the code. It doesn't check lakepolygons, if you want remove points in lakes, you can add your self.
It took 2.7 seconds to check 100000 points on my PC. If you want more speed, you can convert the polygons into a bitmap, but it's a little difficult to do this. Please tell me if the following code is not fast enought for your dataset.
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.nxutils as nx
def points_in_polys(points, polys):
result = []
for poly in polys:
mask = nx.points_inside_poly(points, poly)
result.extend(points[mask])
points = points[~mask]
return np.array(result)
points = np.random.randint(0, 90, size=(100000, 2))
m = Basemap(projection='moll',lon_0=0,resolution='c')
m.drawcoastlines()
m.fillcontinents(color='coral',lake_color='aqua')
x, y = m(points[:,0], points[:,1])
loc = np.c_[x, y]
polys = [p.boundary for p in m.landpolygons]
land_loc = points_in_polys(loc, polys)
m.plot(land_loc[:, 0], land_loc[:, 1],'ro')
plt.show()
The HYRY's answer won't work on new versions of matplotlib (nxutils is deprecated). I've made a new version that works:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from matplotlib.path import Path
import numpy as np
map = Basemap(projection='cyl', resolution='c')
lons = [0., 0., 16., 76.]
lats = [0., 41., 19., 51.]
x, y = map(lons, lats)
locations = np.c_[x, y]
polygons = [Path(p.boundary) for p in map.landpolygons]
result = np.zeros(len(locations), dtype=bool)
for polygon in polygons:
result += np.array(polygon.contains_points(locations))
print result
The simplest way is to use basemap's maskoceans.
If for each lat, lon you have a data and you want to
use contours:
After meshgrid and interpolation:
from scipy.interpolate import griddata as gd
from mpl_toolkits.basemap import Basemap, cm, maskoceans
xi, yi = np.meshgrid(xi, yi)
zi = gd((mlon, mlat),
scores,
(xi, yi),
method=grid_interpolation_method)
#mask points on ocean
data = maskoceans(xi, yi, zi)
con = m.contourf(xi, yi, data, cmap=cm.GMT_red2green)
#note instead of zi we have data now.
Update (much faster than in_land or in_polygon solutions):
If for each lat, lon you don't have any data, and you just want to scatter the points only over land:
x, y = m(lons, lats)
samples = len(lons)
ocean = maskoceans(lons, lats, datain=np.arange(samples),
resolution='i')
ocean_samples = np.ma.count_masked(ocean)
print('{0} of {1} points in ocean'.format(ocean_samples, samples))
m.scatter(x[~ocean.mask], y[~ocean.mask], marker='.', color=colors[~ocean.mask], s=1)
m.drawcountries()
m.drawcoastlines(linewidth=0.7)
plt.savefig('a.png')
I was answering this question, when I was told that it would be better to post my answer over here. Basically, my solution extracts the polygons that are used to draw the coastlines of the Basemap instance and combines these polygons with the outline of the map to produce a matplotlib.PathPatch that overlays the ocean areas of the map.
This especially useful if the data is coarse and interpolation of the data is not wanted. In this case using maskoceans produces a very grainy outline of the coastlines, which does not look very good.
Here is the same example I posted as answer for the other question:
from matplotlib import pyplot as plt
from mpl_toolkits import basemap as bm
from matplotlib import colors
import numpy as np
import numpy.ma as ma
from matplotlib.patches import Path, PathPatch
fig, ax = plt.subplots()
lon_0 = 319
lat_0 = 72
##some fake data
lons = np.linspace(lon_0-60,lon_0+60,10)
lats = np.linspace(lat_0-15,lat_0+15,5)
lon, lat = np.meshgrid(lons,lats)
TOPO = np.sin(np.pi*lon/180)*np.exp(lat/90)
m = bm.Basemap(resolution='i',projection='laea', width=1500000, height=2900000, lat_ts=60, lat_0=lat_0, lon_0=lon_0, ax = ax)
m.drawcoastlines(linewidth=0.5)
x,y = m(lon,lat)
pcol = ax.pcolormesh(x,y,TOPO)
##getting the limits of the map:
x0,x1 = ax.get_xlim()
y0,y1 = ax.get_ylim()
map_edges = np.array([[x0,y0],[x1,y0],[x1,y1],[x0,y1]])
##getting all polygons used to draw the coastlines of the map
polys = [p.boundary for p in m.landpolygons]
##combining with map edges
polys = [map_edges]+polys[:]
##creating a PathPatch
codes = [
[Path.MOVETO] + [Path.LINETO for p in p[1:]]
for p in polys
]
polys_lin = [v for p in polys for v in p]
codes_lin = [c for cs in codes for c in cs]
path = Path(polys_lin, codes_lin)
patch = PathPatch(path,facecolor='white', lw=0)
##masking the data:
ax.add_patch(patch)
plt.show()
This produces the following plot:
Hope this is helpful to someone :)

How to set the markersize in kilometers?

I want to encircle a certain point. The radius of the circle needs to be 5 km, but how do I set my markersize so that the circle is 5 km on the map?
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
width, height = 400000, 320000
ax=plt.figure(figsize=(20,10))
lonA =[2.631547,2.861595,2.931014]
latA =[51.120983,51.209122,51.238868]
m= Basemap(width=width,height=height,projection='lcc',
resolution='h',lat_0=52.35,lon_0=4.5)
m.drawmapboundary(fill_color='turquoise')
m.fillcontinents(color='white',lake_color='aqua')
m.drawcountries(linestyle='--')
scatter2=m.scatter([], [], s=100, c='white', marker='o', label = 'Aurelia aurita', zorder=3, alpha=0.5, edgecolor='steelblue')
z,a = m(lonA[0:3], latA[0:3])
scatter2.set_offsets(np.c_[z,a])
plt.show()
To plot circles with a specified radius in map units (meters), firstly, I create a function (genCircle2) that accepts input parameters of a circle and returns an array of points along the perimeter of that circle. In my code below, command m(lon,lat) is used to compute (mx,my) in meters of map projection coordinates.
Command genCircle2(cx=mx, cy=my, rad=5000.) computes the points for circle plotting. Here is the working code.
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
def genCircle2(cx=0, cy=0, rad=1):
"""Generate points along perimeters of a circle"""
points = []
segs = 20
for ea in range(segs+1):
xi = cx + rad*np.cos(ea*2.*np.pi/segs)
yi = cy + rad*np.sin(ea*2.*np.pi/segs)
points.append([xi,yi])
return np.array(points)
width, height = 400000, 320000
ax = plt.figure(figsize=(12,10))
# long, lat
lonA = [2.631547, 2.861595, 2.931014]
latA = [51.120983, 51.209122, 51.238868]
# accompanying attributes, colors and ...
clrs = ['r', 'g', 'b']
m = Basemap(width=width, height=height, projection='lcc', \
resolution='i', lat_0=52.35, lon_0=4.5)
m.drawmapboundary(fill_color='turquoise')
m.fillcontinents(color='white', lake_color='aqua')
m.drawcountries(linestyle='--')
# plot circles at points defined by (lonA,latA)
for lon,lat,clr in zip(lonA, latA, clrs):
mx,my = m(lon,lat) # get map coordinates from (lon,lat)
cclpnts = genCircle2(cx=mx, cy=my, rad=5000.) # get points along circle's perimeter
m.plot(cclpnts[:,0], cclpnts[:,1], \
label='Aurelia aurita', color=clr, \
linewidth=0.75) # plot circle
plt.show()
The resulting plot:

set_clip_path on a matplotlib clabel not clipping properly

I'm making a contour plot that is clipped to a polygon path:
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import numpy as np
fig = plt.figure()
axes = plt.subplot()
x,y = np.meshgrid( np.linspace(-10,10,51), np.linspace(-10,10,51) )
z = np.sin(np.sqrt(x**2+y**2))
CS = axes.contour(x, y, z, np.linspace(-1,1,11) )
axes.set_aspect('equal')
# clip contours by polygon
radius = 8
t = np.linspace(0,2*np.pi,101)
x_bound,y_bound = radius*np.sin(t),radius*(np.cos(t)+0.1*(np.cos(7*t)))
clip_map = Polygon(list(zip(x_bound,y_bound)),fc='#EEEEEE',ec='none')
axes.add_patch(clip_map)
for collection in CS.collections:
collection.set_clip_path(clip_map)
# label the contours
CLB = axes.clabel(CS, colors='black')
for text_object in CLB:
text_object.set_clip_path(clip_map) # Doesn't do anything!
plt.show()
To my surprise, the labels aren't clipped despite the Text objects having a set_clip_path method that doesn't return an error:
How can I clip the labels outside of the gray polygon area? Do I need to resort to manually finding the X and Y positions, calculating point in polygon, and set_visible = False for each Text item? Why doesn't this code work as-is? I'm using matplotlib version 1.5.1 and python 3.5.1.
Just in case someone comes across the same issue someday, here's a solution that resorts to having to use the shapely package to test for point in polygon to set the visibility state of the Text object. It gets the job done, but it would be nice if it was possible to use set_clip_path to work directly on the Text object.
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import numpy as np
from shapely.geometry import Polygon as ShapelyPolygon
from shapely.geometry import Point as ShapelyPoint
fig = plt.figure()
axes = plt.subplot()
x,y = np.meshgrid( np.linspace(-10,10,51), np.linspace(-10,10,51) )
z = np.sin(np.sqrt(x**2+y**2))
CS = axes.contour(x, y, z, np.linspace(-1,1,11) )
axes.set_aspect('equal')
# clip contours by polygon
radius = 8
t = np.linspace(0,2*np.pi,101)
x_bound,y_bound = radius*np.sin(t),radius*(np.cos(t)+0.1*(np.cos(7*t)))
clip_map = Polygon(list(zip(x_bound,y_bound)),fc='#EEEEEE',ec='none')
axes.add_patch(clip_map)
for collection in CS.collections:
collection.set_clip_path(clip_map)
# label the contours
CLB = axes.clabel(CS, colors='black')
clip_map_shapely = ShapelyPolygon(clip_map.get_xy())
for text_object in CLB:
if not clip_map_shapely.contains(ShapelyPoint(text_object.get_position())):
text_object.set_visible(False)
plt.show()

get_path() return of a Circle from matplotlib.patches

Does anyone know what the get_path() of a Circle from matplotlib.patches returns? The get_path() of a circle is returning something different from the original circle, which can be seen from the result of the below code. As can be seen from the attached picture, the original orange circle is totally different with the blue circle from get_path() of the original circle.
import numpy as np
import matplotlib
from matplotlib.patches import Circle, Wedge, Polygon, Ellipse
from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
import matplotlib.patches as matpatches
fig, ax = plt.subplots(figsize=(8, 8))
patches = []
circle = Circle((2, 2), 2)
patches.append(circle)
print patches[0].get_path()
print patches[0].get_verts()
polygon = matpatches.PathPatch(patches[0].get_path())
patches.append(polygon)
colors = 2*np.random.rand(len(patches))
p = PatchCollection(patches, cmap=matplotlib.cm.jet, alpha=0.4)
p.set_array(np.array(colors))
ax.add_collection(p)
plt.axis([-10, 10, -10, 10])
plt.show()
fig.savefig('test.png')
contain2 = patches[0].get_path().contains_points([[0.5, 0.5], [1.0, 1.0]])
print contain2
contain3 = patches[0].contains_point([0.5, 0.5])
print contain3
contain4 = patches[0].contains_point([1.0, 1.0])
print contain4
The path of the circle is the unit circle, and the way that matplotlib displays it as a circle with the center and radius that you specify is via a 2D affine transform. If you want the transformed path, you will need to get both the path and the transform and apply the transform to the path.
# Create the initial circle
circle = Circle([2,2], 2);
# Get the path and the affine transformation
path = circle.get_path()
transform = circle.get_transform()
# Now apply the transform to the path
newpath = transform.transform_path(path)
# Now you can use this
polygon = matpatches.PathPatch(newpath)
patches.append(polygon)

Categories