I am currently trying to adapt the scan line correction (SLC) for Landsat 7 on my timeseries. My code is based on the stack exchange question how to do the SLC, but I am not managing to apply it as a loop.
For the SLC-function, there are always two images of the ImageCollection needed, one as a source and the other one to fill the gaps induced by the SL failure. I know how to map over an ImageCollection, but I do not understand, how to always also cast the next image in the collection.
So if my Collection has 6 images, I want to apply this sequence to my loop, and always get an image as a source and the next image in line as the filling.
var list = ee.List.sequence(0,5);
var corrected = ee.ImageCollection.fromImages(
list.map(function (i) {
var source = ee.Image(listOfImages.get(i))
var fill = ee.Image(listOfImages.get(i.add(1)))
var result = GapFill(source, fill, 10, true);
return ee.Image(result);
}));
How can get the next image in a loop? Obviously .add or a + is not working.
Here is the full code
var area = ee.Geometry.Polygon(
[[[35.715690058580556, -6.013865604001415],
[35.715690058580556, -6.190699019077679],
[35.971122187486806, -6.190699019077679],
[35.971122187486806, -6.013865604001415]]], null, false);
// Set study area as map center.
Map.centerObject(area,11);
// Create an empty image into which to paint the features, cast to byte.
var empty = ee.Image().byte();
// Paint all the polygon edges with the same number and width, display.
var outline = empty.paint({
featureCollection: area,
color: 1,
width: 3
});
Map.addLayer(outline, {palette: '000000'}, 'Kiteto & Kongwa');
///////////////////////////////////////////////////////////////////////////////////7
/* Apply the USGS L7 Phase-2 Gap filling protocol, using a single kernel size. */
///////////////////////////////////////////////////////////////////////////////////7
var MIN_SCALE = 1/3;
var MAX_SCALE = 3;
var MIN_NEIGHBORS = 144;
var GapFill = function(src, fill, kernelSize, upscale) {
var kernel = ee.Kernel.square(kernelSize * 30, "meters", false)
// Find the pixels common to both scenes.
var common = src.mask().and(fill.mask())
var fc = fill.updateMask(common)
var sc = src.updateMask(common)
//Map.addLayer(common.select(0).mask(common.select(0)), {palette:['000000']}, 'common mask (both exist)', false)
// Find the primary scaling factors with a regression.
// Interleave the bands for the regression. This assumes the bands have the same names.
var regress = fc.addBands(sc)
regress = regress.select(regress.bandNames().sort())
print(regress,'regress')
var ratio = 5
if(upscale) {
var fit = regress
.reduceResolution(ee.Reducer.median(), false, 500)
.reproject(regress.select(0).projection().scale(ratio, ratio))
.reduceNeighborhood(ee.Reducer.linearFit().forEach(src.bandNames()), kernel, null, false)
.unmask()
.reproject(regress.select(0).projection().scale(ratio, ratio))
} else {
var fit = regress
.reduceNeighborhood(ee.Reducer.linearFit().forEach(src.bandNames()), kernel, null, false)
}
var offset = fit.select(".*_offset")
var scale = fit.select(".*_scale")
//Map.addLayer(scale.select('Blue_scale'), {min:-2, max:2}, 'scale B1', false)
// Find the secondary scaling factors using just means and stddev
var Reducer = ee.Reducer.mean().combine(ee.Reducer.stdDev(), null, true)
if(upscale) {
var src_stats = src
.reduceResolution(ee.Reducer.median(), false, 500)
.reproject(regress.select(0).projection().scale(ratio, ratio))
.reduceNeighborhood(Reducer, kernel, null, false)
.reproject(regress.select(0).projection().scale(ratio, ratio))
var fill_stats = fill
.reduceResolution(ee.Reducer.median(), false, 500)
.reproject(regress.select(0).projection().scale(ratio, ratio))
.reduceNeighborhood(Reducer, kernel, null, false)
.reproject(regress.select(0).projection().scale(ratio, ratio))
} else {
var src_stats = src
.reduceNeighborhood(Reducer, kernel, null, false)
var fill_stats = fill
.reduceNeighborhood(Reducer, kernel, null, false)
}
var scale2 = src_stats.select(".*stdDev").divide(fill_stats.select(".*stdDev"))
var offset2 = src_stats.select(".*mean").subtract(fill_stats.select(".*mean").multiply(scale2))
var invalid = scale.lt(MIN_SCALE).or(scale.gt(MAX_SCALE))
// Map.addLayer(invalid.select(0).mask(invalid.select(0)), {palette:['550000']}, 'invalid1', false)
scale = scale.where(invalid, scale2)
offset = offset.where(invalid, offset2)
// When all else fails, just use the difference of means as an offset.
var invalid2 = scale.lt(MIN_SCALE).or(scale.gt(MAX_SCALE))
// Map.addLayer(invalid2.select(0).mask(invalid2.select(0)), {palette:['552020']}, 'invalid2', false)
scale = scale.where(invalid2, 1)
offset = offset.where(invalid2, src_stats.select(".*mean").subtract(fill_stats.select(".*mean")))
// Apply the scaling and mask off pixels that didn't have enough neighbors.
var count = common.reduceNeighborhood(ee.Reducer.count(), kernel, null, true, "boxcar")
var scaled = fill.multiply(scale).add(offset)
.updateMask(count.gte(MIN_NEIGHBORS))
return src.unmask(scaled, true)
}
//////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Define coefficients supplied by Roy et al. (2016) for translating ETM+
// surface reflectance to OLI surface reflectance.
// Define function to get and rename bands of interest from ETM+.
function renameETM(img) {
return img.select(
['B1', 'B2', 'B3', 'B4', 'B5', 'B7', 'pixel_qa'],
['Blue', 'Green', 'Red', 'NIR', 'SWIR1', 'SWIR2', 'pixel_qa']
);
}
// Define function to mask out clouds and cloud shadows.
function fmask(img) {
var cloudShadowBitMask = 1 << 3;
var cloudsBitMask = 1 << 5;
var qa = img.select('pixel_qa');
var mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0)
.and(qa.bitwiseAnd(cloudsBitMask).eq(0));
return img.updateMask(mask);
}
// Define function to prepare ETM+ images.
function prepETM(img) {
var orig = img;
img = renameETM(img);
img = fmask(img);
return ee.Image(img.copyProperties(orig, orig.propertyNames()));
}
// ################################################################
// ### APPLICATION ###
// ################################################################
var etmCol = ee.ImageCollection('LANDSAT/LE07/C01/T1_SR')
.filterDate('2003-05-30', '2005-05-30')
// Define a collection filter.
var colFilter = ee.Filter.and(
ee.Filter.bounds(area),
ee.Filter.lt('CLOUD_COVER', 05),
ee.Filter.lt('GEOMETRIC_RMSE_MODEL', 10),
ee.Filter.or(
ee.Filter.eq('IMAGE_QUALITY', 9),
ee.Filter.eq('IMAGE_QUALITY_OLI', 9)
));
// Filter collections and prepare them for merging
var l7images = etmCol.filter(colFilter).map(prepETM);
print(l7images,'Landsat 7');
var listOfImages = l7images.toList(l7images.size());
//Example of how the SLC works
var source = ee.Image(listOfImages.get(0))
var fill = ee.Image(listOfImages.get(1))
Map.addLayer(fill, {min:400, max:2500, gamma: 2.222 , bands:["Red", "Green", "Blue"]}, "1", true)
Map.addLayer(source, {min:400, max:2500, gamma: 2.222 , bands:["Red", "Green", "Blue"]}, "2", true)
var result = GapFill(source, fill, 10, false);
Map.addLayer(result.clip(area), {min:400, max:2500, gamma: 2.222, bands:["Red", "Green", "Blue"]}, "filled", true)
var list = ee.List.sequence(0,5);
var corrected = ee.ImageCollection.fromImages(
list.map(function (i) {
var source = ee.Image(listOfImages.get(i))
var fill = ee.Image(listOfImages.get(i.add(1)))
var result = GapFill(source, fill, 10, true);
return ee.Image(result);
}));