爱气象,爱气象家园! 

气象家园

 找回密码
 立即注册

QQ登录

只需一步,快速开始

新浪微博登陆

只需一步, 快速开始

搜索
查看: 37125|回复: 10

[经验总结] Python3.WRF的投影转换

[复制链接]

新浪微博达人勋

发表于 2020-11-23 14:50:16 | 显示全部楼层 |阅读模式

登录后查看更多精彩内容~

您需要 登录 才可以下载或查看,没有帐号?立即注册 新浪微博登陆

x
本帖最后由 墨家大宝 于 2020-11-23 16:43 编辑

有一种WRF输出的数据采用兰伯特双标准纬线投影,那么除非刚好需要同样的投影,想对这种数据进行处理的话往往要进行投影转换,WRF应该是有一套工具可以进行相关的处理,比如wrf-python,但是作为并不熟悉wrf、仅仅是使用WRF输出数据的小白,去使用WRF系工具的话学习成本就比较高了,如何用熟悉、更通用的工具实现这一投影转换呢?
难道不是设置几个投影参数,用常见的投影相关的包就可以实现了吗?

对,问题在于这个参数怎么设置?这个坑还是很坑的,好在最终找到一篇2018年的英文博客https://fabienmaussion.info/2018/01/06/wrf-projection/,加上我自己的尝试和理解,梳理出本文
采用WRF输出的数据格式为GrADS二进制码

关键
从我的角度来看,WRF的兰伯特双标准纬线投影有两个坑:
  • 椭球体参数
  • 投影中心参数

完全不了解WRF输出的情况下
“啪”的一下,很快啊,观察到WRF输出的GrADS二进制数据对应的ctl文件中pdef如下所示
  1. pdef  288 288 lcc  32.318  117.203  144.500  144.500  60.00000  30.00000  117.30000   3000.000   3000.000
复制代码

然后查到pdef的lcc语法如下:
pdef.png
那么熟悉GDAL的小伙伴应该就会很开心地想:哟,这不就是SetLCC里需要的参数吗?咱这么写(默认WGS84地理坐标系):
  1. from osgeo import osr

  2. (isize, jsize, latref, lonref, iref, jref, Struelat, Ntruelat, slon, dx, dy) = \
  3.     (288, 288, 32.318, 117.203, 144.5, 144.5, 60, 30, 117.3, 3000, 3000)

  4. lcc = osr.SpatialReference()
  5. lcc.SetLCC(Struelat, Ntruelat, latref, lonref, iref * dx, jref * dy)
  6. lcc.SetLinearUnitsAndUpdateParameters('kilometre', 3000)
  7. geo = lcc.CloneGeogCS()
  8. geo2lcc = osr.CoordinateTransformation(geo, lcc)
  9. lcc2geo = osr.CoordinateTransformation(lcc, geo)
复制代码

接下来通过geo2lcc和lcc2geo就可以愉快的在投影坐标和地理坐标之间反复横跳了不是?
当然我们需要验证。WRF输出数据中有XLAT和 XLONG两个要素,描述每个格点对应的经纬度(即地理坐标),加上很自然认为投影坐标是均匀地从西南角(0, 0)到东北角(isize-1, jsize-1),如果能够用我们的lcc2geo计算出的经纬度矩阵与XLAT和 XLONG一致那就说明可以放心地左右横跳
  1. x = np.arange(isize)
  2. y = np.arange(jsize)
  3. x_mesh, y_mesh = np.meshgrid(x, y)
  4. latlon = np.array(lcc2geo.TransformPoints(np.vstack([x_mesh.ravel(), y_mesh.ravel()]).T))
  5. lat = latlon[:, 0].reshape(shape)
  6. lon = latlon[:, 1].reshape(shape)
复制代码

然而当我们计算出咱们的经纬度坐标lat和lon后,与XLAT和 XLONG作差(欧氏距离)快速画个图一看:
  1. d = ((lat - lat_in_wrf)**2 + (lon - lon_in_wrf)**2)**0.5

  2. import matplotlib.pyplot as plt

  3. fig = plt.figure()
  4. ax = fig.add_subplot(1, 1, 1)
  5. im = ax.imshow(d, cmap='Reds')
  6. cb = fig.colorbar(im, ax=ax)
  7. fig.set_tight_layout(True)
复制代码
bad0.png
这时候就有弹幕说:“我不满意!”其实我也不满意,误差有点大,但是也没大到离谱,说明应该是投影参数有一点点问题。
修正椭球体参数
于是查资料发现,WRF的椭球体不是椭球体,是【芬芳的语气词】一个半径为6370000米的正球体!那么咱这么干:
  1. lcc = osr.SpatialReference()
  2. lcc.ImportFromProj4('+proj=longlat +a=6370000 +b=6370000 +no_defs')
  3. lcc.SetLCC(Struelat, Ntruelat, latref, lonref, iref * dx, jref * dy)
  4. lcc.SetLinearUnitsAndUpdateParameters('kilometre', 3000)
  5. geo = lcc.CloneGeogCS()
  6. geo2lcc = osr.CoordinateTransformation(geo, lcc)
  7. lcc2geo = osr.CoordinateTransformation(lcc, geo)
复制代码

再画个图瞧瞧
bad1.png
【芬芳的语气词】!但是可以发现最大误差变小了,说明有所改进,最小误差变大了,说明剩下的误差应该是水平偏移的成分多一些了。
继续查资料
修正投影中心地理坐标参数并计算投影坐标
终于,找到开头提到的博客。我虽然不会WRF,但是根据我的理解:WRF大概有两个区域,大区域里嵌套小区域,投影参数是按大区域的来,最终产出的数据是小区域的。这意味着,pdef中的latref和lonref并不是投影中心地理坐标,只是一个reference(洋屁,“参考”的意思),是用来计算真正的投影坐标用的,而真正的投影中心地理坐标,是MOAD_CEN_LAT和STAND_LON,在ctl文件中最下面像注释一样的东西里
  1. @ global String comment MOAD_CEN_LAT =        32.40
  2. @ global String comment STAND_LON =       117.30
复制代码

那么咱这么干:
  1. MOAD_CEN_LAT = 32.40
  2. STAND_LON = 117.30

  3. lcc = osr.SpatialReference()
  4. lcc.ImportFromProj4('+proj=longlat +a=6370000 +b=6370000 +no_defs')
  5. lcc.SetLCC(Struelat, Ntruelat, MOAD_CEN_LAT, STAND_LON, 0, 0)
  6. lcc.SetLinearUnitsAndUpdateParameters('kilometre', 3000)
  7. geo = lcc.CloneGeogCS()
  8. geo2lcc = osr.CoordinateTransformation(geo, lcc)
  9. lcc2geo = osr.CoordinateTransformation(lcc, geo)

  10. e, n, _ = geo2lcc.TransformPoint(lonref, latref)
  11. false_west = -(iref-1) + e
  12. false_south = -(jref-1) + n
  13. x, y = np.arange(isize) + false_west, np.arange(jsize) + false_south
  14. x_mesh, y_mesh = np.meshgrid(x, y)

  15. lonlat = np.array(lcc2geo.TransformPoints(np.vstack([x_mesh.ravel(), y_mesh.ravel()]).T))
  16. lon = lonlat[:, 0].reshape(shape)
  17. lat = lonlat[:, 1].reshape(shape)
复制代码

再画个图瞧瞧
good.png
误差降了两个数量级啦,我已经比较满意了


总结一下
  • WRF投影中心参数为MOAD_CEN_LAT和STAND_LON,采用的椭球体为半径为6370000米的正球体
  • pdef中的latref和lonref表示输出数据第jref行第iref列的地理坐标(经纬度),不代表投影中心(jref和iref是从1开始计数的)

讨论一下
  • 我觉得投影坐标的绝对数值由于直接受单位和偏移的影响可以任意变化,真正反映出空间信息的是投影坐标之间的差值(相对数值),所以我在最后的SetLCC投影参数中没有设置false_east和false_north偏移(最后两个参数)。虽然可以求出符合WRF输出数据的false_east和false_north,再调用一次lcc.SetLCC获得“完美投影关系”,但是似乎会让人感到更复杂。(其实作为非地理专业的我,这段话就已经有点绕了,权当作讨论再绕一点:最后一段代码中lcc.SetLinearUnits也是没有必要的,之后所有距离按米计算就行)
  • 原文采用pyproj进行投影,由于我更熟悉GDAL所以换成了GDAL实现。但是吐槽一点,TransformPoint这个方法在公开地理坐标系下如WGS84传参或返回的地理坐标都是先纬度再经度(如本文第一次调用),在自定义地理坐标系下传参或返回的地理坐标却是先经度再维度(本文最后两次调用),这也有可能是我用得不好,有了解的读者麻烦指教一下
  • 原文的WRF输出是nc,我用的WRF输出是GrADS二进制码,不影响这个投影的逻辑
  • 原文误差在10^-5,而本文误差在10^-4,我觉得应该是nc文件提供的各参数小数位数比GrADS的ctl文件多
  • 查资料时发现WRF也有基于WGS84的数据,不在本文讨论范围内



                               
登录/注册后可看大图



密码修改失败请联系微信:mofangbao

新浪微博达人勋

发表于 2020-11-23 15:17:22 | 显示全部楼层
啪的一下就是一个回复,很快啊
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-11-24 09:10:39 | 显示全部楼层
年轻人,不讲武德!!!
我反手就是一个赞!
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-11-24 10:18:36 | 显示全部楼层
就喜欢楼主这样较真的人
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-11-28 15:27:01 | 显示全部楼层
楼主,你的研究和发现非常有意义。
有点小问题请教一下:
1. 最终是采用ctl文件中的latref和standar lon这两个来作为lcc投影的中心码?
2. 使用cartopy的投影体系该如何设置这个WRF的lcc投影呢?

祝好
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

 楼主| 发表于 2020-11-29 09:06:15 | 显示全部楼层
lovechang1314 发表于 2020-11-28 15:27
楼主,你的研究和发现非常有意义。
有点小问题请教一下:
1. 最终是采用ctl文件中的latref和standar lon ...

1.不是,见总结部分
2.见https://scitools.org.uk/cartopy/ ... rs.LambertConformal
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-12-4 14:53:34 | 显示全部楼层
墨家大宝 发表于 2020-11-29 09:06
1.不是,见总结部分
2.见https://scitools.org.uk/cartopy/docs/latest/crs/projections.html#cartopy.c ...

我看了wrf-python的投影文档和源码,发现可能直接由wrfout转换得到的grads文件的ctl里投影信息压根就不全。
是这样吗?
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

 楼主| 发表于 2020-12-4 21:13:50 | 显示全部楼层
lovechang1314 发表于 2020-12-4 14:53
我看了wrf-python的投影文档和源码,发现可能直接由wrfout转换得到的grads文件的ctl里投影信息压根就不全 ...

我没用过wrf-python啊。目前接触到的wrf02d的数据ctl文件信息挺全的
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-12-16 00:09:13 | 显示全部楼层
lovechang1314 发表于 2020-12-4 14:53
我看了wrf-python的投影文档和源码,发现可能直接由wrfout转换得到的grads文件的ctl里投影信息压根就不全 ...

如果使用wrfPython怎么样转换的呢?
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2021-1-22 15:02:53 | 显示全部楼层
本帖最后由 muggle 于 2021-1-22 15:13 编辑

文中缺少shape定义,应该为[288,288]
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册 新浪微博登陆

本版积分规则

Copyright ©2011-2014 bbs.06climate.com All Rights Reserved.  Powered by Discuz! (京ICP-10201084)

本站信息均由会员发表,不代表气象家园立场,禁止在本站发表与国家法律相抵触言论

快速回复 返回顶部 返回列表