专栏名称: 气象学家
【气象学家】公众号平台为您解读最新气象科研进展、分享气象实用编程技巧、追踪气象即时资讯。欢迎加入气象AI和Python交流群以及气象博士群!与5W+的专业人士一起交流互动!
目录
相关文章推荐
新华社  ·  元宵节快乐! ·  昨天  
新华社  ·  以为是糖,结果放嘴里炸了! ·  2 天前  
人民日报  ·  “申公豹代购药单”,有……有点东西! ·  2 天前  
51好读  ›  专栏  ›  气象学家

气象编程 | 图像裁剪白化终章

气象学家  · 公众号  ·  · 2024-07-08 15:00

正文

图形裁剪白化

图形裁剪白化也是一个经常留言的东西,这里就将常见的问题铺开讲解。

图形裁剪白化如何实现

在matplotlib中,裁剪白化的实现,依靠的是artist的set_clip_path方法。几乎所有的绘图命令的返回值都是artist,所以使用set_clip_path方法才是可行的。如果一个东西没有set_clip_path方法,那么就不能进行白化操作。

set_clip_path如何使用

我们在之前的推文中已经反复强调,函数或者方法要求什么,我们就提供什么。那么set_clip_path参数要求什么呢,首先就是用来裁剪的依据。在气象上,绝大部分裁剪依据就是地图的行政区划,所以问题转化为如何提取行政区划,提取行政区划可以使用geopandas和cartopy。set_clip_path参数要求的第二个就是transform,坐标变换,这个在气象上,绝大多数时候都是ax.transData。所以关键问题进入到path的获取。

path如何获取

对于气象方向来说,path就是行政区划的边界线。实际上大家使用的多是shp文件,而shp文件可以通过geopandas或者cartopy来读取。但是具体到操作上,推荐geopandas,因为他可以生成表格透视表。
geopandas读取shp文件的接口是read_file

import geopandas as gpd
gdf=gpd.read_file(shp_path)

这里有乱码,修改encoding参数来使他显示为中文。encoding常用参数有utf-8、gbk、gb2312、gb18030、ascii等,如果全都不行,那就是文件制作时读取与写入方式有问题。

gdf=gpd.read_file(shp_path,encoding='utf-8')

我们这里想要黑龙江省与辽宁省的行政区划,所以提取这两个省数据:

two_provinces=gdf[gdf['NAME'].isin(['黑龙江省','辽宁省'])]

我们得到想要的path了吗?并没有,我们可以发现得到的只是包含两个省数据的GeoDataFrame而已。

type(two_provinces)

geopandas.geodataframe.GeoDataFrame
我们发现two_provinces的最后一列就是行政区划的几何图像,所以来提取最后一列的数据。

two_provinces['geometry']
type(two_provinces['geometry'])

geopandas.geoseries.GeoSeries
我们提取几何图形,发现仍然不是path,不过我们知道cartopy有个方法可以将geometry变为path,因为方法要求参数为列表、元组或者单个的几何图形,所以先列表化再参与转化:

import cartopy.mpl.patch as cmp
paths=cmp.geos_to_path(list(two_provinces['geometry']))

但是我们还没有达到目的,paths其实是一个存储许多path的列表,set_clip_path要求的是path:

type(paths)

list 不过matplotlib.path.Path提供了一个函数将多个path合成为一个path:

clip_path=mpath.Path.make_compound_path(*paths)
type(clip_path)

matplotlib.path.Path 终于将shp文件的geometry转化为Path。

contourf图如何白化

参考前面已发布的推文。

scatter如何白化

查阅matplotlib文档,可知scatter的返回值为Pathcollection,这是个artist,可以执行set_clip_path方法。

lon,lat=np.meshgrid(ds.r.longitude.values,ds.r.latitude.values)
ax1.scatter(lon[::2,::2],lat[::2,::2],transform=ccrs.PlateCarree(),marker='s',s=2,c=ds.r[0,5].values[::2,::2])
ac=ax2.scatter(lon[::2,::2],lat[::2,::2],transform=ccrs.PlateCarree(),marker='s',s=2,c=ds.r[0,5].values[::2,::2])
ac.set_clip_path(clip_path,transform=ax2.transData)

pcolormesh如何白化

查阅matplotlib文档,可知pcolormesh的返回值为matplotlib.collections.QuadMesh,这是个artist,可以执行set_clip_path方法。

ax1.pcolormesh(ds.r.longitude.values,ds.r.latitude.values,ds.r[0,5].values,transform=ccrs.PlateCarree())
ap=ax2.pcolormesh(ds.r.longitude.values,ds.r.latitude.values,ds.r[0,5].values,transform=ccrs.PlateCarree())
ap.set_clip_path(clip_path,transform=ax2.transData)

barbs如何白化

查阅matplotlib文档,可知barbs的返回值为PolyCollection,这是个artist,可以执行set_clip_path方法。

ax1.barbs(ds.longitude.values[::4],
          ds.latitude.values[::4],
          ds.u[0,5].values[::4,::4],
          ds.v[0,5].values[::4,::4],
          barb_increments={'half':2,'full':4,'flag':20},
          length=4,linewidth=0.5,sizes={'emptybarb':0},
          transform=ccrs.PlateCarree())
ab=ax2.barbs(ds.longitude.values[::4],
          ds.latitude.values[::4],
          ds.u[0,5].values[::4,::4],
          ds.v[0,5].values[::4,::4],
          barb_increments={'half':2,'full':4,'flag':20},
          length=4,linewidth=0.5,sizes={'emptybarb':0},
          transform=ccrs.PlateCarree())
ab.set_clip_path(clip_path,transform=ax2.transData)

quiver如何白化

查阅matplotlib文档,可知quiver的返回值为PolyCollection,这是个artist,可以执行set_clip_path方法。

ax1.quiver(ds.longitude.values[::4],
          ds.latitude.values[::4],
          ds.u[0,5].values[::4,::4],
          ds.v[0,5].values[::4,::4],scale=150,width=0.004,
          transform=ccrs.PlateCarree())
aq=ax2.quiver(ds.longitude.values[::4],
          ds.latitude.values[::4],
          ds.u[0,5].values[::4,::4],
          ds.v[0,5].values[::4,::4],scale=150,width=0.004,
          transform=ccrs.PlateCarree())
aq.set_clip_path(clip_path,transform=ax2.transData)

imshow如何白化

查阅matplotlib文档,可知imshow的返回值为image,这是个artist,可以执行set_clip_path方法。

imgarray=plt.imread(r"C:\Users\宫阙.jpg")
ax1.imshow(imgarray,
           extent=ax1.get_extent(),
           transform=ccrs.PlateCarree())
ai=ax2.imshow(imgarray,
              extent=ax2.get_extent(),
              transform=ccrs.PlateCarree())
ai.set_clip_path(clip_path,transform=ax2.transData)

stremplot如何白化

stremplot的白化较为费劲,查阅文档可知streamplot返回两个返回值,首先对线条进行白化。

ax1.streamplot(ds.longitude.values[::4],
          ds.latitude.values[::4],
          ds.u[0,5].values[::4,::4],
          ds.v[0,5].values[::4,::4],linewidth=0.5,color='k',arrowsize=0.5,
           transform=ccrs.PlateCarree())
ap=ax2.streamplot(ds.longitude.values[::4],
          ds.latitude.values[::4],
          ds.u[0,5].values[::4,::4],
          ds.v[0,5].values[::4,::4],linewidth=0.5,color='k',arrowsize=0.5,
              transform=ccrs.PlateCarree())
ap.lines.set_clip_path(clip_path,transform=ax2.transData)

然后再对残留的小箭头白化:

for art in ax2.get_children():
    if not isinstance(art, matplotlib.patches.FancyArrowPatch):
        continue
    else:
        art.set_clip_path(clip_path,transform=ax2.transData)

contour的文字如何白化

contour的标签命令为clabel,会返回任意多个Text,但是matplotlib直到今天也没有修复这个文本不能被clip的bug,所以我们只能用别的办法实现,通过判断文本的落区是否在地图内部来控制显隐。

a1=ax1.contour(ds.longitude.values,
            ds.latitude.values,
            ds.r[0,5].values,linewidths=0.5,
           transform=ccrs.PlateCarree())
a1t=ax1.clabel(a1,fontsize=4,colors='k')
for t in a1t:
    t.set_bbox({'fc''w','ec':'k','pad':1.25,'linewidth':0.35})
ac=ax2.contour(ds.longitude.values,
               ds.latitude.values,
               ds.r[0,5].values,linewidths=0.5,
              transform=ccrs.PlateCarree())
ctext=ax2.clabel(ac,fontsize=4,colors='k')
for t in ctext:
    t.set_bbox({'fc''w','ec':'k','pad':1.25,'linewidth':0.35})
for col in ac.collections:
    col.set_clip_path(clip_path,transform=ax2.transData)
import shapely
for t in ctext:
    if not shapely.geometry.Polygon(clip_path.vertices).contains(shapely.geometry.Point(t.get_position())):
        t.set_visible(False

总结

对于matplotlib的绝大多数绘图命令,都是可以通过set_clip_path来控制其裁剪功能,从上面这些例子中可发现其运行之规律,掌握规律与方法,就可在各种图像裁剪中游刃有余。









请到「今天看啥」查看全文