Filter pixel values from a list in every Image in an ImageCollection in Google Earth Engine

filtergoogle-earth-engineimageland-use

The context

I am using the MODIS Land Cover image collection ("MODIS/061/MCD12Q1") to calculate land use change area over 20 years. I am using the "LC_Type1" classification and I am interested to check between two adjacent years (e.g., 2001 and 2002) if a class changed from [1,..,11] to [12, 14] (forest to cropland). I identified four steps:

  1. Create an image collection with only MODIS LC_Type1 classes [1,..11]

  2. Create an image collection with only MODIS LC_Type1 classes [12, 14]

  3. Identify the overlap of (1) for year i and (2) for year i+1 with i [1,..,21] (e.g., compare if any classes between 2001 and 2002 changed from forest to cropland)

  4. Calculate the pixel area of the pixels that overlap

The question

How do I filter all images of an image collection for the band "LC_Type1" to show only values that are either in the lists [1,..11] or [12, 14]?

What I tried

// load MODIS image collection and select LC_Type1 bands only
var modisLandCover = ee.ImageCollection("MODIS/061/MCD12Q1");
var LCType1 = modisLandCover.select('LC_Type1')

// create list of high carbon stock (HCS) and land use change (LUC) classes
var HCSClasses = ee.List.sequence(1,11);
var LUCClasses = ee.List([12, 14]);

//// Unsuccessful tries

// Try 0: Filtering whole list of values
var test0 = LCType1filter(ee.Filter.inList(HCSClasses))

// Try 1: On whole collection
var test1 = LCType1.eq(1) // throws error: LCType1.eq is not a function

// Try 2: On an arbitrary date
var test2 = LCType1.filterDate('2005').eq(1) // throws error LCType1.filterDate(...).eq is not a function

// Try 3: Cast Try 2 to image
var test3 = ee.Image(LCType1.filterDate('2005')).eq(1) // throws error although I casted to image: Image.eq, argument 'image1': Invalid type. Expected type: Image<unknown bands>. Actual type: ImageCollection.

// Try 4: Based on https://gis.stackexchange.com/questions/267794/filtering-a-raster-asset-using-list-of-values-in-google-earth-engine
var test4 = LCType1.updateMask(LCType1.eq(ee.Image.constant(HCSClasses)).reduce(ee.Reducer.anyNonZero())) // throws error: LCType1.eq is not a function

// EDIT Try 5: Mapping function across feature collection
var maskFunction = function(image) {
  var mask = image.inList(HCSClasses);
  return image.updateMask(mask);
};

var test5 = LCType1.map(maskFunction); // throws error: image.inList is not a function

Further remarks

I would be happy to receive also suggestions for steps (3) and (4) or an overall other approach for my goal if the current approach is not ideal. For suggestions to steps (3) and (4) I could create a separate question on SO.

EDITS

  • Added Try 5 to code snippet

Best Answer

If you want to check the change between two consecutive years, there is an alternative approach. The images have 17 class, but you only need two of them, so in the first place you have to reclassify. Then you can select the image from each year with the reclassified band. Finally, you can get change pixels by applying decision rules to both images:

// load MODIS image collection and select LC_Type1 bands only
var modisLandCover = ee.ImageCollection("MODIS/061/MCD12Q1");
var LCType1 = modisLandCover.select('LC_Type1')

// reclassify values to have only 3 bins
var reclass_function = function(image) {
  // Select band and reclassify
  var LC_Type1 = image.select("LC_Type1");
  var type1bins = LC_Type1
    .where(LC_Type1.lt(12), 1) // Forest
    .where(LC_Type1.gte(12), 2) // Cropland
    .where(LC_Type1.gt(14), 3); // Other
  return image.addBands(type1bins.rename('bin')); // Add new band with the tree bins
};

var reclassified = LCType1.map(reclass_function);

// Filter consecutive years
var year1= reclassified.filter(ee.Filter.calendarRange(2018,2018,'year')).first();
var year2= reclassified.filter(ee.Filter.calendarRange(2019,2019,'year')).first();

// select "bin" layer
var year1bin = year1.select('bin');
var year2bin = year2.select('bin');

// Get change pixels applying a rule
var forest2cropland = year1bin.eq(1).and(year2bin.eq(2));

// Image visualization -> Red pixels are those with Change
var changeVis = {min: 0, max: 1, palette: ['white', 'red']};
Map.addLayer(forest2cropland, changeVis, 'Forest to cropland');

// Another visualization to check changes
var igbpLandCoverVis = {bands:"LC_Type1",   min: 1.0,  max: 17.0,
  palette: [    '05450a', '086a10', '54a708', '78d203', '009900', 'c6b044', 'dcd159',
    'dade48', 'fbff13', 'b6ff05', '27ff87', 'c24f44', 'a5a5a5', 'ff6d4c',
    '69fff8', 'f9ffa4', '1c0dff'
  ],
};

// Year 1, year 2 and change pixels
Map.addLayer(year1, igbpLandCoverVis, 'Year1 Land Cover');
Map.addLayer(year2, igbpLandCoverVis, 'Year2 Land Cover');
Map.addLayer(forest2cropland.selfMask(), changeVis, 'Forest to cropland (Masked)');