Exploring the Global 30m Land Cover Change Dataset (1985-2022) GLC_FCS30D

A temporally consistent global multi-class time-series classification dataset is critical to understand and quantify long-term changes. Till now, the choices were limited to lower resolution datasets such as MODIS Landcover (2000-present) at 500m resolution or ESA CCI (1992-present) at 300m resolution. We now have a new dataset GLC_FCS30D that provides a high-resolution landcover time-series derived from the Landsat archive (1984-2022) at 30m resolution with 35 classes. This is a very valuable dataset for studying landscape dynamics at high resolution and the first of its kind to be available made available in the public domain. The source dataset was released on Zenodo and can be downloaded as GeoTIFF files. This data is also available in the Google Earth Engine Community catalog and can be used within GEE directly. In this post, I want to share some technical details and scripts to help you analyze this data using Google Earth Engine. You will learn

  • How to access and pre-process the GLC_FCS30D dataset.
  • How to visualize and compare landcover changes between 1985-2022.
  • How to calculate landcover statistics and export a CSV with areas of each class for the entire time series over multiple regions.

1. Pre-processing the Data

The original dataset was produced in tiles of 5° x 5° tiles – with each image having bands for each year of classification. This is the same structure of the dataset that was uploaded to the Earth Engine Community Catalog. There are two separate dataset for the five-yearly classifications (1985-90, 1990-95 and 1995-2000) and yearly classifications (2000-2022). Most workflows in GEE are structured around ImageCollections – and not multiband images. So it is useful to transform this original data into an ImageCollection with some modifications as below.

  • Merge the tiles into a global mosaic
  • Convert the multi-band images into ImageCollections
  • Merge the five-yearly and yearly images into a single ImageCollection
  • Reclassify the pixels to sequential class values

Making these changes gives us a single ImageCollection with global classification images from 1985-2022 that is much easier to work with.

Here’s the Earth Engine code for the pre-processing step which can be accessed from this script.

// Example script showing how to pre-process the GLC_FCS30D
// landcover dataset to create an ImageCollection with yearly
// landcover images and recoded class values

// Yearly data from 2000-2022
var annual = ee.ImageCollection(
  'projects/sat-io/open-datasets/GLC-FCS30D/annual');
// Five-Yearly data for 1985-90, 1990-95 and 1995-2000
var fiveyear = ee.ImageCollection(
  'projects/sat-io/open-datasets/GLC-FCS30D/five-years-map');

// The classification scheme has 36 classes 
// (35 landcover class and 1 fill value)
var classValues = [
  10, 11, 12, 20, 51, 52, 61, 62, 71, 72, 81, 82, 91, 92, 120, 121, 122, 
  130, 140, 150, 152, 153, 181, 182, 183, 184, 185, 186, 187, 190, 200, 
  201, 202, 210, 220, 0
];
// Landcover class names
// We removes spaces and special characters from the names
var classNames = [
  'Rainfed_cropland',
  'Herbaceous_cover_cropland',
  'Tree_or_shrub_cover_cropland',
  'Irrigated_cropland',
  'Open_evergreen_broadleaved_forest',
  'Closed_evergreen_broadleaved_forest',
  'Open_deciduous_broadleaved_forest',
  'Closed_deciduous_broadleaved_forest',
  'Open_evergreen_needle_leaved_forest',
  'Closed_evergreen_needle_leaved_forest',
  'Open_deciduous_needle_leaved_forest',
  'Closed_deciduous_needle_leaved_forest',
  'Open_mixed_leaf_forest',
  'Closed_mixed_leaf_forest',
  'Shrubland',
  'Evergreen_shrubland',
  'Deciduous_shrubland',
  'Grassland',
  'Lichens_and_mosses',
  'Sparse_vegetation',
  'Sparse_shrubland',
  'Sparse_herbaceous',
  'Swamp',
  'Marsh',
  'Flooded_flat',
  'Saline',
  'Mangrove',
  'Salt_marsh',
  'Tidal_flat',
  'Impervious_surfaces',
  'Bare_areas',
  'Consolidated_bare_areas',
  'Unconsolidated_bare_areas',
  'Water_body',
  'Permanent_ice_and_snow',
  'Filled_value'
];

var classColors = [
  '#ffff64', '#ffff64', '#ffff00', '#aaf0f0', '#4c7300',
  '#006400', '#a8c800', '#00a000', '#005000', '#003c00',
  '#286400', '#285000', '#a0b432', '#788200', '#966400', 
  '#964b00', '#966400', '#ffb432', '#ffdcd2', '#ffebaf', 
  '#ffd278', '#ffebaf', '#00a884', '#73ffdf', '#9ebb3b', 
  '#828282', '#f57ab6', '#66cdab', '#444f89', '#c31400', 
  '#fff5d7', '#dcdcdc', '#fff5d7', '#0046c8', '#ffffff',
  '#ffffff'
];
  
// The data is split into tiles
// Mosaic them into a single image
var annualMosaic = annual.mosaic();
var fiveYearMosaic = fiveyear.mosaic();

// Each image in five year image 3 bands,
// one for each year from 1985-90, 1990-95 and 1995-2000

// Create a list of year strings
var fiveYearsList = ee.List.sequence(1985, 1995, 5).map(function(year) {
  return ee.Number(year).format('%04d');
});
var fiveyearMosaicRenamed = fiveYearMosaic.rename(fiveYearsList);

// Each image in annual image 23 bands, one for each year from 2000-2022
// Rename bands from b1,b2.. to 2000,2001... etc.
var yearsList = ee.List.sequence(2000, 2022).map(function(year) {
  return ee.Number(year).format('%04d')
})
var annualMosaicRenamed = annualMosaic.rename(yearsList);

var years = fiveYearsList.cat(yearsList);

// Turn the multiband image to a ImageCollection
var fiveYearlyMosaics = fiveYearsList.map(function(year) {
  var date = ee.Date.fromYMD(ee.Number.parse(year), 1, 1);
  return fiveyearMosaicRenamed.select([year]).set({
    'system:time_start': date.millis(),
    'system:index': year,
    'year': ee.Number.parse(year)
  });
});

var yearlyMosaics = yearsList.map(function(year) {
  var date = ee.Date.fromYMD(ee.Number.parse(year), 1, 1);
  return annualMosaicRenamed.select([year]).set({
    'system:time_start': date.millis(),
    'system:index': year,
    'year': ee.Number.parse(year)
  });
});

var allMosaics = fiveYearlyMosaics.cat(yearlyMosaics);
var mosaicsCol = ee.ImageCollection.fromImages(allMosaics);

// For ease of visualization and analysis, it is recommended
// to recode the class values into sequential values
// We use remap() to convert the original values into new values
// from 1 to 36
var newClassValues = ee.List.sequence(1, ee.List(classValues).length());

var renameClasses = function(image) {
  var reclassified = image.remap(classValues, newClassValues)
    .rename('classification');
  return reclassified;
};

var landcoverCol = mosaicsCol.map(renameClasses);

print('Pre-processed Collection', landcoverCol);

// Visualize the data
var year = 2022;
var selectedLandcover = landcoverCol
  .filter(ee.Filter.eq('year', year)).first();

var palette = [
  '#ffff64', '#ffff64', '#ffff00', '#aaf0f0', '#4c7300', '#006400', '#a8c800', '#00a000', 
  '#005000', '#003c00', '#286400', '#285000', '#a0b432', '#788200', '#966400', '#964b00', 
  '#966400', '#ffb432', '#ffdcd2', '#ffebaf', '#ffd278', '#ffebaf', '#00a884', '#73ffdf', 
  '#9ebb3b', '#828282', '#f57ab6', '#66cdab', '#444f89', '#c31400', '#fff5d7', '#dcdcdc', 
  '#fff5d7', '#0046c8', '#ffffff', '#ffffff'
];
var classVisParams = {min:1, max:36, palette: palette};
Map.addLayer(selectedLandcover, classVisParams, 'Landcover ' + year);

2. Visualizing Changes using a Split-panel App

A useful way to visualize a landcover time-series is through a user interface that allows us to compare and contrast data for multiple years. Using a split-panel, we can load classifications for 2 different years and swipe to see changes between them. We create a split panel interface with a dropdown selector allowing you to change the year and visualize the changes. To make the map interpretation easier, we also construct a legend. Since the legend has 36 entries – we make it a 2-column legend and have a checkbox to control the visibility.

You can explore the app at https://spatialthoughts.projects.earthengine.app/view/global-landcover-change-explorer

The source code for the app is available below and can be accessed from this script.

// Example script for an App to explore GLC_FCS30D
// landcover dataset using a split-panel

// Pre-process the Collection
// Yearly data from 2000-2022
var annual = ee.ImageCollection(
  'projects/sat-io/open-datasets/GLC-FCS30D/annual');
// Five-Yearly data for 1985-90, 1990-95 and 1995-2000
var fiveyear = ee.ImageCollection(
  'projects/sat-io/open-datasets/GLC-FCS30D/five-years-map');

// The classification scheme has 36 classes 
// (35 landcover class and 1 fill value)
var classValues = [
  10, 11, 12, 20, 51, 52, 61, 62, 71, 72, 81, 82, 91, 92, 120, 121, 122, 
  130, 140, 150, 152, 153, 181, 182, 183, 184, 185, 186, 187, 190, 200, 
  201, 202, 210, 220, 0
];
// Landcover class names
// We removes spaces and special characters from the names
var classNames = [
  'Rainfed_cropland',
  'Herbaceous_cover_cropland',
  'Tree_or_shrub_cover_cropland',
  'Irrigated_cropland',
  'Open_evergreen_broadleaved_forest',
  'Closed_evergreen_broadleaved_forest',
  'Open_deciduous_broadleaved_forest',
  'Closed_deciduous_broadleaved_forest',
  'Open_evergreen_needle_leaved_forest',
  'Closed_evergreen_needle_leaved_forest',
  'Open_deciduous_needle_leaved_forest',
  'Closed_deciduous_needle_leaved_forest',
  'Open_mixed_leaf_forest',
  'Closed_mixed_leaf_forest',
  'Shrubland',
  'Evergreen_shrubland',
  'Deciduous_shrubland',
  'Grassland',
  'Lichens_and_mosses',
  'Sparse_vegetation',
  'Sparse_shrubland',
  'Sparse_herbaceous',
  'Swamp',
  'Marsh',
  'Flooded_flat',
  'Saline',
  'Mangrove',
  'Salt_marsh',
  'Tidal_flat',
  'Impervious_surfaces',
  'Bare_areas',
  'Consolidated_bare_areas',
  'Unconsolidated_bare_areas',
  'Water_body',
  'Permanent_ice_and_snow',
  'Filled_value'
];

var classColors = [
  '#ffff64', '#ffff64', '#ffff00', '#aaf0f0', '#4c7300',
  '#006400', '#a8c800', '#00a000', '#005000', '#003c00',
  '#286400', '#285000', '#a0b432', '#788200', '#966400', 
  '#964b00', '#966400', '#ffb432', '#ffdcd2', '#ffebaf', 
  '#ffd278', '#ffebaf', '#00a884', '#73ffdf', '#9ebb3b', 
  '#828282', '#f57ab6', '#66cdab', '#444f89', '#c31400', 
  '#fff5d7', '#dcdcdc', '#fff5d7', '#0046c8', '#ffffff',
  '#ffffff'
];
  
// The data is split into tiles
// Mosaic them into a single image
var annualMosaic = annual.mosaic();
var fiveYearMosaic = fiveyear.mosaic();

// Each image in five year image 3 bands,
// one for each year from 1985-90, 1990-95 and 1995-2000

// Create a list of year strings
var fiveYearsList = ee.List.sequence(1985, 1995, 5).map(function(year) {
  return ee.Number(year).format('%04d');
});
var fiveyearMosaicRenamed = fiveYearMosaic.rename(fiveYearsList);

// Each image in annual image 23 bands, one for each year from 2000-2022
// Rename bands from b1,b2.. to 2000,2001... etc.
var yearsList = ee.List.sequence(2000, 2022).map(function(year) {
  return ee.Number(year).format('%04d')
})
var annualMosaicRenamed = annualMosaic.rename(yearsList);

var years = fiveYearsList.cat(yearsList);

// Turn the multiband image to a ImageCollection
var fiveYearlyMosaics = fiveYearsList.map(function(year) {
  var date = ee.Date.fromYMD(ee.Number.parse(year), 1, 1);
  return fiveyearMosaicRenamed.select([year]).set({
    'system:time_start': date.millis(),
    'system:index': year,
    'year': ee.Number.parse(year)
  });
});

var yearlyMosaics = yearsList.map(function(year) {
  var date = ee.Date.fromYMD(ee.Number.parse(year), 1, 1);
  return annualMosaicRenamed.select([year]).set({
    'system:time_start': date.millis(),
    'system:index': year,
    'year': ee.Number.parse(year)
  });
});

var allMosaics = fiveYearlyMosaics.cat(yearlyMosaics);
var mosaicsCol = ee.ImageCollection.fromImages(allMosaics);

// For ease of visualization and analysis, it is recommended
// to recode the class values into sequential values
// We use remap() to convert the original values into new values
// from 1 to 36
var newClassValues = ee.List.sequence(1, ee.List(classValues).length());

var renameClasses = function(image) {
  var reclassified = image.remap(classValues, newClassValues)
    .rename('classification');
  return reclassified;
};

var landcoverCol = mosaicsCol.map(renameClasses);

print('Pre-processed Collection', landcoverCol);


// ***************************************************
// Split-panel App
// ***************************************************

// Create the layout
var center = {lon:77.57, lat:12.95, zoom:10};

var leftMap = ui.Map(center);
var rightMap = ui.Map(center);

var linker = new ui.Map.Linker([leftMap, rightMap]);

// Create a split panel with the two maps.
var splitPanel = ui.SplitPanel({
  firstPanel: leftMap,
  secondPanel: rightMap,
  orientation: 'horizontal',
  wipe: true
});

// Add dropdown year selectors
var leftYearSelector = ui.Select({
  style: {
    'fontSize': '20px',
    'backgroundColor': '#f7f7f7',
    'position':'middle-left'
  },
});

var rightYearSelector = ui.Select({
  style: {
    'fontSize': '20px',
    'backgroundColor': '#f7f7f7',
    'position':'middle-right'
  },
});

leftMap.add(leftYearSelector);
rightMap.add(rightYearSelector);


// Add a 2-column Legend
var legend = ui.Panel({
  style: {
    position: 'bottom-right',
    padding: '8px 15px',
    backgroundColor: '#f0f0f0'
  }
});

// Add a checkbox to show/hide legend
var showLegend = ui.Checkbox({
  label: 'Show Legend',
  
  style: {
    fontSize: '10px',
    backgroundColor: '#f0f0f0',
  },
});
legend.add(showLegend);

var column1 = ui.Panel({
  style: {
    padding: '8px 15px',
    backgroundColor: '#f0f0f0'
  }
});

var column2 = ui.Panel({
  style: {
    padding: '8px 15px',
    backgroundColor: '#f0f0f0'
  }
});

var columns = ui.Panel({
  layout: ui.Panel.Layout.Flow('horizontal'),
   style: {
    backgroundColor: '#f0f0f0'
  }
});
columns.add(column1);
columns.add(column2);

// Creates and styles 1 row of the legend.
var makeRow = function(color, name) {
  // Create the label that is actually the colored box.
  var colorBox = ui.Label({
    style: {
      backgroundColor: color,
      // Use padding to give the box height and width.
      padding: '8px',
      margin: '0 0 4px 0'
    }
  });
  
  // Create the label filled with the description text.
  var description = ui.Label({
    value: name,
    style: {
      margin: '0 0 4px 6px',
      backgroundColor: '#f0f0f0'
    }
  });
  
  return ui.Panel({
    widgets: [colorBox, description],
    layout: ui.Panel.Layout.Flow('horizontal'),
    style: {backgroundColor: '#f0f0f0'}
  });
};

var totalItems = classNames.length;
for (var i = 0; i < totalItems/2; i++) {
  column1.add(makeRow(classColors[i], classNames[i]));
}
for (var i = totalItems/2; i < totalItems; i++) {
  column2.add(makeRow(classColors[i], classNames[i]));
}
rightMap.add(legend);

// Configure the widgets
years.evaluate(function(yearsList) {
  leftYearSelector.items().reset(yearsList);
  rightYearSelector.items().reset(yearsList);
  // Set the value to the first year
  leftYearSelector.setValue(yearsList[0]);
  // Set the value to the last year
  rightYearSelector.setValue(yearsList[yearsList.length -1]);
});

// Function to get a layer for the selected year
function getLayer(year) {
  var selectedLandcover = landcoverCol
    .filter(ee.Filter.eq('year', ee.Number.parse(year))).first();


  var classVisParams = {min:1, max:36, palette: classColors};
  var layer = ui.Map.Layer(
    selectedLandcover, classVisParams, 'Landcover ' + year);
  return layer;
}

function setLeftLayer(year) {
  var layer = getLayer(year);
  leftMap.layers().reset([]);
  leftMap.add(layer);
}

function setRightLayer(year) {
  var layer = getLayer(year);
  rightMap.layers().reset([]);
  rightMap.add(layer);
}

leftYearSelector.onChange(setLeftLayer);
rightYearSelector.onChange(setRightLayer);

function changeLegendVisibility(value) {
  if (value) {
    legend.add(columns);

  }
  else {
    legend.remove(columns);
  }
}
showLegend.onChange(changeLegendVisibility);


ui.root.clear();
ui.root.add(splitPanel);

3. Calculating and Exporting Class Areas

A landcover time-series allows us to quantify change by calculating and comparing areas of different classes over time. We can leverage the massive computation capabilities of GEE by using map() to calculate class areas for multiple regions over multiple years and generate a single CSV file with all the statistics.

I have made a detailed step-by-step video guide on writing efficient code to calculate areas from the landcover dataset. You may watch this video series to learn how to write and modify code for such analysis.

The sample code provided here calculates the area of each of 35 landcover classes for all years from 1985-2022 for multiple admin regions and exports the results as a CSV file.

The code for this analysis is shown below and can be accessed from this script.

// **************************************************************
// Example script showing how to extract yearly class statistics
// from the GLC_FCS30D landcover dataset from 1985-2022

// The script shows how to compute areas 
// for each of 35 landcover classes
// for each year from 1982 to 2002
// for multiple admin2 region

// Code is adapted from 
// https://spatialthoughts.com/2020/06/19/calculating-area-gee/
// Video explanation at 
// https://www.youtube.com/playlist?list=PLppGmFLhQ1HLl0St2wiOPePr58sKu0Vh1
// **************************************************************

// Use GAUL Admin2 collection
var admin2 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level2');

// Replace with your chosen region
// Add 'admin2' to the map and inspect for names for your region
var admin0Name = 'India';
var admin1Name = 'Karnataka';

var adminFiltered = admin2
  .filter(ee.Filter.eq('ADM0_NAME', admin0Name))
  .filter(ee.Filter.eq('ADM1_NAME', admin1Name));

// Use the GLC_FCS30D collection
// https://gee-community-catalog.org/projects/glc_fcs/
// Pre-process the Collection
// Yearly data from 2000-2022
var annual = ee.ImageCollection(
  'projects/sat-io/open-datasets/GLC-FCS30D/annual');
// Five-Yearly data for 1985-90, 1990-95 and 1995-2000
var fiveyear = ee.ImageCollection(
  'projects/sat-io/open-datasets/GLC-FCS30D/five-years-map');

// The classification scheme has 36 classes 
// (35 landcover class and 1 fill value)
var classValues = [
  10, 11, 12, 20, 51, 52, 61, 62, 71, 72, 81, 82, 91, 92, 120, 121, 122, 
  130, 140, 150, 152, 153, 181, 182, 183, 184, 185, 186, 187, 190, 200, 
  201, 202, 210, 220, 0
];
// Landcover class names
// We removes spaces and special characters from the names
var classNames = [
  'Rainfed_cropland',
  'Herbaceous_cover_cropland',
  'Tree_or_shrub_cover_cropland',
  'Irrigated_cropland',
  'Open_evergreen_broadleaved_forest',
  'Closed_evergreen_broadleaved_forest',
  'Open_deciduous_broadleaved_forest',
  'Closed_deciduous_broadleaved_forest',
  'Open_evergreen_needle_leaved_forest',
  'Closed_evergreen_needle_leaved_forest',
  'Open_deciduous_needle_leaved_forest',
  'Closed_deciduous_needle_leaved_forest',
  'Open_mixed_leaf_forest',
  'Closed_mixed_leaf_forest',
  'Shrubland',
  'Evergreen_shrubland',
  'Deciduous_shrubland',
  'Grassland',
  'Lichens_and_mosses',
  'Sparse_vegetation',
  'Sparse_shrubland',
  'Sparse_herbaceous',
  'Swamp',
  'Marsh',
  'Flooded_flat',
  'Saline',
  'Mangrove',
  'Salt_marsh',
  'Tidal_flat',
  'Impervious_surfaces',
  'Bare_areas',
  'Consolidated_bare_areas',
  'Unconsolidated_bare_areas',
  'Water_body',
  'Permanent_ice_and_snow',
  'Filled_value'
];

var classColors = [
  '#ffff64', '#ffff64', '#ffff00', '#aaf0f0', '#4c7300',
  '#006400', '#a8c800', '#00a000', '#005000', '#003c00',
  '#286400', '#285000', '#a0b432', '#788200', '#966400', 
  '#964b00', '#966400', '#ffb432', '#ffdcd2', '#ffebaf', 
  '#ffd278', '#ffebaf', '#00a884', '#73ffdf', '#9ebb3b', 
  '#828282', '#f57ab6', '#66cdab', '#444f89', '#c31400', 
  '#fff5d7', '#dcdcdc', '#fff5d7', '#0046c8', '#ffffff',
  '#ffffff'
];
  
// The data is split into tiles
// Mosaic them into a single image
var annualMosaic = annual.mosaic();
var fiveYearMosaic = fiveyear.mosaic();

// Each image in five year image 3 bands,
// one for each year from 1985-90, 1990-95 and 1995-2000

// Create a list of year strings
var fiveYearsList = ee.List.sequence(1985, 1995, 5).map(function(year) {
  return ee.Number(year).format('%04d');
});
var fiveyearMosaicRenamed = fiveYearMosaic.rename(fiveYearsList);

// Each image in annual image 23 bands, one for each year from 2000-2022
// Rename bands from b1,b2.. to 2000,2001... etc.
var yearsList = ee.List.sequence(2000, 2022).map(function(year) {
  return ee.Number(year).format('%04d')
})
var annualMosaicRenamed = annualMosaic.rename(yearsList);

var years = fiveYearsList.cat(yearsList);

// Turn the multiband image to a ImageCollection
var fiveYearlyMosaics = fiveYearsList.map(function(year) {
  var date = ee.Date.fromYMD(ee.Number.parse(year), 1, 1);
  return fiveyearMosaicRenamed.select([year]).set({
    'system:time_start': date.millis(),
    'system:index': year,
    'year': ee.Number.parse(year)
  });
});

var yearlyMosaics = yearsList.map(function(year) {
  var date = ee.Date.fromYMD(ee.Number.parse(year), 1, 1);
  return annualMosaicRenamed.select([year]).set({
    'system:time_start': date.millis(),
    'system:index': year,
    'year': ee.Number.parse(year)
  });
});

var allMosaics = fiveYearlyMosaics.cat(yearlyMosaics);
var mosaicsCol = ee.ImageCollection.fromImages(allMosaics);

// For ease of visualization and analysis, it is recommended
// to recode the class values into sequential values
// We use remap() to convert the original values into new values
// from 1 to 36
var newClassValues = ee.List.sequence(1, ee.List(classValues).length());

var renameClasses = function(image) {
  var reclassified = image.remap(classValues, newClassValues)
    .rename('classification');
  return reclassified;
};

var landcoverCol = mosaicsCol.map(renameClasses);

print('Pre-processed Collection', landcoverCol);

// Calculate class statistics

// Define a class value to class name dictionary
var newClassValues = ee.List.sequence(1, ee.List(classValues).length());
var newClassValuesStrings = newClassValues.map(function(classValue) {
  return ee.Number(classValue).format('%02d');
});
var classDict = ee.Dictionary.fromLists(newClassValuesStrings, classNames);

// Write a function to calculate class areas for each image
var calculateClassArea = function(classified) {
  var year = classified.get('year');

  var areasFc = ee.Image.pixelArea().addBands(classified)
    .reduceRegions({
      collection: adminFiltered,
      reducer: ee.Reducer.sum().group({
        groupField: 1,
        groupName: 'class',
        }),
      scale: 30,
      tileScale: 16
    });
  // We get a FeatureCollection with group statistics
  // Extract the group statistics
  var areaFcProcessed = areasFc.map(function(feature) {
    
    var classAreas = ee.List(feature.get('groups'));
    var classAreaLists = classAreas.map(function(item) {
      var areaDict = ee.Dictionary(item);
      var classNumber = ee.Number(
        areaDict.get('class')).format('%02d');
      var className = classDict.get(classNumber);
      var areaSqKm = ee.Number(
        areaDict.get('sum')).divide(1e6).format('%.2f'); // round to 2 decimals
      return ee.List([className, areaSqKm]);
    });
 
    var classAreaValues = ee.Dictionary(classAreaLists.flatten());
    var properties = feature.toDictionary();
    // List of original properties to keep
    var propertiesToKeep = ['ADM1_NAME', 'ADM2_NAME'];
    // We add 'year' and the class area properties
    var newProperties = properties.select(propertiesToKeep)
      .combine(classAreaValues)
      .set('year', year);
    return ee.Feature(feature.geometry(), newProperties);
  });
  return areaFcProcessed;
};

// map() the function on the collection
var landcoverStats = landcoverCol
  .map(calculateClassArea)
  .flatten();

// Prepare the collection for Export

// Generate a list of properties for all classes
// This needs to be a client-side (javascript) list
var outputFields = ['ADM1_NAME', 'ADM2_NAME', 'year'].concat(classNames);

Export.table.toDrive({
  collection: landcoverStats,
  description: 'class_area_by_region_by_year',
  folder: 'earthengine',
  fileNamePrefix: 'class_area_by_region_by_year',
  fileFormat: 'CSV',
  selectors: outputFields
});

The result will be a CSV file such as shown below.

I encourage you to read the original paper referenced below to understand the methodology and limitations. If you end up using this dataset, I would love to hear your feedback on the quality of the data for your study region.

Zhang, X., Zhao, T., Xu, H., Liu, W., Wang, J., Chen, X., and Liu, L.: GLC_FCS30D: the first global 30 m land-cover dynamics monitoring product with a fine classification system for the period from 1985 to 2022 generated using dense-time-series Landsat imagery and the continuous change-detection method, Earth Syst. Sci. Data, 16, 1353–1381, https://doi.org/10.5194/essd-16-1353-2024, 2024.

15 Comments

Leave a Comment

  1. What an amazing workflow. Made a lot of difficulties much easier. Thanks to the data providers and Ujaval. How great would it be if a personal data for 2024 can be included in the image collection!

  2. Hi, I am grateful for this wonderful LULC classification. This will be a great help for me. I would like to find out whether this classification will continue from 2023 onward. Thank you.

    • Yes. Just export the result “landcoverStats’ in required format instead of CSV. In the Export.table.toDrive() function use GeoJSON or SHP as the format and remove the “selectors” parameter

  3. Hello there, is it possible that this LCC of yours can be used to integrated into MOLUSCE plugin for projection of future land changes ?

  4. Hello……I need to create point samples for 7 land cover classes from the years 2000 to 2010. I want to download the land cover maps for each year clipped to my study area, but I’m unsure how to properly download these annual maps for my region.

Leave a Reply to Adrian MonteclaroCancel reply