爱气象,爱气象家园! 

气象家园

 找回密码
 立即注册

QQ登录

只需一步,快速开始

新浪微博登陆

只需一步, 快速开始

搜索
查看: 53934|回复: 41

[经验总结] 用pyhdf读取CloudSat R05的hdf文件

[复制链接]

新浪微博达人勋

发表于 2020-10-19 15:10:09 | 显示全部楼层 |阅读模式

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

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

x
本帖最后由 灭火器 于 2020-10-20 08:04 编辑

HDF EOS 的官网上有用 Python 读取 CloudsSat 2B-GEOPROF 数据的实例脚本(https://hdfeos.org/zoo/index_openCDPC_Examples.php#CloudSAT)用该网站上提供的实例数据进行测试,能够得到正常的结果。图片如下
2010128055614_21420_CS_2B-GEOPROF_GRANULE_P_R04_E03.hdf.py.png

但是当我用自己下载的数据进行测试时,发现 CloudSat 的数据在 2019 年初升级为 R05 版本,而该脚本(包括 NCL 版)无法正常处理 R05 的数据。尝试后发现原因是 R05 中的变量不再附有自描述的属性,所以下面这样的代码将会失效:
  1. # Read attributes.
  2. attrs = dset.attributes(full=1)
  3. lna=attrs["long_name"]
  4. long_name = lna[0]
  5. sfa=attrs["factor"]
  6. scale_factor = sfa[0]        
  7. vra=attrs["valid_range"]
  8. valid_min = vra[0][0]        
  9. valid_max = vra[0][1]        
  10. ua=attrs["units"]
  11. units = ua[0]
复制代码
解决方法也很简单。这些属性虽然不再直接附在变量上,但仍然以 vdata 的形式存储在 hdf 文件中,把它们手动查询出来即可。下面为重新读取后的结果:
test.png

雷达反射率的剖面图中额外添上了地形(灰色部分)。结果与上面的图片一致,可以发现原图片中低空处的强回波是地形导致的。相关代码直接贴在后面:
  1. from pyhdf import HDF, VS, SD

  2. import numpy as np
  3. import numpy.ma as ma
  4. import pandas as pd

  5. class reader:
  6.     '''用于读取CloudSat R05 2B产品的类.'''

  7.     def __init__(self, fname):
  8.         '''打开HDF,Vdata table,和scientific dataset.'''

  9.         self.hdf = HDF.HDF(fname, HDF.HC.READ)
  10.         self.vs = self.hdf.vstart()
  11.         self.sd = SD.SD(fname, SD.SDC.READ)

  12.     def attach_vdata(self, varname):
  13.         '''从Vdata table中读取一个变量的所有记录.'''

  14.         vdata = self.vs.attach(varname)
  15.         data = vdata[:]
  16.         vdata.detach()

  17.         return data
  18.    
  19.     def scale_and_mask(self, data, varname):
  20.         '''依据变量的factor进行放缩,并根据valid_range来mask掉填充值.'''

  21.         factor = self.attach_vdata(f'{varname}.factor')[0][0]
  22.         valid_min, valid_max = \
  23.             self.attach_vdata(f'{varname}.valid_range')[0][0]

  24.         invalid = np.logical_or(data <= valid_min, data >= valid_max)
  25.         data = ma.array(data, mask=invalid)
  26.         data = data / factor

  27.         return data
  28.    
  29.     def read_geo(self, process=True):
  30.         '''读取经纬度和地形高度.参数process指定是否放缩并mask地形高度.'''

  31.         lon = np.array(self.attach_vdata('Longitude'))[:,0]
  32.         lat = np.array(self.attach_vdata('Latitude'))[:,0]
  33.         elv = np.array(self.attach_vdata('DEM_elevation'))[:,0]

  34.         if process:
  35.             elv = self.scale_and_mask(elv, 'DEM_elevation')
  36.         
  37.         return lon, lat, elv
  38.    
  39.     def read_time(self, datetime=True):
  40.         '''读取每个数据点对应的时间.
  41.         
  42.         datetime=True: 返回所有数据点的日期时间组成的DatetimeIndex.
  43.         datetime=False: 返回所有数据点相对于第一个点所经过的秒数组成的numpy数组.
  44.         '''

  45.         seconds = np.array(self.attach_vdata('Profile_time'))[:,0]

  46.         if datetime:
  47.             TAI = self.attach_vdata('TAI_start')[0][0]
  48.             start = pd.to_datetime('1993-01-01') + pd.Timedelta(seconds=TAI)
  49.             offsets = pd.to_timedelta(seconds, unit='s')
  50.             time = pd.date_range(start=start, end=start, periods=offsets.size)
  51.             time = time + offsets
  52.             return time
  53.         else:
  54.             return seconds
  55.    
  56.     def read_sds(self, varname, process=True):
  57.         '''读取scientific dataset.参数process指定是否放缩并mask.'''

  58.         data = self.sd.select(varname)[:]
  59.         if process:
  60.             data = self.scale_and_mask(data, varname)
  61.         
  62.         return data
  63.    
  64.     def close(self):
  65.         '''关闭HDF文件.'''

  66.         self.vs.end()
  67.         self.sd.end()
  68.         self.hdf.close()

  69. # 测试数据.
  70. if __name__ == '__main__':
  71.     fname = '2010128055614_21420_CS_2B-GEOPROF_GRANULE_P1_R05_E03_F00.hdf'
  72.     f = reader(fname)
  73.     f.close()
复制代码
  1. #-----------------------------------------------------------
  2. # 2020/10/19
  3. # 读取CloudSat 2B-GEOPROF数据,画出卫星轨迹和雷达反射率因子的剖面图.
  4. # 参考:
  5. # [1] https://hdfeos.org/zoo/index_openCDPC_Examples.php#CloudSAT
  6. # [2] https://moonbooks.org/Articles/How-to-read-CloudSat-2B-GEOPROF-GRANULE-HDF4-file-using-python-and-pyhdf-/
  7. #-----------------------------------------------------------
  8. from read_CloudSat import reader

  9. import numpy as np
  10. import numpy.ma as ma

  11. import matplotlib.pyplot as plt
  12. from mpl_toolkits.axes_grid1 import make_axes_locatable
  13. import cartopy.crs as ccrs
  14. from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter

  15. def set_map(ax):
  16.     '''设置地图.'''

  17.     proj = ccrs.PlateCarree()
  18.     ax.coastlines(lw=0.5)
  19.    
  20.     xticks = np.arange(-180, 180 + 60, 60)
  21.     yticks = np.arange(-90, 90 + 30, 30)
  22.     ax.set_xticks(xticks, crs=proj)
  23.     ax.set_yticks(yticks, crs=proj)
  24.     ax.xaxis.set_major_formatter(LongitudeFormatter())
  25.     ax.yaxis.set_major_formatter(LatitudeFormatter())
  26.     ax.tick_params('both', labelsize='x-small')

  27.     ax.set_global()

  28. def draw_track(ax, lon1D, lat1D):
  29.     '''根据经纬度画出轨迹,并标识出起点.'''

  30.     ax.plot(lon1D, lat1D, lw=2, color='b', transform=ccrs.Geodetic())
  31.     ax.plot(lon1D[0], lat1D[0], 'ro', ms=3, transform=ccrs.PlateCarree())
  32.     ax.text(
  33.         lon1D[0] + 5, lat1D[0], 'start', color='r', fontsize='x-small',
  34.         transform=ccrs.PlateCarree()
  35.     )

  36. def draw_cross_section(ax, time, hgt, data):
  37.     '''画出data的时间-高度剖面.'''

  38.     im = ax.pcolormesh(time, hgt, data.T, cmap='jet', shading='nearest')
  39.    
  40.     ax.set_ylim(0, 20)
  41.     ax.set_xlim(0, time.max())
  42.     ax.tick_params(labelsize='x-small')
  43.     ax.set_xlabel('Seconds since the start of the granule [s]', fontsize='x-small')
  44.     ax.set_ylabel('Height [km]', fontsize='x-small')

  45.     divider = make_axes_locatable(ax)
  46.     cax = divider.append_axes('bottom', size='10%', pad=0.42)
  47.     cbar = plt.colorbar(im, cax=cax, extend='both', orientation='horizontal')
  48.     cbar.ax.tick_params(labelsize='x-small')
  49.     cbar.set_label('dBZe', fontsize='x-small')

  50. def draw_elevation(ax, time, elv):
  51.     '''画出地形抬升.'''
  52.    
  53.     ax.fill_between(time, elv, color='gray')

  54. if __name__ == '__main__':
  55.     # 读取数据
  56.     fname = '2010128055614_21420_CS_2B-GEOPROF_GRANULE_P1_R05_E03_F00.hdf'
  57.     f = reader(fname)
  58.     lon, lat, elv = f.read_geo()
  59.     data = f.read_sds('Radar_Reflectivity')
  60.     height = f.read_sds('Height')
  61.     time = f.read_time(datetime=False)
  62.     start_time, end_time = f.read_time(datetime=True)[[0,-1]]
  63.     f.close()

  64.     # 每个profile的高度有微小的差别,这里用平均值代替所有profile的高度.
  65.     hgt = height.mean(axis=0).data
  66.     # 高度单位全部转为km.
  67.     elv /= 1000
  68.     hgt /= 1000

  69.     fig = plt.figure(dpi=200, figsize=(6, 6))
  70.     ax1 = fig.add_axes([0.25, 0.45, 0.5, 0.5], projection=ccrs.PlateCarree())
  71.     ax2 = fig.add_axes([0.25, 0.2, 0.5, 0.3])

  72.     set_map(ax1)
  73.     draw_track(ax1, lon, lat)

  74.     # 把granule的起止时间标上.
  75.     start_str = start_time.strftime('%Y-%m-%d %H:%M')
  76.     end_str = end_time.strftime('%Y-%m-%d %H:%M')
  77.     ax1.set_title(f'From {start_str} to {end_str}', fontsize='small')

  78.     draw_cross_section(ax2, time, hgt, data)
  79.     draw_elevation(ax2, time, elv)
  80.     ax2.set_title('Radar Reflectivity Factor', fontsize='small')

  81.     fig.savefig('test', bbox_inches='tight')
  82.     plt.close(fig)
复制代码



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

新浪微博达人勋

 楼主| 发表于 2022-2-26 11:08:22 | 显示全部楼层
郭小小 发表于 2022-2-26 09:55
楼主你好, 我看了你读cloudsat数据的脚本,有一句是from read_CloudSat import reader,但是我运行的时 ...

这个module不是网上的,我贴出来的代码有两个部分,第一部分即这个module,是个很简单的class
密码修改失败请联系微信:mofangbao
回复 支持 1 反对 0

使用道具 举报

新浪微博达人勋

发表于 2022-2-26 09:55:47 | 显示全部楼层

楼主你好, 我看了你读cloudsat数据的脚本,有一句是from read_CloudSat import reader,但是我运行的时候说No module named 'read_CloudSat',我pip install这个module ,显示ERROR: No matching distribution found for read_CloudSat,找不到这个呢
密码修改失败请联系微信:mofangbao
回复 支持 1 反对 0

使用道具 举报

新浪微博达人勋

 楼主| 发表于 2022-2-26 08:57:03 | 显示全部楼层
郭小小 发表于 2022-2-25 17:13
楼主,请问我把你的代码拿来测试,换了一个同样的数据,怎么显示HDF4Error: HDF: no such file

看看完整代码
密码修改失败请联系微信:mofangbao
回复 支持 1 反对 0

使用道具 举报

新浪微博达人勋

发表于 2020-11-9 21:18:58 | 显示全部楼层
请问一下楼主,下载cloudsat数据的时候,选择了相应的时间里面的文件会有Ready TO BE DOWNLOADED 但是点进去就是网页不存在,请问这是什么问题呢?还能怎么下载呢?那个ftp的官网也打不开,谢谢了!!
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

 楼主| 发表于 2020-11-14 15:14:02 | 显示全部楼层
南衣GQ 发表于 2020-11-9 21:18
请问一下楼主,下载cloudsat数据的时候,选择了相应的时间里面的文件会有Ready TO BE DOWNLOADED 但是点进 ...

我直接在这个网站
http://www.cloudsat.cira.colostate.edu/
注册后选取要下载的数据,然后就可以下载了。不知道你这是什么问题。
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-11-15 10:27:43 | 显示全部楼层
牛人啊。问下楼主,参考这个代码,是不是就可以读它的双胞胎数据CALIPSO了。
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-11-15 15:01:23 | 显示全部楼层
灭火器 发表于 2020-11-14 15:14
我直接在这个网站
http://www.cloudsat.cira.colostate.edu/
注册后选取要下载的数据,然后就可以下载 ...

C:\Users\郜倩倩\DesktopC:\Users\郜倩倩\Desktop
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-11-15 15:11:49 | 显示全部楼层
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

 楼主| 发表于 2020-11-16 08:40:26 | 显示全部楼层
独孤酒见 发表于 2020-11-15 10:27
牛人啊。问下楼主,参考这个代码,是不是就可以读它的双胞胎数据CALIPSO了。

读取CALIPSO不用这么麻烦,直接用pyhdf.SD读,参考hdfeos网站的示例代码即可。
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-11-16 15:37:14 | 显示全部楼层
灭火器 发表于 2020-11-16 08:40
读取CALIPSO不用这么麻烦,直接用pyhdf.SD读,参考hdfeos网站的示例代码即可。

谢谢高人,近期在读这个CALIPSO的数据,头疼。
到时候有问题向您请教。
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2021-1-20 21:58:42 | 显示全部楼层
不好意思,麻烦请问楼主能提供一个不是面向对象的编程吗?小白实在看不懂面向对象,只会直接写下来,感谢您!谢谢!
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2021-2-2 10:34:40 | 显示全部楼层
楼主你好,我通过ftp下了2B-GEOPROF-LIDAR.P2_R05数据,请问为什么Data Fields里面连雷达反射率都没有呢?楼主下的数据中也只有这几个参量吗?
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

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

本版积分规则

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

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

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