如何为Basemap多边形设置裁剪路径
我想用imshow这个函数来显示一些数据,范围是在一个国家的边界内(为了举例,我选择了美国)。下面这个简单的例子展示了我想要的效果:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
data = np.arange(100).reshape(10, 10)
fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(data)
poly = RegularPolygon([ 0.5, 0.5], 6, 0.4, fc='none',
ec='k', transform=ax.transAxes)
im.set_clip_path(poly)
ax.add_patch(poly)
ax.axis('off')
plt.show()
结果是:
现在我想做的事情是,除了简单的多边形,我还想用美国的复杂形状来展示。我已经创建了一些示例数据,这些数据存储在名为“Z”的数组中,下面的代码可以看到这些数据。我想用颜色图来显示这些数据,但只在美国本土的边界内。
到目前为止,我尝试了以下方法。我从这里获取了一个形状文件,文件名是“nationp010g.shp.tar.gz”,然后我在Python中使用Basemap模块来绘制美国。请注意,这是我找到的唯一一种可以获取我需要的区域多边形的方法。如果有其他方法,我也很感兴趣。接着,我创建了一个名为“mainpoly”的多边形,这个多边形几乎是我想要的,颜色是蓝色:
注意,只有一个区域被涂成了颜色,其他不相连的多边形仍然是白色的:
所以,涂成蓝色的区域几乎是我想要的,注意到在靠近加拿大的地方有一些不想要的边界线,因为边界实际上穿过了一些湖泊,但这只是个小问题。真正的问题是,为什么我的imshow数据没有在美国的范围内显示?对比我第一个和第二个例子的代码,我看不出为什么在第二个例子中没有像第一个那样显示被裁剪的imshow。任何帮助我理解我遗漏了什么的建议都非常感谢。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap as Basemap
from matplotlib.patches import Polygon
# Lambert Conformal map of lower 48 states.
m = Basemap(llcrnrlon=-119,llcrnrlat=22,urcrnrlon=-64,urcrnrlat=49,
projection='lcc',lat_1=33,lat_2=45,lon_0=-95)
shp_info = m.readshapefile('nationp010g/nationp010g', 'borders', drawbounds=True) # draw country boundaries.
for nshape,seg in enumerate(m.borders):
if nshape == 1873: #This nshape denotes the large continental body of the USA, which we want
mainseg = seg
mainpoly = Polygon(mainseg,facecolor='blue',edgecolor='k')
nx, ny = 10, 10
lons, lats = m.makegrid(nx, ny) # get lat/lons of ny by nx evenly space grid.
x, y = m(lons, lats) # compute map proj coordinates.
Z = np.zeros((nx,ny))
Z[:] = np.NAN
for i in np.arange(len(x)):
for j in np.arange(len(y)):
Z[i,j] = x[0,i]
ax = plt.gca()
im = ax.imshow(Z, cmap = plt.get_cmap('coolwarm') )
im.set_clip_path(mainpoly)
ax.add_patch(mainpoly)
plt.show()
更新
我意识到这行代码
ax.add_patch(mainpoly)
甚至没有把多边形形状添加到图中。我是不是没有正确使用它?据我所知,mainpoly是通过Polygon()方法正确计算出来的。我检查了坐标输入,发现是合理的:
plt.plot(mainseg[:,0], mainseg[:,1] ,'.')
这给出的结果是
1 个回答
我也考虑了这个问题很久。
我发现NCL语言有一个功能,可以把某些边界外的数据隐藏起来。
这里有个例子:
http://i5.tietuku.com/bdb1a6c007b82645.png
这个等高线图只显示在中国的边界内。点击这里可以查看代码。
我知道Python有一个叫PyNCL的包,可以在Python框架中支持所有NCL代码。
但我真的想用basemap来绘制这种图。如果你已经搞定了,请在网上分享一下。我会第一时间学习的。
谢谢!
补充 2016-01-16
在某种程度上,我已经找到了方法。
这是我的想法和代码,灵感来自我今天问的这个问题。
我的方法:
1. 把感兴趣的区域(比如美国)的形状文件转成shapely.polygon。
2. 测试每个值点是在多边形内还是外。
3. 如果值点在研究区域外,就把它标记为np.nan。
介绍:
* 这个多边形xxx是中国的一个城市,格式是ESRI形状文件。
* 这里使用了fiona和shapely包。
# generate the shapely.polygon
shape = fiona.open("xxx.shp")
pol = shape.next()
geom = shape(pol['geometry'])
poly_data = pol["geometry"]["coordinates"][0]
poly = Polygon(poly_data)
结果看起来是这样的:
http://i4.tietuku.com/2012307faec02634.png
### test the value point
### generate the grid network which represented by the grid midpoints.
lon_med = np.linspace((xi[0:2].mean()),(xi[-2:].mean()),len(x_grid))
lat_med = np.linspace((yi[0:2].mean()),(yi[-2:].mean()),len(y_grid))
value_test_mean = dsu.mean(axis = 0)
value_mask = np.zeros(len(lon_med)*len(lat_med)).reshape(len(lat_med),len(lon_med))
for i in range(0,len(lat_med),1):
for j in range(0,len(lon_med),1):
points = np.array([lon_med[j],lat_med[i]])
mask = np.array([poly.contains(Point(points[0], points[1]))])
if mask == False:
value_mask[i,j] = np.nan
if mask == True:
value_mask[i,j] = value_test_mean[i,j]
# Mask the np.nan value
Z_mask = np.ma.masked_where(np.isnan(so2_mask),so2_mask)
# plot!
fig=plt.figure(figsize=(6,4))
ax=plt.subplot()
map = Basemap(llcrnrlon=x_map1,llcrnrlat=y_map1,urcrnrlon=x_map2,urcrnrlat=y_map2)
map.drawparallels(np.arange(y_map1+0.1035,y_map2,0.2),labels= [1,0,0,1],size=14,linewidth=0,color= '#FFFFFF')
lon_grid = np.linspace(x_map1,x_map2,len(x_grid))
lat_grid = np.linspace(y_map1,y_map2,len(y_grid))
xx,yy = np.meshgrid(lon_grid,lat_grid)
pcol =plt.pcolor(xx,yy,Z_mask,cmap = plt.cm.Spectral_r ,alpha =0.75,zorder =2)
结果
http://i4.tietuku.com/c6620c5b6730a5f0.png
http://i4.tietuku.com/a22ad484fee627b9.png