草庐IT

python气象数据可视化学习笔记6——利用python地图库cnmaps绘制地图填色图并白化

qylirene 2024-02-27 原文

文章目录

1. 效果图

2. cnmaps简介及安装

2.1 写在前面

前序博文Python气象数据可视化学习笔记5——基于cartopy绘制contour并对中国地区进行白化(包含南海) 的阅读和收藏量都很高,感觉大家还是有很多地图白化裁剪的需求。但是在上述博文中,需要自己找mask文件和地图shp数据,对很多人来说也是个麻烦事,近期发现了一个在线的中国地图python扩展包,试了下,非常好用,写博客记录下学习过程,也希望对看到这篇博文的人有所帮助。

2.2 cnmaps简介和安装

官网介绍,cnmaps是一个致力于让中国地图的获取和使用更丝滑的python扩展包,主要功能包括:
(1)自带合规地图边界,数据源来自于高德等测绘机构,让你无需再额外寻找地图边界文件。
(2)支持地图边界之间的加减、交并集等常规操作,让你可以自由地组合想要的地图形状
(3)具有易于使用的地图裁剪功能,且裁剪效果好,平滑无锯齿。
(4)与cartopy集成,可以自动转换地图边界的投影
这4点简直是气象er们的福音,这样的扩展包怎么能不支持。而且cnmaps目前包括了省级到县级地图的绘制,简直太给力了。
主要参考网站
(1)cnmaps使用指南
(2)cnmaps安装
(3)cnmaps github

安装这里提示大家一定要用最新版本,并且cartopy版本要在20以上,老版本有些函数都用不了。

3. 导入库

绘制效果图主要使用到4个库:
(1) cnmaps: 主要用到get_adm_maps, draw_maps,clip_contours_by_map, draw_map 四个函数,后面会介绍
(2) cartopy: 用地图的投影转换,经纬度坐标的设置等
(3) matplotlib: 不用介绍了,cartopy都是基于matplotlib的,一定要导入的
(4) xarray: 效果图因为用到了WRF的静态地形数据geo_em_d01.nc的海拔高度做填色,所以需要使用xarray读取数据。

import xarray as xr
import numpy as np 
import matplotlib.pyplot as plt
import matplotlib as mpl
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
from cnmaps import get_adm_maps, draw_maps,clip_contours_by_map, draw_map
mpl.rcParams["font.size"] = 13

4. 定义绘图函数

利用cnmaps绘制地图和白化主要包含三个步骤:
(1)使用get_adm_maps返回地图边界。
(2)ax.contourf绘制填色图
(3)clip_contours_by_map基于填色图和地图边界进行裁剪和白化处理。
(4)draw_map 或者 draw_maps绘制地图边界
在以上四个步骤的基础上,还有一些细节处理。本文为了呈现多种地图和白化效果,定义了map_plot函数,下面介绍该函数的具体思路。

4.1 使用get_adm_maps返回地图边界

  • beijing1 = get_adm_maps(province=‘北京市’)可直接返回北京的地图边界,jingjinji1 = beijing1 + tianjin1 + hebei1 通过组合可将三个省份边界相加
  • beijing2 = get_adm_maps(province=‘北京市’, only_polygon=True, record=‘first’),jingjinji2 = beijing2 + tianjin2 + hebei2情况下则返回的是整个京津冀大区域的边界,没有包含各个省份的边界
  • 以上两点有明显区别,就是图(a)和图(c)所呈现的两种不同效果
  • get_adm_maps中可实现国家、省、市、县的边界绘制,例子这里是用了省份的数据,可以根据需求绘制对应的边界。具体使用细节可参考cnmaps使用指南
def map_plot(fig,ax,grid_lat,grid_lon,data, is_mask, is_province_boundary,title):
  #调用地图,返回多条边界
  beijing1 = get_adm_maps(province='北京市')
  tianjin1 = get_adm_maps(province='天津市')
  hebei1 = get_adm_maps(province='河北省')
  #仅返回地图边界对象(MapPolygon), 一条边界
  beijing2 = get_adm_maps(province='北京市', only_polygon=True, record='first')
  tianjin2 = get_adm_maps(province='天津市', only_polygon=True, record='first')
  hebei2 = get_adm_maps(province='河北省', only_polygon=True, record='first')

  jingjinji1 = beijing1 + tianjin1 + hebei1
  jingjinji2 = beijing2 + tianjin2 + hebei2
  
  #设置显示区域
  ax.set_extent([113,120,36.2,42.5], crs=ccrs.PlateCarree())

4.2 ax.contourf绘制填色图

这里使用了geo_em_d01.nc中HGT的数据,绘图原理不再重复,比较简单

  #绘制填色图
  cf = ax.contourf(grid_lon, grid_lat,data, cmap=plt.cm.terrain,levels=np.linspace(0, 3000, 11),transform=ccrs.PlateCarree())

4.3 clip_contours_by_map基于填色图和地图边界进行裁剪和白化处理

填色图白化使用的函数是clip_contours_by_map,输入数据为contour返回对象cf和地图返回对象jingjinji2. 注意白化时无论最后地图边界呈现如何,这里要选择仅包含一条边界的地图对象(Jingjinji2),不然会报错. 这样能够得到白化后的效果,图(b)和图(d)。

  #设置是否白化,白化必须基于一条边界(jingjinji2)
  if is_mask:
     clip_contours_by_map(cf, jingjinji2)

4.4 draw_map 或者 draw_maps绘制地图边界

根据官方文档 draw_map 和draw_maps的区别是前者是使用map_polygon地图边界对象,后者使用maps (list or GeoDataFrame) ,对应的分别是jingjinji2和jingjinji1,代表仅有一条边界还是全部省份边界,图(a)和图 ©的差别。如果只有一个省份,那用draw_map就可以了。

  #绘制地图
  if is_province_boundary:
     draw_maps(jingjinji1,linewidth=0.8, color='k')   #绘制全部省份地图
  else:
     draw_map(jingjinji2,linewidth=0.8, color='k')    #仅绘制所有省份的一条边界

  #设置标题
  ax.set_title(title)

  #添加经纬度格网 兰伯特
  gl=ax.gridlines(draw_labels=True,linestyle=":",linewidth=0.3 ,x_inline=False, y_inline=False,color='k')
  gl.top_labels=False #关闭上部经纬标签                                  
  gl.right_labels=False#关闭右边经纬标签   
  gl.rotate_labels=None#关闭兰伯特经纬标签旋转  
  gl.xformatter = LONGITUDE_FORMATTER  #使横坐标转化为经纬度格式            
  gl.yformatter = LATITUDE_FORMATTER   
  
  #添加coloarbar
  fig.colorbar(cf,ax=ax, shrink=0.9,  extendfrac='auto',extendrect=True,location='right',fraction=0.05, pad=0.05) 
  
  • 以上代码还包含了设置经纬度和colorbar,也是在之前博文中提到过的方法。
  • 这样绘图函数map_plot就定义好了,总结下来就是只要传入fig, ax, lon, lat, data(二维数据),是否白化(is_mask),是否包含省界(is_province_boundary)等信息就能够绘图了

5. 导入数据并绘图

使用了经度grid_lon, 纬度grid_lat和data(grid_lat, grid_lon)二维数据,并使用了lambert投影(中心经纬度37N,105E,切割纬度纬30N和60N),这里可根据自己的数据选择不同的投影。
例子里通过前面定义好的map_plot函数绘制了四幅图,具体见效果图。

# read data
grid_path = "../air_quality/geo_em.d01.nc"
grid_data = xr.open_dataset(grid_path)
grid_lat = grid_data["XLAT_M"].values[0]
grid_lon = grid_data["XLONG_M"].values[0]
data = grid_data['HGT_M'].values[0]
# %%
# make plot
proj_lambert = ccrs.LambertConformal(central_latitude=37, central_longitude=105,standard_parallels=(30,60))
#(a) 仅包含一条边界
fig1, ax1 = plt.subplots(1,1,figsize=(6,6), subplot_kw=dict(projection=proj_lambert))
map_plot(fig1, ax1, grid_lat,grid_lon,data,False,False,'(a) Without province boundary')
#(b)京津冀白化
fig2, ax2 = plt.subplots(1,1,figsize=(6,6), subplot_kw=dict(projection=proj_lambert))
map_plot(fig2, ax2, grid_lat,grid_lon,data,True,False,'(b) Clip contours')
#(c)包含全部省份边界
fig3, ax3 = plt.subplots(1,1,figsize=(6,6), subplot_kw=dict(projection=proj_lambert))
map_plot(fig3, ax3, grid_lat,grid_lon,data,False,True,'(c) With province boundary')
#(d)包含全部省份边界
fig4, ax4 = plt.subplots(1,1,figsize=(6,6), subplot_kw=dict(projection=proj_lambert))
map_plot(fig4, ax4, grid_lat,grid_lon,data,True,True,'(d) Clip contours with province boundary')

plt.show()

大功告成!

6. 代码完整版

import xarray as xr
import numpy as np 
import matplotlib.pyplot as plt
import matplotlib as mpl
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
from cnmaps import get_adm_maps, draw_maps,clip_contours_by_map, draw_map
mpl.rcParams["font.size"] = 13
# %%
def map_plot(fig,ax,grid_lat,grid_lon,data, is_mask, is_province_boundary,title):
  #调用地图,返回多条边界
  beijing1 = get_adm_maps(province='北京市')
  tianjin1 = get_adm_maps(province='天津市')
  hebei1 = get_adm_maps(province='河北省')
  #仅返回地图边界对象(MapPolygon), 一条边界
  beijing2 = get_adm_maps(province='北京市', only_polygon=True, record='first')
  tianjin2 = get_adm_maps(province='天津市', only_polygon=True, record='first')
  hebei2 = get_adm_maps(province='河北省', only_polygon=True, record='first')

  jingjinji1 = beijing1 + tianjin1 + hebei1
  jingjinji2 = beijing2 + tianjin2 + hebei2
  
  #设置显示区域
  ax.set_extent([113,120,36.2,42.5], crs=ccrs.PlateCarree())
  
  #绘制填色图
  cf = ax.contourf(grid_lon, grid_lat,data, cmap=plt.cm.terrain,levels=np.linspace(0, 3000, 11),transform=ccrs.PlateCarree())
  
  #设置是否白化,白化必须基于一条边界(jingjinji2)
  if is_mask:
     clip_contours_by_map(cf, jingjinji2)

  #绘制地图
  if is_province_boundary:
     draw_maps(jingjinji1,linewidth=0.8, color='k')   #绘制全部省份地图
  else:
     draw_map(jingjinji2,linewidth=0.8, color='k')    #仅绘制所有省份的一条边界

  #设置标题
  ax.set_title(title)

  #添加经纬度格网 兰伯特
  gl=ax.gridlines(draw_labels=True,linestyle=":",linewidth=0.3 ,x_inline=False, y_inline=False,color='k')
  gl.top_labels=False #关闭上部经纬标签                                  
  gl.right_labels=False#关闭右边经纬标签   
  gl.rotate_labels=None#关闭兰伯特经纬标签旋转  
  gl.xformatter = LONGITUDE_FORMATTER  #使横坐标转化为经纬度格式            
  gl.yformatter = LATITUDE_FORMATTER   
  
  #添加coloarbar
  fig.colorbar(cf,ax=ax, shrink=0.9,  extendfrac='auto',extendrect=True,location='right',fraction=0.05, pad=0.05) 
  
# %%
# read data
grid_path = "../air_quality/geo_em.d01.nc"
grid_data = xr.open_dataset(grid_path)
grid_lat = grid_data["XLAT_M"].values[0]
grid_lon = grid_data["XLONG_M"].values[0]
data = grid_data['HGT_M'].values[0]
# %%
# make plot
proj_lambert = ccrs.LambertConformal(central_latitude=37, central_longitude=105,standard_parallels=(30,60))
#(a) 仅包含一条边界
fig1, ax1 = plt.subplots(1,1,figsize=(6,6), subplot_kw=dict(projection=proj_lambert))
map_plot(fig1, ax1, grid_lat,grid_lon,data,False,False,'(a) Without province boundary')
#(b)京津冀白化
fig2, ax2 = plt.subplots(1,1,figsize=(6,6), subplot_kw=dict(projection=proj_lambert))
map_plot(fig2, ax2, grid_lat,grid_lon,data,True,False,'(b) Clip contours')
#(c)包含全部省份边界
fig3, ax3 = plt.subplots(1,1,figsize=(6,6), subplot_kw=dict(projection=proj_lambert))
map_plot(fig3, ax3, grid_lat,grid_lon,data,False,True,'(c) With province boundary')
#(d)包含全部省份边界
fig4, ax4 = plt.subplots(1,1,figsize=(6,6), subplot_kw=dict(projection=proj_lambert))
map_plot(fig4, ax4, grid_lat,grid_lon,data,True,True,'(d) Clip contours with province boundary')

plt.show()
# %%

有关python气象数据可视化学习笔记6——利用python地图库cnmaps绘制地图填色图并白化的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  4. ruby - Ruby 中的波形可视化 - 2

    我即将开始一个将录制和编辑音频文件的项目,我正在寻找一个好的库(最好是Ruby,但会考虑Java或.NET以外的任何库)以进行实时可视化波形。有人知道我应该从哪里开始搜索吗? 最佳答案 要流入浏览器的数据量很大。Flash或Flex图表可能是唯一能提高内存效率的解决方案。Javascript图表往往会因大型数据集而崩溃。 关于ruby-Ruby中的波形可视化,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c

  5. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  6. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  7. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

随机推荐