Google Earth Engine – How to Split Categorical Image into Multi-Band Image Using GEE

google-earth-enginegoogle-earth-engine-javascript-apimaskingmulti-band

Context

I need to calculate the yearly forest area lost for every country, based on the Hansen Tree Cover Loss (TCL) image. The band I use is categorical and indicates the lossyear in the form of an integer (e.g. a pixel that was deforested in 2005 would show the value 5). I identified 3 steps to reach my goal:

  1. Mask the TCL layer with every year
  2. Calculate the pixel area of pixel that were deforested in a certain year
  3. Sum up the calculated pixel areas on a country level

I am looking for a simple way to tackle step 1: Splitting the lossyear band of the TCL layer into separate bands, where every band contains only pixel of a certain year (e.g. 1 band with only pixels with the value one, …, one band with only pixels of year 21).

What I tired

The code tackling steps 1 and 2 outlined above (aggregation to country needs to be added in the form of ReduceRegions and applied to every year).

var lossyears = ee.List.sequence(1, 21)
var gfw_tcl = ee.Image("UMD/hansen/global_forest_change_2021_v1_9")

//////////////////////// FUNCTIONS ////////////////////////

// Mask TCL image with year value and turn that value to 1 to be
// subsequently multiplied with the area, see https://gis.stackexchange.com/questions/359852/how-do-i-extract-pixels-of-specific-values-from-a-raster-in-google-earth-engine
var maskYear = function(year) {
  var gfw_tcl_lossyear = gfw_tcl.select('lossyear')
  return gfw_tcl_lossyear.updateMask(
  gfw_tcl_lossyear
  .eq(year))
  .remap({from: [year], to: [1], bandName: 'lossyear'})
  .rename('forestloss');
};


// Calculate area of pixels in image in hectares [ha]
var pixelArea = function(image) {
  return image.multiply(ee.Image.pixelArea()).divide(10000);
};


// Calculate area deforested in ha [ha]
var yearlyAreaDeforested = function(year) {
  var yearTCL = maskYear(year);
  return pixelArea(yearTCL);
};


//////////////////////// MAIN ////////////////////////

var yad = lossyears.map(yearlyAreaDeforested);

The code yields the following error: List (Error) Image.eq, argument 'image2': Invalid type. Expected type: Image. Actual type: Float. Actual value: 1.0

The question

How can I achieve splitting a categorical image into bands where every band contains only values from one category, without a for-loop?

I am also open to different approaches if what I try to do does not make sense within the strict division of client-server side in Google Earth Engine.

Best Answer

You're close, you just need to cast the number that comes into your mapped function into an Image (because it's too late for the client to figure that out for you.

var maskYear = function(year) {
  var gfw_tcl_lossyear = gfw_tcl.select('lossyear')
  return gfw_tcl_lossyear.updateMask(
  gfw_tcl_lossyear
  .eq(ee.Image.constant(year)))
  .remap({from: [year], to: [1], bandName: 'lossyear'})
  .rename('forestloss');
};

But you can do it all in one go with this:

maskYear = gfw_tcl.select("lossyear").eq(ee.Image.constant(lossyears))

Each band of that image will be 1 for the given year, and 0 everywhere else. You can multiply that with the pixelArea image directly (or you can self-mask it if you want the 0s to go away for some reason).

Related Question