本篇主要介绍如何加载单幅Sentinel2影像,以及合成长时间序列感兴趣区域的影像集合,自己在写代码的过程中,出现了一些问题,比如导出的影像会以多幅tif影像出现,导出的影像在Arcgis中加载全为黑色,也看了一些博主的博客,有的解释很简单,在这我会详细解释一下,让大家少走弯路(被我走完了)。
遇到问题的同学可以直接跳转到第三部分。
此代码只对感兴趣区域,并辅以时间范围进行影像筛选,使用了经典的Sentinel去云算法,利用QA60波段进行去云操作,并结合了高程数据,将坡度坡向波段加入到影像的波段中。
//导入自己的感兴趣区域
Map.centerObject(roi, 8);
Map.addLayer(roi, {color: "red"}, "roi");
//去云函数
function maskS2clouds(image) {
var qa = image.select('QA60');
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
.and(qa.bitwiseAnd(cirrusBitMask).eq(0));
return image.updateMask(mask).divide(10000);
}
var dataset = ee.ImageCollection('COPERNICUS/S2')
.filterBounds(roi)
.filterDate('2018-04-01', '2018-05-30')
// Pre-filter to get less cloudy granules.
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10))
.map(maskS2clouds);
//加入高程数据
var srtm = ee.Image("USGS/SRTMGL1_003");
var dem = ee.Algorithms.Terrain(srtm);
var elevation = dem.select("elevation");
var slope = dem.select("slope");
var s2Image=dataset.median()
.addBands(elevation.rename("ELEVATION"))
.addBands(slope.rename("SLOPE"))
.clip(roi)
.select(['B4', 'B3', 'B2']);
var rgbVis = {
min: 0.0,
max: 0.3,
bands: ['B4', 'B3', 'B2'],
};
print(s2Image)
Map.addLayer(s2Image, rgbVis, 'RGB');
//导出影像
Export.image.toDrive({
image:s2Image,
description:'sentinel2',
region: roi,
scale:10,
crs: "EPSG:4326",
maxPixels:1e13
});
在GEE中进行影像显示,显示结果符合预期。
在这里其实我并没有导出全部感兴趣区域的影像,而是选择了一个小范围进行导出,可以发现导出的结果可以正常显示。
在影像导出时还可能会出现一些问题,如果导出的感兴趣区域太大的话,可能会加载很慢,而且会出现导出结果为多幅tif影像,在第三部分会进行解释。
合成长时间序列影像其实就是合成单幅影像向时间范围的推广,可以为进行长时间序列研究提供源数据支持。
以前写过合成landsat影像长时间序列的代码,然后移植到sentinel2影像上时,会发生一些问题,于是进行了一些改进与修改。
var S2SR = ee.ImageCollection('COPERNICUS/S2');
var rawLayer = null;
var bands = ['B4', 'B3', 'B2'];
//融合DEM高程数据
var srtm = ee.Image("USGS/SRTMGL1_003");
var dem = ee.Algorithms.Terrain(srtm);
var elevation = dem.select("elevation");
var slope = dem.select("slope");
//展示影像集合,侧边栏
function addPanel(sCol) {
var id_list = sCol.reduceColumns(ee.Reducer.toList(), ['system:index'])
.get('list');
id_list.evaluate(function(ids) {
// print("id_list ", ids);
var total = ids.length;
var showTitle = ui.Label("", {fontWeight: 'bold'});
var curIndex = 0;
var bPlus = ui.Button("+", function() {
curIndex += 1;
if (curIndex >= total) {
curIndex = 0;
}
showTitle.setValue(ids[curIndex]);
showSelectRawImage(sCol, ids[curIndex]);
});
var bReduce = ui.Button("-", function() {
curIndex -= 1;
if (curIndex < 0) {
curIndex = total - 1;
}
showTitle.setValue(ids[curIndex]);
showSelectRawImage(sCol, ids[curIndex]);
});
showTitle.setValue(ids[curIndex]);
showSelectRawImage(sCol, ids[curIndex]);
var main = ui.Panel({
widgets: [
ui.Label('click "+" or "-" to move time window', {fontWeight: 'bold'}),
bPlus, bReduce,
ui.Label("select date: ", {fontWeight: 'bold'}),
showTitle
],
style: {width: '200px', padding: '8px'}
});
ui.root.insert(0, main);
});
}
function showSelectRawImage(sCol, key) {
print("show raw image id is: " + key);
if (rawLayer !== null) {
Map.remove(rawLayer);
rawLayer = null;
}
var visParam = {
min: 0.0,
max: 0.3,
bands: ['B4', 'B3', 'B2'],
};
var image = ee.Image(sCol.filter(ee.Filter.eq("system:index", key)).first());
print(image);
// Map.addLayer(image, {'bands':["B4","B3","B2"],min:0,max:3000}, key);
rawLayer = Map.addLayer(image, {'bands':["B4","B3","B2"],min:0,max:0.3}, key);
}
//构建哨兵影像集合
var Sentinel2 = {
//利用QA60波段去云操作
maskS2clouds: function(image){
var qa = image.select('QA60');
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
.and(qa.bitwiseAnd(cirrusBitMask).eq(0));
return image.updateMask(mask).divide(10000);
},
}
//每年固定日期影像合成
function getYearCol(sDate, eDate) {
var yearList = ee.List.sequence(ee.Date(sDate).get("year"), ee.Number(ee.Date(eDate).get("year")).subtract(1));
// var monthList = ee.List.sequence(5, 6);
var yearImgList = yearList.map(function(year) {
year = ee.Number(year);
var _sdate = ee.Date.fromYMD(year, 4, 1);
var _edate = ee.Date.fromYMD(year, 6, 30);
//影像筛选
var tempCol = S2SR.filterDate(_sdate, _edate)
.filterBounds(roi)
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10))
.map(Sentinel2.maskS2clouds)
// .select(bands);
//影像中值合成
var img = tempCol.median()
.clip(roi)
.addBands(elevation.rename("ELEVATION"))
.addBands(slope.rename("SLOPE"));
img = img.set("year", year);
img = img.set("system:index", ee.String(year.toInt()));
return img;
});
var yearImgCol = ee.ImageCollection.fromImages(yearImgList);
return yearImgCol;
}
//导出所有影像
function exportImageCollection(imgCol) {
var indexList = imgCol.reduceColumns(ee.Reducer.toList(), ["system:index"])
.get("list");
indexList.evaluate(function(indexs) {
for (var i=0; i<indexs.length; i++) {
var image = imgCol.filter(ee.Filter.eq("system:index", indexs[i])).first();
image = image.toInt16();
Export.image.toDrive({
image: image.clip(roi),
description: "s2"+indexs[i],
fileNamePrefix: indexs[i],
region: roi,
scale: 10,
crs: "EPSG:4326",
maxPixels: 1e13
});
}
});
}
function main(){
Map.centerObject(roi, 8);
var sDate = "2017-1-1";
var eDate = "2022-1-1";
var imgCol = getYearCol(sDate, eDate);
print("imgCol", imgCol);
exportImageCollection(imgCol)
addPanel(imgCol);
}
main();
运行结果图如下所示,本代码生成了2017-2018年4,5,6三个月的中值合成影像,在侧边栏加入了用户交互界面,并可将结果按年份导出。
1.有的同学会发现导出到Drive中的影像会出现多个tif影像文件,并且加载到arcgis中时影像全为黑色,其实打开属性表,会发现波段的值全是未知,所以该影像根本没有波段值,怎么拉伸都不会出现影像。可能是因为导出的影像波段数量过多,导致影像内存过于庞大,GEE会自动将其分为多个文件进行存储,如果网不好,还可能会有波段信息丢失的情况,所以在进行影像导出的时候,先进行波段选择,会大概率避免这种情况。
2. 影像全为黑色还有另一种情况,再进行landsat影像合成的时候,一般都会把landsat影像的波段值乘上0.0001,但是sentinel数据不需要进行波段拉伸,不然就会造成波段值过小,会自动忽略不计,导致波段值全为空,影像自然为黑色。
以下代码为罪魁祸首,在合成哨兵数据时千万不要带着他:
scaleImage: function(image) {
var time_start = image.get("system:time_start");
image = image.multiply(0.0001);
image = image.set("system:time_start", time_start);
return image;
},