博客 / 詳情

返回

GEE:批量處理和下載指定時間段的MODIS-GPP產品(MOD17A3HGF)

01 説明

  1. 任務分成兩個:第一是指定時間範圍,提取該範圍內的所有GPP影像求取均值;第二是指定時間範圍,按年尺度提取每一年中的GPP影像求取均值(一年一景)
  2. 要求包括:全球尺度、輸出地理座標系WGS84且分辨率為0.05°、無效值處理;

02 代碼説明

2.1 輸出指定時間段的影像均值

完整代碼:

// 選擇需要輸出GPP產品的時間範圍
var start_date = ee.Date.fromYMD(2023, 1, 1);  // 開始日期
var end_date = ee.Date.fromYMD(2023, 12, 31);  // 結束日期
var gpp_name = 'Gpp';  // 需要的波段名稱, MOD17A3HGF包括Gpp、Npp以及Npp_QC三個波段
var apply_land_mask = true;  // 是否掩膜只要陸地地區, 若為true則掩膜;若為false則不進行掩膜
var min_value = 0;  // 有效值的範圍
var max_value = 65500;  // 同理
var out_res = 0.05  // 輸出分辨率(單位為°)


// 加載MODIS-GPP產品(MOD17A3HGF)
var gpp_collection = ee.ImageCollection("MODIS/061/MOD17A3HGF");  // 從雲計算平台加載指定數據集
// 篩選指定年份的影像
var gpp_img = gpp_collection.filterDate(start_date, end_date).select(gpp_name).mean();
// 獲取並應用有效值範圍的掩膜
var valid_range_mask = gpp_img.gte(min_value).and(gpp_img.lte(max_value));
gpp_img = gpp_img.updateMask(valid_range_mask);
// 應用縮放因子(縮放後單位為:kg*C/m^2, 由於MOD17A3是年尺度,因此這也可以視為kg*C/m^2/year)
gpp_img = gpp_img.multiply(0.0001);
/*
為什麼要應用縮放因子?
因為GEE上存儲的數據量非常大, 整數存儲相比於小數存儲在大數據量面前將會有非常大的性價比減少
更多的存儲空間, 因此存儲時會先將小數放大n倍轉化為小數,在需要時乘以縮放因子即可恢復原來的數值.
*/
// 掩膜(只需要陸地地區)
if (apply_land_mask === true){
  var water_mask = ee.Image('MODIS/MOD44W/MOD44W_005_2000_02_24').select('water_mask');
  var land_mask = water_mask.eq(0);
  gpp_img = gpp_img.updateMask(land_mask);
}
// 定義地理範圍
var global_region = ee.Geometry.Rectangle([-180, -90, 180, 90], 'EPSG:4326', false);


// 可視化參數
var gpp_vis_param = {
  min: 0.0,
  max: 3.0,
  palette: ['#bbe029', '#0a9501', '#074b03']
};
// 可視化
Map.setCenter(0, 0, 2);  // 顯示的中心位置和縮放大小, 參數依次為(經度: 0, 緯度: 0, 縮放級別:2)


// 導出影像到Google Drive
var cur_year = start_date.get('year');
cur_year.evaluate(function(cur_year){
  Map.addLayer(gpp_img, gpp_vis_param, 'Annual_GPP_' + cur_year);
  
  Export.image.toDrive({
  image: gpp_img.resample('bilinear'),  // 輸出使用雙線性插值, 若不需要刪除.resample('bilinear')
  description: 'Global_GPP_' + cur_year,
  folder: 'Global_GPP',
  fileNamePrefix: 'Global_GPP_' + cur_year,
  region: global_region,
  crs: 'EPSG:4326',  // 座標系為WGS84
  crsTransform: [out_res, 0, -180, 0, -out_res, 90],  // 
  maxPixels: 1e13,  // 防止像元個數過多拒絕導出, 增加導出上限
  formatOptions: {'noData': -9999}  // NoData值為-9999
  });
});

可能需要説明的是evaluate方法(這段代碼使用evaluate方法有點多餘,但是後續批量輸出下載等是很難避免使用這個方法,不如在這裏將這個方法稍微理一理):

原文為:Asynchronously retrieves the value of this object from the server and passes it to the provided callback function.
即異步地將該變量(誰調用的evaluate方法誰就是此處所指代的the value,例如year_list.evaluate(),則the value指代year_list)傳遞給服務器,讓服務器計算它的值,服務器計算好該值之後再將其提供給回調函數。
所以含義是什麼?
不管異步這一操作,就是在客户端(你的瀏覽器)上存在一個變量,這個變量具體的值還不清楚沒有計算但是你現在又需要這裏面的值來進行後續處理,那麼你就可以通過evaluate方法將其傳遞給服務器(GEE的遠程雲計算平台),讓服務器計算好之後,再傳送回給客户端瀏覽器,再繼續在客户端本地進行後續處理,這裏的後續處理就是前面提及的回調函數,服務器計算好的結果應該怎麼傳送回給本地瀏覽器呢?你用一個變量去接?接住了還需要進行處理? ⇒ 那麼方法就是定義一個回調函數以及一個函數的參數,服務器計算好之後將這個計算好的變量值傳遞給這個回調函數參數,然後運行這個回調函數。
那麼異步是什麼意思呢?就是year_list.evaluate執行之後,變量到了服務器就好,接着往後運行後續代碼,而至於.evaluate()內部的回調函數的定義、運行回調函數它慢慢運行就好了,我的主程序是不會等待的。舉一個不恰當的例子就是我在去銀行取號排隊,中間排隊這個過程就是evaluate函數內部做的事情(變量上傳給服務器、服務器計算結果、結果返回給回調函數,執行回調函數),但是排隊過程我無需排隊一直等待(即不用等前面括號內的這一系列事情做完),我可以去做我想做的事情(執行後續代碼)例如買零食買玩具。

上述的evaluate方法因為時間關係並沒有解釋特別到位,但是這裏列舉一點示例稍微説明一下這個函數

// 情況1
var year_list = [2010, 2011, 2012, 2013, 2014];
year_list.map(function(cur_year){
  print(cur_year);
});

// 情況2
var year_list = ee.List.sequence(2010, 2014)
year_list.map(function(cur_year){
  print(cur_year);
});

// 情況3
var year_list = ee.List.sequence(2010, 2014)
year_list.evaluate(function(year_list){
  year_list.map(function(cur_year){
    print(cur_year);
  });
})

對於上面三種情況,只有情況2無法運行會報錯,為什麼呢?這裏就需要理解為什麼要使用.evaluate()方法了。由於year_list中的各個元素值是需要print輸出的。而通過ee.List.sequence定義的year_list使用的map方法是需要在服務端上運行的(這裏就要提及為什麼情況1的map就不需要,因為通過[]定義的是具體array數組,其map方法不需要在服務器中運行,此外這兩個map方法也有一定的差別,如果對於情況2的報錯內容仔細查看發現首先是報錯map方法沒有返回值),而map方法中的print是在本地進行運行的(因為print輸出肯定是在瀏覽器中展示結果自然是需要在本地瀏覽器中運行),因此map在服務器中運行時,遇到了print,服務器不知道打印到哪裏去,因此報錯了。但是對於情況3,year_list.evaluate將變量傳遞給服務器計算之後傳回本地瀏覽器,執行回調函數,由於變量已經傳回本地了因此print輸出自然沒有問題。

至於異步這一問題,查看下方代碼和輸出自行理解即可,這裏不再贅述:

// 情況4
print('1. 代碼開始運行')
var year_list = ee.List.sequence(2010, 2014)
year_list.evaluate(function(year_list){
  print('3. 獲取得到服務器傳遞的變量值, 批量輸出中···')
  year_list.map(function(cur_year){
    print(cur_year);
  });
  print('4. 回調函數執行完畢')
})
print('2. 主程序運行完畢')

輸出結果:

異步的結果

2.2 年尺度上批量輸出指定時間段的一年影像均值(一年一景)

完整代碼:

// 選擇需要輸出GPP產品的時間範圍(一年一景)
var start_date = ee.Date.fromYMD(2001, 1, 1);  // 開始日期, MOD17A3的生產範圍是2001-1-1至今
var end_date = ee.Date.fromYMD(2020, 12, 31);  // 結束日期
var gpp_name = 'Gpp';  // 需要的波段名稱, MOD17A3HGF包括Gpp、Npp以及Npp_QC三個波段
var apply_land_mask = true;  // 是否掩膜只要陸地地區, 若為true則掩膜;若為false則不進行掩膜
var is_vis = true  // 是否在下方地圖中顯示每一年的GPP產品(耗時)
var min_value = 0;  // 有效值的範圍
var max_value = 65500;  // 同理
var out_res = 0.05;  // 輸出分辨率(單位為°)


// 獲取年列表-用於批量輸出
var start_year = start_date.get('year');
var end_year = end_date.get('year');
var year_list = ee.List.sequence(start_year, end_year)

// 加載MODIS-GPP產品(MOD17A3HGF)
var gpp_collection = ee.ImageCollection("MODIS/061/MOD17A3HGF");  // 從雲計算平台加載指定數據集
// 獲取陸地掩膜
var water_mask = ee.Image('MODIS/MOD44W/MOD44W_005_2000_02_24').select('water_mask');
var land_mask = water_mask.eq(0);
// 定義地理範圍(默認全球)
var global_region = ee.Geometry.Rectangle([-180, -90, 180, 90], 'EPSG:4326', false)

// 可視化參數
var gpp_vis_param = {
  min: 0.0,
  max: 3.0,
  palette: ['#bbe029', '#0a9501', '#074b03']
};
// 可視化
Map.setCenter(0, 0, 2);  // 顯示的中心位置和縮放大小, 參數依次為(經度: 0, 緯度: 0, 縮放級別:2)


// 導出影像到Google Drive(ps: 一年一景是理論, 實際上由於全球區域太大且分辨率過高,因此一年的tiff會分塊輸出成多個tiff文件)
year_list.evaluate(function(year_list){
  year_list.map(function(cur_year){
    var cur_start_date = ee.Date.fromYMD(cur_year, 1, 1);
    var cur_end_date = ee.Date.fromYMD(cur_year, 12, 31);
    
    // 篩選指定年份的影像
    var gpp_img = gpp_collection.filterDate(cur_start_date, cur_end_date).select(gpp_name).first();
    // 獲取並應用有效值範圍的掩膜
    var valid_range_mask = gpp_img.gte(min_value).and(gpp_img.lte(max_value));
    gpp_img = gpp_img.updateMask(valid_range_mask);
    // 應用縮放因子(縮放後單位為:kg*C/m^2, 由於MOD17A3是年尺度,因此這也可以視為kg*C/m^2/year)
    gpp_img = gpp_img.multiply(0.0001);
    /*
    為什麼要應用縮放因子?
    因為GEE上存儲的數據量非常大, 整數存儲相比於小數存儲在大數據量面前將會有非常大的性價比減少
    更多的存儲空間, 因此存儲時會先將小數放大n倍轉化為小數,在需要時乘以縮放因子即可恢復原來的數值.
    */
    // 掩膜(只需要陸地地區)
    if (apply_land_mask === true){
      gpp_img = gpp_img.updateMask(land_mask);
    }
    // 是否可視化
    if (is_vis === true){
      Map.addLayer(gpp_img, gpp_vis_param, 'Annual_GPP_' + cur_year);
    }
    
    Export.image.toDrive({
    image: gpp_img.resample('bilinear'),  // 若需要最近鄰插值, 刪除.resample('bilinear')即可
    description: 'Global_GPP_' + cur_year,
    folder: 'Global_GPP',
    fileNamePrefix: 'Global_GPP_' + cur_year,
    crs: 'EPSG:4326',  // 座標系為WGS84
    crsTransform: [out_res, 0, -180, 0, -out_res, 90],  // 仿射係數
    region: global_region,  // 輸出的地理範圍
    maxPixels: 1e13,  // 防止像元個數過多拒絕導出, 增加導出上限
    formatOptions: {'noData': -9999}  // NoData值為-9999
    });
  })
});

這裏關於代碼説明不在過多贅述,原理類似,只是多加了一層循環。

本文由博客一文多發平台 OpenWrite 發佈!
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.