PIE-Engine 遙感云計算平臺上集成了海量遙感數據、強大的算力和豐富的算子,使得大規模尺度長時間序列的遙感數據分析可以在線快速完成,其中一大利器便是map算子。本期我們來詳細介紹map算子的作用,它和傳統for循環的區別,以及如何巧用map算子優化代碼,提高計算效率。
一、 map算子的作用
map算子是我們在云計算平臺上處理影像時常用的算子,可以對集合(List、FeatureCollection、ImageCollection)中的每一個元素進行操作,操作完成返回的結果仍是一個集合對象。以FeatureCollection調用map算子為例,其基本形式如下:
var featureCol = pie.FeatureCollection('NGCC/CHINA_PROVINCE_BOUNDARY');
var featureColNew = featureCol.map(function (feature) {
var geometry = feature.geometry();
var featureNew = pie.Feature(geometry.centroid());
return featureNew;
})
上述代碼取出featureCol 中的每一個feature,然后求取各feature的幾何中心,得到一個新的矢量集合-featureColNew。
二、map算子和for循環的區別
當然,有些map操作用傳統的for/while循環也可以實現,但二者在計算機制上有本質區別,for循環是在前端控制變量的循環,且是順序執行代碼,效率比較慢,一般在PIE-Engine里很少用。而map則是在后臺服務器上直接作用于集合中的元素進行循環操作,執行效率很快,相當于“并行執行任務”, 但在map里一般不再用 print、Map、Export 以及Reducer等方法。下面分別對比在集合中使用map算子和for循環的不同之處。
針對List中的每個元素進行循環計算
//對List中的每個元素加1,采用map算子
var list1 = pie.List([1,2,3,2]);
var list2 = list1.map(function (value) {
return pie.Number(value).add(1);
});
print("list2",list2);
輸出結果:
//對List中的每個元素加1,采用for循環
var list1 = pie.List([1,2,3,2]);
var list2 = [];
for(var i = 0;i
list2.push(pie.Number(list1.get(i)).add(1).getInfo());
}
print("list2",list2);
輸出結果:
List集合調用map算子后便可直接作用于其中的元素,而采用for循環則還需要增加諸多限制,才能實現相同的功能。
針對FeatureCollection中的每個Feature進行循環計算
利用中國省級行政區劃矢量數據計算每個省的面積,分別采用map算子和for循環進行計算。
(1)取FeatureCollection中的每個元素并計算其面積,采用map算子:
向上滑動閱覽
var featureCol = pie.FeatureCollection().load('NGCC/CHINA_PROVINCE_BOUNDARY');
var featureColNew = featureCol.map(function (feature) {
var name = feature.get("name");
var geometry = feature.geometry();
var area = geometry.area().divide(1000000);
feature = feature.set("name",name);
feature = feature.set("area",area);
return feature;
})
var result = featureColNew.reduceColumns(pie.Reducer.toList(2),["name","area"]);
print("result",result);
Map.addLayer(featureCol,,"featureCol");
Map.setCenter(118,39.7,3);
代碼鏈接:
https://engine.piesat.cn/engine-share/shareCode.html?id=cbe5f4f5efca4b5faa8344b0ab325c04
采用map算子獲取中國各省的面積
(2)取FeatureCollection中的每個元素并計算其面積,采用for循環:
向上滑動閱覽
var featureCol = pie.FeatureCollection().load('NGCC/CHINA_PROVINCE_BOUNDARY');
print(featureCol)
for(i = 0; i
var name = featureCol.getAt(i).get("name");
var geometry = featureCol.getAt(i).geometry();
var area = geometry.area().divide(1000000);
print(name,area);
}
Map.addLayer(featureCol,,"featureCollection");
Map.setCenter(118,39.7,3);
代碼鏈接:
https://engine.piesat.cn/engine-share/shareCode.html?id=4198d136975841269e367aec1ae47e70
運行結果:
▲for循環輸出各省面積
上述代碼采用map算子,2秒即可出結果,而for循環則需要40秒,等待時間較長,從而體現map算子的優勢。
針對ImageCollection中的每個Image進行循環計算
(1)取ImageCollection中的每個元素計算NDVI,采用map算子:
向上滑動閱覽
var featureCol = pie.FeatureCollection().load('NGCC/CHINA_PROVINCE_BOUNDARY');
var beijing = featureCol.filter(pie.Filter.eq("name", "北京市"));
var bjGeo = beijing.getAt(0).geometry();
var visParams = ;
Map.addLayer(beijing, visParams, "北京市");
Map.centerObject(beijing, 6);
var imageCol = pie.ImageCollection("LC08/01/T1")
.filterDate("2019-12-01", "2019-12-31")
.filterBounds(bjGeo);
print("imageCol", imageCol);
var imageColNDVI = imageCol.map(function (image) {
// NDVI計算
var img_Nir = image.select("B5");
var img_Red = image.select("B4");
var img_NDVI = img_Nir.subtract(img_Red).divide(img_Nir.add(img_Red)).rename("NDVI");
return image.addBands(img_NDVI);
});
print("imageColNDVI", imageColNDVI);
//NDVI繪制樣式
var visParamNDVI = {
min: -0.2,
max: 0.8,
palette: 'CA7A41, CE7E45, DF923D, F1B555, FCD163, 99B718, '+
'74A901, 66A000, 529400,3E8601, 207401, 056201, 004C00,'+
'023B01, 012E01, 011D01, 011301'
};
var imageNDVI = imageColNDVI.select("NDVI").mosaic().clip(bjGeo);
Map.addLayer(imageNDVI, visParamNDVI, "Layer_NDVI");
代碼鏈接:
https://engine.piesat.cn/engine-share/shareCode.html?id=4ec895ce6e3b411daab877713ca81f45
輸出結果:
(2)取ImageCollection中的每個元素計算NDVI,采用for循環:
向上滑動閱覽
var featureCol = pie.FeatureCollection().load('NGCC/CHINA_PROVINCE_BOUNDARY');
var beijing = featureCol.filter(pie.Filter.eq("name", "北京市"));
var bjGeo = beijing.getAt(0).geometry();
var visParams = { color: "ff0000ff", fillColor: "00000000" };
Map.addLayer(beijing, visParams, "北京市");
Map.centerObject(beijing, 6);
var imageCol = pie.ImageCollection("LC08/01/T1")
.filterDate("2019-8-01", "2019-8-30")
.filterBounds(bjGeo);
print("imageCol", imageCol);
var newCol=[];
for (i = 0; i
var image = imageCol.getAt(i);
var img_Nir = image.select("B5");
var img_Red = image.select("B4");
var img_NDVI = img_Nir.subtract(img_Red).divide(img_Nir.add(img_Red))
.rename("NDVI");
image = image.addBands(img_NDVI);
newCol.push(image);
}
var newImageCol=pie.ImageCollection().fromImages(newCol);
print("newImageCol", newImageCol);
//NDVI繪制樣式
var visParamNDVI = {
min: -0.2,
max: 0.8,
palette: 'CA7A41, CE7E45, DF923D, F1B555, FCD163, 99B718, ' +
'74A901, 66A000, 529400,3E8601, 207401, 056201, 004C00,' +
'023B01, 012E01, 011D01, 011301'
};
var imageNDVI = newImageCol.select("NDVI").mosaic().clip(bjGeo);
Map.addLayer(imageNDVI, visParamNDVI, "Layer_NDVI");
代碼鏈接:
https://engine.piesat.cn/engine-share/shareCode.html?id=ef81bdf97c8d45809c3101f5f8dee4c4
上述代碼采用map算子和for循環的計算時間基本相同, 但在使用for循環時要重新構造一個存放新的image集合的數組,并在循環結束后重新構造一個由這些image組成的ImageCollection,在步驟上較為復雜。
因此,在遙感云計算平臺中,涉及到對集合中的每個元素進行操作時,推薦使用map算子,快速且便利。但前述提到,在map算子中不宜再用 print、Map、Export 以及Reducer等方法,如果需要對集合中的每個元素進行統計操作,則可用for循環代替,以下代碼展示了使用for循環統計北京各城區提取的建筑區面積:
向上滑動閱覽
var chn = pie.FeatureCollection('NGCC/CHINA_PROVINCE_BOUNDARY');
var beijing = chn.filter(pie.Filter.eq("name", "北京市"));
var bjGeo = beijing.getAt(0).geometry();
var chn2 = pie.FeatureCollection('NGCC/CHINA_COUNTY_BOUNDARY');
var beijing2 = chn2.filter(pie.Filter.eq("cname", "北京市"));
var bjGeo2 = beijing2.getAt(0).geometry();
Map.centerObject(beijing, 7)
var visParams = { color: "ff0000ff", fillColor: "00000000" };
Map.addLayer(beijing, visParams, "北京市邊界");
Map.addLayer(beijing2, visParams, "北京市各區");
function rmCloud(image) {
var qa = image.select("BQA");
var cloudMask = qa.bitwiseAnd(1
return image.updateMask(cloudMask);
}
var l8 = pie.ImageCollection("LC08/01/T1")
.filterDate("2020-7-1", "2020-8-30")
.filterBounds(bjGeo)
.select(["B2", "B3", "B4", "B5", "B6", "BQA"])
.map(rmCloud);
//計算用到的指數
function imgCalculate(image) {
var green = image.select("B3");
var red = image.select("B4");
var nir = image.select("B5");
var swir1 = image.select("B6");
var ndvi = (nir.subtract(red)).divide(nir.add(red)).rename("NDVI");
var mndwi = green.subtract(swir1)
.divide(green.add(swir1))
.rename("MNDWI");
return image.addBands(ndvi).addBands(mndwi);
}
var imgCol = l8.map(imgCalculate)
.mean()
.clip(bjGeo);
Map.addLayer(imgCol, { min: 0, max: 3000, bands: ["B4", "B3", "B2"] }, "imgCol");
var ndvi = imgCol.select("NDVI");
var nonVeg = imgCol.updateMask(ndvi.lte(0.6));
Map.addLayer(nonVeg, { min: 0, max: 3000, bands: ["B4", "B3", "B2"] }, "nonVeg");
var mndwi = imgCol.select("MNDWI");
var nonVegWater = nonVeg.updateMask(mndwi.lte(0.3));
Map.addLayer(nonVegWater, { min: 0, max: 3000, bands: ["B4", "B3", "B2"] }, "nonVegWater");
var nonVegWater2 = nonVegWater.select("B2").gt(0);
Map.addLayer(nonVegWater2, { min: 0, max: 1, palette: "ff0000" }, "nonVegWater2");
var areaImage = nonVegWater2.pixelArea().multiply(nonVegWater2.gt(0));
var area = areaImage.reduceRegion(pie.Reducer.sum(), bjGeo, 30);
print("北京市建筑用地面積(單位:平方千米): ", pie.Number(area.get("constant")).divide(1000000));
var newFeatures = [];
for (var i = 0; i
var temp = nonVegWater2.clip(beijing2.getAt(i).geometry());
var areaImg = temp.pixelArea().multiply(temp.gt(0));
var area = areaImg.reduceRegion(pie.Reducer.sum(), beijing2.getAt(i).geometry(), 30);
newFeatures.push(beijing2.getAt(i).set("area", area.get("constant")));
}
beijing2 = pie.FeatureCollection(newFeatures);
print("beijing2",beijing2);
var result = beijing2.reduceColumns(pie.Reducer.toList(2), ["name", "area"]);
print("result", result);
代碼鏈接:
https://engine.piesat.cn/engine-share/shareCode.html?id=bf885222bf584f33aaa96c2a0093c03d
輸出結果:
三、巧用map算子,提高計算效率
map算子帶來便利的同時,也將大量的計算轉移給了后臺計算服務。通常我們在影像處理時會計算多個指數,單個指數的計算公式通常會寫成函數的形式方便調用。多個map算子共同使用時,相當于多次循環,所涉及到的運算量也很大,怎樣使用map算子才能優化計算邏輯,提高計算效率呢?
下面以去云后計算裸土指數BSI、改進的歸一化差異水體指數MNDWI、增強型的裸土指數EBSI、建筑用地指數NDBI等4個指數的計算為例,來說明如何在代碼級別進行優化來減少計算量從而提高計算效率。
方法1,將每個指數的計算單獨寫成函數,在篩選完影像后依次用map算子調用:
向上滑動閱覽
var chn = pie.FeatureCollection('NGCC/CHINA_PROVINCE_BOUNDARY');
var beijing = chn.filter(pie.Filter.eq("name", "北京市"));
var bjGeo = beijing.getAt(0).geometry();
Map.centerObject(beijing, 7)
var visParams = { color: "ff0000ff", fillColor: "00000000" };
Map.addLayer(beijing, visParams, "北京市邊界");
//去云
function rmCloud(image) {
var qa = image.select("BQA");
var cloudMask = qa.bitwiseAnd(1
return image.updateMask(cloudMask);
}
//裸土指數BSI: [(B06 + B04)-(B05 + B02)]/[(B06 + B04)+(B05 + B02)]
function BSI(image) {
var nir = image.select("B5");
var swir1 = image.select("B6");
var red = image.select("B4");
var blue = image.select("B2");
var bsi = (swir1.add(red).subtract(nir.add(blue)))
.divide(swir1.add(red).add(nir.add(blue)));
return image.addBands(bsi.rename("BSI"));
}
//改進的歸一化差異水體指數MNDWI: (B03 - B06)/(B03 + B06)
function MNDWI(image) {
var mndwi = image.select("B3").subtract(image.select("B6"))
.divide(image.select("B3").add(image.select("B6")))
return image.addBands(mndwi.rename("MNDWI"));
}
//增強型的裸土指數EBSI:(BSI - MNDWI)/(BSI + MNDWI)
function EBSI(image) {
var mndwi = image.select("MNDWI");
var bsi = image.select("BSI");
var ebsi = (bsi.subtract(mndwi)).divide(bsi.add(mndwi));
return image.addBands(ebsi.rename("EBSI"));
}
//建筑用地指數NDBI: (B06 - B05)/(B06 + B05)
function NDBI(image) {
var ndbi = image.select("B6").subtract(image.select("B5"))
.divide(image.select("B6").add(image.select("B5")))
return image.addBands(ndbi.rename("NDBI"));
}
//加載Landsat 8影像數據集合
var imgColl8 = pie.ImageCollection("LC08/01/T1")
.filterDate("2020-4-1", "2020-4-30")
.filterBounds(bjGeo)
.select(["B2", "B3", "B4", "B5", "B6", "B7", "BQA"])
.map(rmCloud)
.map(BSI)
.map(MNDWI)
.map(EBSI)
.map(NDBI)
.mean()
.clip(bjGeo);
print("imgColl8",imgColl8);
Map.addLayer(imgColl8, { min: 0, max: 3000, bands: ["B4", "B3", "B2"] }, "imgCol1");
Map.addLayer(imgColl8.select("BSI"), { min: -0.2, max: 0.2 }, "BSI");
Map.addLayer(imgColl8.select("NDBI"), { min: -0.2, max: 0.2 }, "NDBI");
Map.addLayer(imgColl8.select("MNDWI"), , "MNDWI");
Map.addLayer(imgColl8.select("EBSI"), { min: 0, max: 1 }, "EBSI");
代碼鏈接:
https://engine.piesat.cn/engine-share/shareCode.html?id=402cadc45985400c8650620b9d93bad9
上述代碼共調用了5次map算子,相當于對篩選后的影像進行了5次遍歷,除去云操作外,每一次都是對每景影像進行波段運算完后添加計算后的指數,將大量的算力消耗在了重復讀取和寫入上,輸出計算結果用時85秒,完整顯示用時135秒。
依次調用多個map計算每個指數
方法2,將去云及指數運算封裝成一個函數,在篩選完影像后僅用map算子調用一次:
向上滑動閱覽
var chn = pie.FeatureCollection('NGCC/CHINA_PROVINCE_BOUNDARY');
var beijing = chn.filter(pie.Filter.eq("name", "北京市"));
var bjGeo = beijing.getAt(0).geometry();
Map.centerObject(beijing, 7)
var visParams = { color: "ff0000ff", fillColor: "00000000" };
Map.addLayer(beijing, visParams, "北京市邊界");
var imgColl8 = pie.ImageCollection("LC08/01/T1")
.filterDate("2020-4-1", "2020-4-30")
.filterBounds(bjGeo)
.select(["B2", "B3", "B4", "B5", "B6", "B7", "BQA"]);
//影像處理函數
function imgCalculate(image) {
var qa = image.select("BQA");
var cloudMask = qa.bitwiseAnd(1
image = image.updateMask(cloudMask);
var blue = image.select("B2");
var green = image.select("B3");
var red = image.select("B4");
var nir = image.select("B5");
var swir1 = image.select("B6");
var bsi = (swir1.add(red).subtract(nir.add(blue)))
.divide(swir1.add(red).add(nir.add(blue)))
.rename("BSI");
var mndwi = green.subtract(swir1)
.divide(green.add(swir1))
.rename("MNDWI");
var ebsi = (bsi.subtract(mndwi)).divide(bsi.add(mndwi))
.rename("EBSI");
var ndbi = swir1.subtract(nir)
.divide(swir1.add(nir))
.rename("NDBI");
return image.addBands(mndwi).addBands(bsi).addBands(ebsi)
.addBands(ndbi);
}
imgColl8 = imgColl8.map(imgCalculate).mean().clip(bjGeo);
print("imgColl8", imgColl8);
Map.addLayer(imgColl8, { min: 0, max: 3000, bands: ["B4", "B3", "B2"] }, "imgCol1");
Map.addLayer(imgColl8.select("BSI"), { min: -0.2, max: 0.2 }, "BSI");
Map.addLayer(imgColl8.select("NDBI"), { min: -0.2, max: 0.2 }, "NDBI");
Map.addLayer(imgColl8.select("MNDWI"), , "MNDWI");
Map.addLayer(imgColl8.select("EBSI"), { min: 0, max: 1 }, "EBSI");
代碼鏈接:
https://engine.piesat.cn/engine-share/shareCode.html?id=ce08fdb1cca147acb6ae6da7629fb49f
將指數計算封裝到一個函數中調用
上述代碼僅調用了1次map算子,對篩選后的影像進行了去云操作以及4個指數波段的添加,僅需對每景影像讀取寫入一次,大大節省了算力,只用8秒即可輸出結果并完整顯示,相比于方法1,計算速度提升近10倍。
學會了這種思路后,平常我們在編寫代碼時,注意將計算過程優化,就可以提高計算效率,減少等待時間。
本文地址:http://www.dayishuiji.com/new/17613.html - 轉載請保留原文鏈接。免責聲明:本文轉載上述內容出于傳遞更多信息之目的,不代表本網的觀點和立場,故本網對其真實性不負責,也不構成任何其他建議;本網站圖片,文字之類版權申明,因為網站可以由注冊用戶自行上傳圖片或文字,本網站無法鑒別所上傳圖片或文字的知識版權,如果侵犯,請及時通知我們,本網站將在第一時間及時刪除。 |