ENVI IDL:MODIS SWATH产品的点位-像元提取(另附Python代码)

本文档介绍了如何使用ENVI IDL和Python从MODIS SWATH产品中提取与特定经纬度(39.90°N,116.40°E)最接近的AOD有效结果。实验涉及年月日的提取、距离限制以及找到最近有效像元的方法,并提供了相关代码示例。在IDL部分,讨论了imsl_datetodays和imsl_daystodate函数的应用,以及如何计算与指定点位的欧式距离。Python部分则提到了使用pyhdf模块读取HDF4数据的注意事项和安装问题。
摘要由CSDN通过智能技术生成

目录

01 说明

1.1 ENVI IDL本小节实验说明

1.2 Python本小节实验说明

02 代码

2.1 我的ENVI IDL代码

2.2 实验给定ENVI IDL代码

2.3 我的python代码

2.4 实验给定python代码


01 说明

实验说明

提取/coarse_data/chapter_3/modis_swath/目录下所有MODIS气溶胶产品中Image_Optical_Depth_Land_And_Ocean数据集内与北京经纬度(39.90°N,116.40°E)最接近的像元的AOD有效结果,每个有效结果按 年-月-日,经度,纬度,AOD值的形式输出到文本文件,数值保留3位小数,如:

2013-01-09,116.491,39.912,2.767

1.1 ENVI IDL本小节实验说明

首先简单说一下思路,这个实验有两个难点:

第一就是年月日的提取,下面是文件名:

MYD04_3K.A2018137.0545.061.2018137182124.hdf

2018年第137天如何转化为年月日,你可以自己编写函数,但是ENVI IDL也有一些取巧的函数:

imsl_datetodays和imsl_daystodate两个函数:
前面是将年月日形式的日期转化为儒略日(实际上就是输入日期距离1900年1月1日的累加天数);后者正好相反。
这是ENVI IDL语法:
Result = imsl_datetodays(day, month, year)

imsl_daystodate, days, day, month, year

那么怎么用呢?很简单,假定我们需要2018年第127天的年月日数据,那么可以通过Result = imsl_datetodays(day, month, year)获取得到2017年12月31日的儒略日,然后在此基础上加上127天就是2018年第127天这个日子的儒略日,然后通过imsl_daystodate, days, day, month, year将该儒略日传入得到对应的年月日数据。

 第二就是如何获取距离指定点位的最接近的有效像元的栅格值?

第一像元要最接近指定点位;

第二像元的值要是有效的;

这里我就自己擅自加上一些要求吧,我感觉实验有点简单了。如果距离指定点位(实际解决问题时此处应该就是气象站点)的最近像元(而且是有效的)的距离是10°呢,我个人认为这个最近像元的栅格值与该点位进行对应是没有意义的,所以我们还需要进行范围的限制。这里我假定最大距离是0.1度,如果超过了这个点位就算作无效值了。

有时候可能会有这样的需求,就是我做的是长时间序列,那么十分希望点位与某一个像元一直对应,不要更换即使是无效值。如果是WGS84等常规一些的坐标系(常规就在于你仅仅依据左上角的经纬度和经纬度分辨率、行列号就可以简单得知每一个像元的经纬度,反过来就是你知道一个像元的经纬度那么就知道这个像元的行列号,再类推一下,如果你知道点位的经纬度,那么你可以近似知道该点位距离哪个经纬度最靠近然后一直使用那个像元的经纬度)。(其实不常规的坐标系也是如此,只是计算时就不是通过行列号转经纬度这种方式进行最接近像元的求取了,这里不进行详细描述<其实和下面的思路类似>)

但是本实验无法进行这样的操作,我们暂且不进行什么值要有效、也不考虑范围限制,仅仅就是最近这一个要求。为什么不能像上面如此操作呢?因为MODIS SWATH产品是轨道卫星,每次拍摄的影像都会有一定的偏移,换句话说,今天的影像和明天的影像有一些地区重叠,有一些地区不一致。图示如下:

也就是每天都不一样,所以今天求取的最近像元不能很好地对应明天地那个位置。

因此,我们的思路就是,先得到lon和lat的2D数据集,然后通过其得到所有像元与指定点位的欧式距离(初中知识)distance的2D数据集。然后进行筛选(怎么筛选看代码),筛选我们需要使用到where函数:
   Result = WHERE( Array_Expression [, Count])
它会返回数组中所有不等于0的元素的索引(以数组形式)。

不行,太简单了,讲不下去了,看代码吧(我会稍微详细一些注释的)。函数不会就看帮助或者买书<其实书上不一定有,还是看帮助吧>

需要说明,我的代码和实验给定代码有一定的区别,第一就是实验给定代码实际上存在瑕疵在数据集中有效值的范围是-100~5000:
 但是实验简单的将<=0的全部舍弃,这会导致部分数据缺失.

当然,在我的结果中,我和实验都有的数据都是一致的,只是我的多一些:
我的结果:

实验结果:

1.2 Python本小节实验说明

Python我的和我的ENVI IDL代码思路类似(结果完全一致),而实验和他自己的相似。所以不会重复说明。

这里需要安装一个读取HDF4相关数据的模块,pyhdf。但是直接通过pycharm进行安装存在报错说找不到模块当你from pyhdf.SD import SD时。所以我又重新通过wheel文件进行安装,另外我有多个版本的python,而且我现在的所有的实验放在一个项目里,这个项目为了避免版本冲突我创建了虚拟环境,所以自己安装wheel文件有一定的小小麻烦。(麻烦1就是你可能安装到其他版本的python去,麻烦2就是你就算安装本版本去可能也只是安装到本体,而没有安装到虚拟环境中)。

下载wheel地址:Pyhdf Wheel文件下载 Here~

注意要看自己对应哪个版本(我怀疑之前pycharm下载就是没有正确对应不同pyhon导致错误)

我的是python3.8,所以我下的是:

pip安装如下:

 (当然,你自己cd也行)

 

 

02 代码

2.1 我的ENVI IDL代码

; @Author	: ChaoQiezi
; @Time		: 2023年9月26日-上午8:46:22
; @Email	: chaoqiezi.one@qq.com

; 该程序用于 获取MODIS SWTAH产品与某点位的最接近的AOD有效值

function read_h4, h4_path, ds_name
    ; 该函数用于读取HDF4文件的数据集
    
    file_id = hdf_sd_start(h4_path, /read)  ; 以只读模式打开HDF4文件, 返回文件ID
    ds_ix = hdf_sd_nametoindex(file_id, ds_name)  ; 获取数据集的索引-通过数据集名称获取索引
    ds_id = hdf_sd_select(file_id, ds_ix)  ; 获取数据集ID
    hdf_sd_getdata, ds_id, ds
    
    hdf_sd_endaccess, ds_id  ; 关闭数据集
    hdf_sd_end, file_id  ; 关闭文件
    
    return, ds
end

function read_h4_attr, h4_path, ds_name, attr_name
    ; 该函数用于获取HDF4文件的数据集属性
    
    file_id = hdf_sd_start(h4_path, /read)  ; 只读模式打开HDF4文件, 返回文件ID
    ds_ix = hdf_sd_nametoindex(file_id, ds_name)  ; 获取数据集的索引
    ds_id = hdf_sd_select(file_id, ds_ix)  ; 获取数据集ID
    attr_ix = hdf_sd_attrfind(ds_id, attr_name)  ; 获取数据集下属性的索引
    hdf_sd_attrinfo, ds_id, attr_ix, data=attr
    
    hdf_sd_endaccess, ds_id  ; 关闭数据集
    hdf_sd_end, file_id  ; 关闭文件
    
    return, attr
end

pro mod04_nearest_point_value_extracting
    ; 准备
    in_dir = 'D:\Objects\JuniorFallTerm\IDLProgram\Experiments\ExperimentalData\chapter_3\MODIS_SWATH'
    out_dir = 'D:\Objects\JuniorFallTerm\IDLProgram\Experiments\ExperimentalData\chapter_3\OutMe'
    point_lon = 116.40d  ; 双精度浮点
    point_lat = 39.90d
    aod_name = 'Image_Optical_Depth_Land_And_Ocean'
    lon_name = 'Longitude'
    lat_name = 'Latitude'
    sf_name = 'scale_factor'
    off_name = 'add_offset'
    valid_range_name = 'valid_range'
    max_distance = 0.1  ; degree, 约等于11KM

    if ~file_test(out_dir, /directory) then file_mkdir, out_dir  ; 如果文件夹不存在则创建
    openw, 1, out_dir + '\nearest_aod.txt'  ; 创建一个txt文件, 通过代号1对其进行后续处理
    
    hdf_paths_list = file_search(in_dir, 'MYD04_3K*.hdf', count=file_amount)  ; 获取文件夹下满足通配符的所有文件的绝对路径(字符串数组形式返回)
    foreach hdf_path, hdf_paths_list do begin  ; 类似于python的for path in hdf_paths_list
        hdf_name = file_basename(hdf_path)  ; 获取绝对路径下的文件名称
        aod = float(read_h4(hdf_path, aod_name))  ; 原本为int的数组, 后续进行nan<浮点型的一个不是数字的值>赋值会警告, 事先转float型
        lon = read_h4(hdf_path, lon_name)
        lat = read_h4(hdf_path, lat_name)
        aod_sf = read_h4_attr(hdf_path, aod_name, sf_name)  ; 注意返回的属性实际上为数组形式, 下面类似
        aod_off = read_h4_attr(hdf_path, aod_name, off_name)
        aod_range = read_h4_attr(hdf_path, aod_name, valid_range_name)
        
        aod_invalid_ix = where(((aod lt aod_range[0]) or (aod gt aod_range[-1])), /null)
        ; 获取所有逻辑运算后为1的元素的索引, /null表示如果一个1没有那么返回/null而不是-1
        ; 如果你的aod_invalid_ix会作为其他数组的索引进行取值那么一般会加上/null
        ; 因为如果不加, 那么当出现一个值也没有时就会返回-1, 实际上-1也是可以在数组中取到值的, 当这个操作是错误的
        ; 但是如果返回!null那么取值数组时实际上取回的就是空的也就是!null==> 后续只需要添加判断即可
        aod[aod_invalid_ix] = !values.F_NAN  ; 赋值nan(not a number)
        aod = aod * aod_sf[0] + aod_off[0]  ; 回归真实值
        
        ; 获取年-月-日(年积日 ==> 年月日)
        year = fix(strmid(hdf_name, 10, 4))  ; strmid提取索引10后面的4个字符, fix将其转化为整型
        year_days = fix(strmid(hdf_name, 14, 3))
        _temp = imsl_datetodays(31, 12, year - 1)  ; 计算[指定年月日]至 1900年1月1日的天数
        imsl_daystodate, _temp + year_days, day, month, year  ; 计算1900年1月1日过去[指定天数]后的年月日
        
        distances = sqrt((lon - point_lon) ^ 2.0d + (lat - point_lat) ^ 2.0d)  ; 计算所有像元与指定点位的距离的数组
        distances[where(finite(aod, /nan) or (distances gt max_distance), /null)] = !values.F_NAN  ; finite函数用于检测当前输入是否为NAN
        ; 上述实际上时将所有aod值无效的像元位置或者距离超过最大距离限制的像元位置上的distances全部赋值为nan
        valid_ix = where(distances eq min(distances, /nan), /null)  ; min表示取最小值, /nan表示忽略数组中的nan
        
        if valid_ix eq !null then continue  ; 说明不满足要求: 可能是aod无效值\距离的范围限制
        nearest_aod = aod[valid_ix[0]]
        nearest_lon = lon[valid_ix[0]]
        nearest_lat = lat[valid_ix[0]]

        printf, 1, year, month, day, nearest_lon, nearest_lat, nearest_aod, $
            format='%i-%02i-%02i,%0.3f,%0.3f,%0.3f'  ; 这里采用C语言的格式化输出语法, 你也可以使用fortran语言的, IDL支持它
    endforeach
    
    free_lun, 1  ; 释放代号为1的文件, 否则其他软件无法打开该文件, 因为一直被占用锁了
end

2.2 实验给定ENVI IDL代码

function hdf4_data_get,file_name,sds_name
  sd_id=hdf_sd_start(file_name,/read)
  sds_index=hdf_sd_nametoindex(sd_id,sds_name)
  sds_id=hdf_sd_select(sd_id,sds_index)
  hdf_sd_getdata,sds_id,data
  hdf_sd_endaccess,sds_id
  hdf_sd_end,sd_id
  return,data
end

function hdf4_attdata_get,file_name,sds_name,att_name
  sd_id=hdf_sd_start(file_name,/read)
  sds_index=hdf_sd_nametoindex(sd_id,sds_name)
  sds_id=hdf_sd_select(sd_id,sds_index)
  att_index=hdf_sd_attrfind(sds_id,att_name)
  hdf_sd_attrinfo,sds_id,att_index,data=att_data
  hdf_sd_endaccess,sds_id
  hdf_sd_end,sd_id
  return,att_data
end

pro mod04_nearest_point_value_extracting
  extract_lon=116.40
  extract_lat=39.90
  point_name='Beijing'
  data_path='O:/coarse_data/chapter_3/modis_swath/'
  file_list=file_search(data_path,'*.hdf')
  file_n=n_elements(file_list)   
  out_file=data_path+'point_value_'+point_name+'.txt'
  openw,1,out_file

  for file_i=0,file_n-1 do begin
    out_date=strmid(file_basename(file_list[file_i]),10,7);年积日获取
    date=fix(strmid(file_basename(file_list[file_i]),14,3))
    out_year_fix=fix(strmid(file_basename(file_list[file_i]),10,4))
    date_julian=imsl_datetodays(31,12,out_year_fix-1) ;IMSL相关的函数说明在ENVI安装目录../Exelis/IDL85/help/pdf/advmathstats.pdf中
    imsl_daystodate,date_julian+date,out_day,out_month,out_year

    modis_lon_data=hdf4_data_get(file_list[file_i],'Longitude')
    modis_lat_data=hdf4_data_get(file_list[file_i],'Latitude')
    modis_aod_data=hdf4_data_get(file_list[file_i],'Image_Optical_Depth_Land_And_Ocean')
    scale_factor=hdf4_attdata_get(file_list[file_i],'Image_Optical_Depth_Land_And_Ocean','scale_factor')
    fill_value=hdf4_attdata_get(file_list[file_i],'Image_Optical_Depth_Land_And_Ocean','_FillValue')            
    modis_aod_data=(modis_aod_data ne fill_value[0])*modis_aod_data*scale_factor[0]
    
    lon_minus=(extract_lon-modis_lon_data)
    lat_minus=(extract_lat-modis_lat_data)
    distance=sqrt(lon_minus^2.0+lat_minus^2.0)
    min_pos=where(distance eq min(distance))
    if modis_aod_data[min_pos[0]] eq 0.0 then continue
print,out_year,out_month,out_day,modis_lon_data[min_pos[0]],modis_lat_data[min_pos[0]],modis_aod_data[min_pos[0]],format='(I0,"-",I02,"-",I02,",",3(F0.3,:,","))'
printf,1,out_year,out_month,out_day,modis_lon_data[min_pos[0]],modis_lat_data[min_pos[0]],modis_aod_data[min_pos[0]],format='(I0,"-",I02,"-",I02,",",3(F0.3,:,","))'
  endfor
  free_lun,1
end

2.3 我的python代码

# @Author   : ChaoQiezi
# @Time     : 2023-09-26  19:58
# @Email    : chaoqiezi.one@qq.com

"""
This script is used to 用于读取MODIS SWATH产品中与某点位最接近的像元的有效值
"""

import os
import glob
import numpy as np
import datetime
from pyhdf.SD import SD


def read_h4(h4_path, ds_name):
    """
    该函数用于读取HDF4文件的数据集
    :param h4_path:
    :param ds_name:
    :return:
    """

    f = SD(h4_path)
    data = f.select(ds_name)[:]
    f.end() # 关闭文件

    return data


def read_h4_attr(h4_path, ds_name, attr_name):
    """
    该函数用于读取HDF4文件数据集的属性
    :param h4_path:
    :param ds_name:
    :param attr_name:
    :return:
    """

    f = SD(h4_path)
    attr = f.select(ds_name).attributes()[attr_name]
    f.end()

    return attr


# 准备
in_dir = r'D:\Objects\JuniorFallTerm\IDLProgram\Experiments\ExperimentalData\chapter_3\MODIS_SWATH'
out_dir = r'D:\Objects\JuniorFallTerm\IDLProgram\Experiments\ExperimentalData\chapter_3\OutMePy'
if not os.path.exists(out_dir):
    os.makedirs(out_dir)
aod_name = 'Image_Optical_Depth_Land_And_Ocean'
lon_name = 'Longitude'
lat_name = 'Latitude'
sf_name = 'scale_factor'
off_name = 'add_offset'
valid_range_name = 'valid_range'
point_lon = 116.40  # 北京经纬度
point_lat = 39.90
max_distance = 0.1  # 约等于11KM


# 预
h4_paths_list = glob.glob(os.path.join(in_dir, r'MYD04_3K*.hdf'))

# 循环处理
with open(os.path.join(out_dir, 'nearest_point_value.txt'), 'w') as f:
    for h4_path in h4_paths_list:
        h4_name = os.path.basename(h4_path)
        # 读取数据集和相关属性
        aod = read_h4(h4_path, aod_name).astype(np.float32)
        lon = read_h4(h4_path, lon_name)
        lat = read_h4(h4_path, lat_name)
        sf = read_h4_attr(h4_path, aod_name, sf_name)
        off = read_h4_attr(h4_path, aod_name, off_name)
        valid_range = read_h4_attr(h4_path, aod_name, valid_range_name)

        # 获取年月日(年积日==>年月日)
        year = int(h4_name[10:14])
        days = int(h4_name[14:17])
        date = datetime.datetime(year, 1, 1) + datetime.timedelta(days - 1)
        year, month, day = date.year, date.month, date.day


        # 数据集处理
        aod[(aod < valid_range[0]) | (aod > valid_range[1])] = np.nan
        aod = aod * sf + off

        # 距离
        distances = np.sqrt((lon - point_lon) ** 2 + (lat - point_lat) ** 2)
        distances[np.isnan(aod)] = np.nan  # 排除无效值
        distances[distances > max_distance] = np.nan  # 范围限制

        # 选择最接近的有效的像元
        if np.all(np.isnan(distances)):
            continue
        nearest_pixel_ix = np.nanargmin(distances)
        nearest_pixel_aod = aod.flat[nearest_pixel_ix]  # flat属性用于将数组转换为一维数组
        nearest_pixel_lon = lon.flat[nearest_pixel_ix]
        nearest_pixel_lat = lat.flat[nearest_pixel_ix]

        f.write('{:d}-{:02d}-{:02d},{:.3f},{:.3f},{:.3f}\n'.format(
            year, month, day, nearest_pixel_lon, nearest_pixel_lat, nearest_pixel_aod))

2.4 实验给定python代码

# 需要hdf4库pyhdf。通过pip install pyhdf的方式在线安装pyhdf可能有错,建议直接下载对应python版本的pyhdf wheel文件进行安装该库(https://www.lfd.uci.edu/~gohlke/pythonlibs/)。
import numpy as np
from pyhdf.SD import SD
import os
import time


def main():
    target_lon = 116.40
    target_lat = 39.90
    file_prefix = '.hdf'
    input_directory = 'O:/coarse_data/chapter_1/MODIS_2018_mod04_3k/'
    file_list = os.listdir(input_directory)
    with open('O:/coarse_data/chapter_1/MODIS_2018_mod04_3k/Beijing_value.txt', 'w') as txt_file:
        for file_i in file_list:
            if file_i.endswith(file_prefix):
                file_name = input_directory + file_i
                file_doy = file_i[10:17]
                file_date = time.strptime(file_doy, '%Y%j')
                date_str = time.strftime('%Y-%m-%d', file_date)
                file_datasets = SD(file_name)
                lon_data = file_datasets.select('Longitude')
                lon_data_array = lon_data.get()
                lat_data = file_datasets.select('Latitude')
                lat_data_array = lat_data.get()
                aod_data = file_datasets.select('Image_Optical_Depth_Land_And_Ocean')
                aod_data_array = aod_data.get()
                aod_att = aod_data.attributes()

                lon_difference = lon_data_array - target_lon
                lat_difference = lat_data_array - target_lat
                distance = ((lon_difference ** 2) + (lat_difference ** 2)) ** 0.5
                distance_min = np.min(distance)
                min_pos = np.where(distance == distance_min)
                if (distance_min < 0.1) and (aod_data_array[min_pos] != -9999):
                    print('%s,%.3f,%.3f,%.3f' % (date_str, lon_data_array[min_pos], lat_data_array[min_pos],
                                                 aod_data_array[min_pos] * aod_att['scale_factor']))
                    print('%s,%.3f,%.3f,%.3f' % (date_str, lon_data_array[min_pos], lat_data_array[min_pos],
                                                 aod_data_array[min_pos] * aod_att['scale_factor']), file=txt_file)


if __name__ == '__main__':
    main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

炒茄子

不装逼我浑身难受aaa

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值