0

I am new at Google Earth Engine and I want to fill null pixels of an image collection with pixels of complete images. In my case I want to make that in a loop, in which one the first image of a list fill the nulls of the second image and, the resulting image completed, will fill the nulls of the 3er image, and so on. I tried to make it with a iterate function but it didn't work well.

//Crear mascara para eliminar datos de nubes
function maskS2clouds(image) {
  var qa = image.select('QA60');

  // Bits 10 and 11 are clouds and cirrus, respectively.
  var cloudBitMask = 1 << 10;
  var cirrusBitMask = 1 << 11;

  // Both flags should be set to zero, indicating clear conditions.
  var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
            .and(qa.bitwiseAnd(cirrusBitMask).eq(0));

  return image.updateMask(mask).divide(10000);
}

//Introducir shape de cuenca hidrográfica del jucar para delimitar imagenes
var RellenoSE = ee.Image().byte();
var LimiteSE = RellenoSE.paint({featureCollection: SEjucar, width: 5,});
Map.addLayer(LimiteSE, {palette: 'red',opacity:0.5},'SE jucar');

//Filtrado de imagenes por diferentes parámetros
var imagenA =ee.ImageCollection('COPERNICUS/S2_SR') 
              .select('B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12', 'AOT', 'WVP', 'SCL', 'TCI_R', 'TCI_G', 'TCI_B', 'QA10', 'QA20', 'QA60')
              .filterBounds(geometry)
              .filterDate('2018-01-20', '2018-02-20')
              .filterMetadata('CLOUDY_PIXEL_PERCENTAGE','less_than',70)
              .filter(ee.Filter.eq('SENSING_ORBIT_NUMBER',94))
              .map(maskS2clouds)
              .map(function(image){return image.clip(SEjucar)});
print (imagenA, 'Imagen_filtrada');
//Map.addLayer(imagenA, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'ImagenA');

//Obtener imagenes como una lista de imagenes
var imgList = imagenA.toList (999)
//print ('ListOfImages',imgList)

//Relleno de la primera imagen vacia con una anterior a las fechas de estudio
var imagen_vieja = ee.ImageCollection('COPERNICUS/S2_SR') 
              .select('B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B9', 'B11', 'B12', 'AOT', 'WVP', 'SCL', 'TCI_R', 'TCI_G', 'TCI_B', 'QA10', 'QA20', 'QA60')
              .filterBounds(geometry)
              .filterDate('2017-12-04', '2017-12-06')
              .filterMetadata('CLOUDY_PIXEL_PERCENTAGE','less_than',70)
              .filter(ee.Filter.eq('SENSING_ORBIT_NUMBER',94))
              .map(maskS2clouds)
              .map(function(image){return image.clip(SEjucar)});
var imgList2 = imagen_vieja.toList (999);
//var prueba_imagen_vieja = ee.Image(ee.List(imgList2).get(0));
//Map.addLayer(prueba_imagen_vieja, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'Imagen vieja');
var imagen_madre = ee.Image(ee.List(imgList).get(0)).unmask(ee.Image(ee.List(imgList2).get(0)));
//Map.addLayer(imagen_madre, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'Imagen madre');

//Unir 1ª imagen ya rellena a la lista de imagenes que pasarán por el bucle
var imgList_vacia = imgList.remove(ee.Image(ee.List(imgList).get(0)))
//print (imgList_vacia,'lista con 3 img')

var imagen_bucle = imgList_vacia.insert(0,imagen_madre)
print (imagen_bucle,'Lista_imagenes_bucle')

//Realizar bucle para rellenar pixeles de una imagen incompleta con los pixeles de la imagen anterior
  
  //Creacion bucle con iterate para rellenar imagenes
  var inicial = ee.Image(ee.List(imagen_bucle).get(0));
  function fillnullclouds (current, previous){
    var LastImage = ee.Image(ee.List(imagen_bucle).get(0-1));
    var Updated = ee.Image(ee.List(imagen_bucle.get(0+1)));
    var fill = Updated.unmask(LastImage);

    return ee.List(previous).add(Updated)
  }
  
  var imagen_filled = ee.List(imagen_bucle.iterate(fillnullclouds,ee.List([inicial])));
    print (imagen_filled, 'imagen rellena')

  var imagenCollection_filled = ee.ImageCollection (imagen_filled);
  print (imagenCollection_filled, 'imagenCollection rellena')
  
  
  
  //Prueba representación imagen por imagen
  //var prueba_separada = ee.ImageCollection (prueba).toList(999);
  //print ('ListOfImagesFilled',prueba_separada);
  var prueba_imagen0 = ee.Image(ee.List(imagen_filled).get(0));
  var prueba_imagen1 = ee.Image(ee.List(imagen_filled).get(1));
  var prueba_imagen2 = ee.Image(ee.List(imagen_filled).get(2));
  var prueba_imagen3 = ee.Image(ee.List(imagen_filled).get(3));
  Map.addLayer(prueba_imagen0, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'ImagenA Filled0');
  Map.addLayer(prueba_imagen1, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'ImagenA Filled1');
  Map.addLayer(prueba_imagen2, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'ImagenA Filled2');
  Map.addLayer(prueba_imagen3, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'ImagenA Filled3');
//Prueba visualizacion de las imagenes; Saber cuales son completas y cual no, 
//para poder comparar y saber si la función esta funcionando o no
var imagen00 = ee.Image(ee.List(imgList).get(0));
var imagen11 = ee.Image(ee.List(imgList).get(1));
var imagen22 = ee.Image(ee.List(imgList).get(2));
var imagen33 = ee.Image(ee.List(imgList).get(3));
Map.addLayer(imagen00, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'Imagen00');
Map.addLayer(imagen11, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'Imagen11');
Map.addLayer(imagen22, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'Imagen22');
Map.addLayer(imagen33, {bands: ['B4', 'B3', 'B2'], min: 0, max: 4000}, 'Imagen33');

1 Answer 1

1

I didn't follow exactly how you wanted this to work. Are you looking for the pixel value closest to some "target date", or perhaps you want a pixel from your first (2018-01-20 to 2018-02-20) date range, and fall back to a pixel from the second (2017-12-04 to 2017-12-06) in case pixels are masked?

Never the less, I think you should have a look at ee.ImageCollection.mosaic() and ee.ImageCollection.qualityMosaic(). They probably can be helpful in your implementation.

If you want the pixel closest to a "target date" you can do like this:

var targetDate = ee.Date('2018-02-01')
var closestedToTarget = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
  .filterMetadata('CLOUDY_PIXEL_PERCENTAGE', 'less_than', 70)
  .filterBounds(geometry)
  .filter(ee.Filter.or(
    ee.Filter.date('2018-01-20', '2018-02-20'),
    ee.Filter.date('2017-12-04', '2017-12-06')
  ))
  .map(function (image) {
    var daysFromTarget = image.date().difference(targetDate, 'days').abs().multiply(-1)
    return image
      .addBands(
        ee.Image(daysFromTarget).int16().rename('daysFromTarget')
      )
      .updateMask(image.select('QA60').not())
  })
  .qualityMosaic('daysFromTarget')

https://code.earthengine.google.com/e22dcd522b86c0d119e9a0ecc9a0313f

The problem with this is that you are very much reliant on the cloud mask. Another approach is to create median composites for a number of date ranges. Then start to pick pixels from your "preferred" ranges.

var dateRanges = [ // We will pick valid pixels based on the order
  ['2018-01-20', '2018-02-20'],
  ['2017-12-04', '2017-12-06'],
  ['2018-12-04', '2018-12-06']
]

var composite = ee.ImageCollection(ee.List(dateRanges).reverse()
  .map(function (dateRange) {
    return createComposite(ee.List(dateRange))
  })
).mosaic() // Uses the last non-masked pixel in the collection

function createComposite(dateRange) {
  return ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
    .filterMetadata('CLOUDY_PIXEL_PERCENTAGE', 'less_than', 70)
    .filterBounds(geometry)
    .filterDate(dateRange.getString(0), dateRange.getString(1))
    .map(function (image) {
      return image
        .updateMask(image.select('QA60').not())
    })
    .median()
}

https://code.earthengine.google.com/d30d09c60d644ccdf29b4f8e57d9ed0d

This example still look terrible. The date ranges are short and cloudy, and the QA60 band is pretty bad. You can improve the cloud masking by using the COPERNICUS/S2_CLOUD_PROBABILITY collection. It slows down the processing, but works a lot better:

var dateRanges = [ // We will pick valid pixels based on the order
  ['2018-01-20', '2018-02-20'],
  ['2017-12-04', '2017-12-06'],
  ['2018-12-04', '2018-12-06']
]
var cloudThreshold = 30

var composite = ee.ImageCollection(ee.List(dateRanges).reverse()
  .map(function (dateRange) {
    return createComposite(ee.List(dateRange))
  })
).mosaic() // Uses the last non-masked pixel in the collection

function createComposite(dateRange) {
  var filter = ee.Filter.and(
      ee.Filter.bounds(geometry),
      ee.Filter.date(dateRange.getString(0), dateRange.getString(1))
  )
  return ee.ImageCollection(
      ee.Join.saveFirst('cloudProbability').apply({
          primary: ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
            .filterMetadata('CLOUDY_PIXEL_PERCENTAGE', 'less_than', 70)
            .filter(filter),
          secondary: ee.ImageCollection('COPERNICUS/S2_CLOUD_PROBABILITY').filter(filter),
          condition: ee.Filter.equals({leftField: 'system:index', rightField: 'system:index'})
      })
  ).map(function (image) {
    var cloudFree = ee.Image(image.get('cloudProbability')).lt(cloudThreshold)
    return image
      .updateMask(cloudFree)
  }).median()
}

https://code.earthengine.google.com/657996ccba8fc1533b8945ece5006146

For the area I ran the script on, it still looks bad, though significantly better then the other approaches. It will work better for less cloudy areas and/or for longer date ranges, where the median composites contain less cloud/haze/cloud shadow/noise.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.