set_clip_path on a matplotlib clabel not clipping properly - python

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()

Related

Color Overlapping Polygons in Shapely 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()

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 :)

Strange behavior with contours in Cartopy polar stereographic plot

I'm trying to create a contour plot on a North Polar Stereographic map projection using Cartopy. I used add_cyclic_point() to try and get around the problem of having a gap between longitude 0 and longitude 35X and followed an example from the documentation (always_circular_stereographic) to set up the map axes.
When I call plt.contour, I get the following plot. It looks like the contour plotter is getting confused at the transition from 355 to 0 longitude, and sends contour lines around the globe.
Here is my code:
import numpy as np
import cartopy.crs as ccrs
from cartopy.util import add_cyclic_point
import matplotlib.pyplot as plt
def define_map():
from matplotlib.path import Path
fig = plt.figure(figsize=(10,10))
ax = plt.axes(projection=ccrs.NorthPolarStereo())
ax.coastlines()
# From example: http://scitools.org.uk/cartopy/docs/latest/examples/always_circular_stereo.html
theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = Path(verts * radius + center)
ax.set_boundary(circle, transform=ax.transAxes)
return(fig, ax)
lats = np.arange(65,91,5)
lons = add_cyclic_point(np.arange(0,359,5))
data = add_cyclic_point(np.random.random((len(lats),len(lons)-1)))
fig, ax = define_map()
plt.contour(lons,lats,data,5,transform=ccrs.PlateCarree(), cmap=plt.cm.Blues)
plt.colorbar(fraction=0.05, shrink=0.9)
plt.show()
How do I do a Cartopy contour plot properly?
Also, why do the contours only show up with transform=ccrs.PlateCarree() and not with transform=ccrs.NorthPolarStereo()?
Apparently the add_cyclic_point function is just for the data; the contour routine treats 0 different than 360. So the simple fix is to set
lons = np.arange(0,360,5)

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)

setting color range in matplotlib patchcollection

I am plotting a PatchCollection in matplotlib with coords and patch color values read in from a file.
The problem is that matplotlib seems to automatically scale the color range to the min/max of the data values. How can I manually set the color range? E.g. if my data range is 10-30, but I want to scale this to a color range of 5-50 (e.g. to compare to another plot), how can I do this?
My plotting commands look much the same as in the api example code: patch_collection.py
colors = 100 * pylab.rand(len(patches))
p = PatchCollection(patches, cmap=matplotlib.cm.jet, alpha=0.4)
p.set_array(pylab.array(colors))
ax.add_collection(p)
pylab.colorbar(p)
pylab.show()
Use p.set_clim([5, 50]) to set the color scaling minimums and maximums in the case of your example. Anything in matplotlib that has a colormap has the get_clim and set_clim methods.
As a full example:
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Circle
import numpy as np
# (modified from one of the matplotlib gallery examples)
resolution = 50 # the number of vertices
N = 100
x = np.random.random(N)
y = np.random.random(N)
radii = 0.1*np.random.random(N)
patches = []
for x1, y1, r in zip(x, y, radii):
circle = Circle((x1, y1), r)
patches.append(circle)
fig = plt.figure()
ax = fig.add_subplot(111)
colors = 100*np.random.random(N)
p = PatchCollection(patches, cmap=matplotlib.cm.jet, alpha=0.4)
p.set_array(colors)
ax.add_collection(p)
fig.colorbar(p)
fig.show()
Now, if we just add p.set_clim([5, 50]) (where p is the patch collection) somewhere before we call fig.show(...), we get this:

Categories