Cloud Masking Example Scripts

In the GEE repository for this workshop, you will find a folder called cloudMasking_examples containing some scripts with example cloud masking scripts called HLS_cloudMasking, Planet_cloudMasking, and Sentinel_Landsat_cloudMasking. You can use these scripts to compare various cloud masking techniques with the available optical imagery.

Sentinel and Landsat

// setup
// -------------------------------------------------------------------

// threshold for percent cloud cover in a single scene
var cloudCoverThreshold = 80

// dates of interest
var d1 = '2018-1-1'
var d2 = '2018-12-31'

// center the map on the AOI
Map.centerObject(geometry, 7);

// imagery
// -------------------------------------------------------------------

// import cloud score +
var csPlus = ee.ImageCollection('GOOGLE/CLOUD_SCORE_PLUS/V1/S2_HARMONIZED')
  .filterDate(d1, d2)
  .filterBounds(geometry)

// import sentinel 2
var S2collection = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
  // filter for dates of interest
  .filterDate(d1, d2)
  // filter for images that intersect with the AOI
  .filterBounds(geometry)
  // link the collection to the Cloud Score + collection
  .linkCollection(csPlus, ['cs_cdf'])
  // filter for images with low cloud cover
  .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE',cloudCoverThreshold))
  // mask clouds using the functions defined at the end of the script
  .map(maskS2cloudsCS)
  .map(maskS2clouds)
  // sort by cloud cover
  .sort('CLOUDY_PIXEL_PERCENTAGE',false)
  
// set visualization parameters
var S2vis = {
  min: 0.0,
  max: 0.3,
  bands: ['B4', 'B3', 'B2'],
};

// Map.setCenter(83.277, 17.7009, 12);

Map.addLayer(S2collection.median().clip(geometry), S2vis, 'Sentinel 2');
// Map.addLayer(TOA, visualization, 'TOA');

/////////////////////////////////////////////////////////////////////////////////////////

var L8collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
    .filterDate(d1,d2)
    .filterBounds(geometry)
    .filter(ee.Filter.lt('CLOUD_COVER', cloudCoverThreshold))
    .map(maskL8clouds)
    .map(applyScaleFactors)
    .sort('CLOUD_COVER', false) 

var L8vis = {
  bands: ['SR_B4', 'SR_B3', 'SR_B2'],
  min: 0.0,
  max: 0.3,
};

Map.addLayer(L8collection.median().clip(geometry), L8vis, 'Landsat 8');

// functions
// -------------------------------------------------------------------

// mask clouds from Sentinel-2 images using QA band
function maskS2clouds(image) {
  // define the quality score band to use
  var qa = image.select('QA60');
  // select the bits of interest
  // Bits 10 and 11 are clouds and cirrus
  var cloudBitMask = 1 << 10;
  var cirrusBitMask = 1 << 11;
  // create a mask where values are 0
  var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
      .and(qa.bitwiseAnd(cirrusBitMask).eq(0));
      // Apply mask
  return image.updateMask(mask).divide(10000);
}

// mask clouds from Sentinel-2 images using Cloud Score +
function maskS2cloudsCS(image) {
  // select the quality score band
  var qa = image.select('cs_cdf');
  // create a mask with pixels with high quality scores
  var mask = qa.gte(0.80)
  // Apply mask
  return image.updateMask(mask);
}

// mask clouds from Landsat-8 images using QA band
function maskL8clouds(image) {
  // define the quality score band to use
  var qaBand = image.select('QA_PIXEL');
  // select the bits of interest
  // Bits 1,2,3,4 are cloud-related 
  // The << operator is the "bitwise shift" operator, shifting 1 to the left by the specified number of bits
  var dilatedCloudBitMask = 1 << 1;  // 1 shifted left by 1 bit  = 00000010 = 2  (dilated clouds)
  var cirrusBitMask = 1 << 2;        // 1 shifted left by 2 bits = 00000100 = 4  (cirrus clouds)
  var cloudBitMask = 1 << 3;         // 1 shifted left by 3 bits = 00001000 = 8  (clouds)
  var cloudShadowBitMask = 1 << 4;   // 1 shifted left by 4 bits = 00010000 = 16 (cloud shadow)
  // create a mask where values are 0
  var mask = qaBand.bitwiseAnd(dilatedCloudBitMask).eq(0)
            .and(qaBand.bitwiseAnd(cirrusBitMask).eq(0))
            .and(qaBand.bitwiseAnd(cloudBitMask).eq(0))
            .and(qaBand.bitwiseAnd(cloudShadowBitMask).eq(0));
  // Apply mask
  return image.updateMask(mask);
}

// Apply scaling factors to Landsat-8 images
function applyScaleFactors(image) {
  // sclae optical bands
  var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
  // scale thermal bands
  var thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0);
  // return new bands
  return image.addBands(opticalBands, null, true)
              .addBands(thermalBands, null, true);
}

Harmonized Landsat Sentinel

// setup
// -------------------------------------------------------------------

// threshold for percent cloud cover in a single scene
var cloudCoverThreshold = 100

// dates of interest
var d1 = '2016-1-1'
var d2 = '2016-12-31'

// center the map on the AOI
Map.centerObject(geometry, 7);

// imagery
// -------------------------------------------------------------------

// import Landsat HLS
var landsatHLS = ee.ImageCollection("NASA/HLS/HLSL30/v002")
  // filter for images that intersect with the AOI
  .filterBounds(geometry)
  // filter for dates of interest
  .filter(ee.Filter.date(d1, d2))
  // filter for images with low cloud cover
  .filter(ee.Filter.lt('CLOUD_COVERAGE',cloudCoverThreshold))
  // mask clouds using the function defined at the end of the script
  .map(maskHSLclouds)
  // sort by cloud cover
  .sort('CLOUD_COVERAGE', false)
  // select and rename bands
  .select(['B2',    'B3',    'B4',    'B5',    'B6',    'B7',   'Fmask'], 
          ['blue',  'green', 'red',   'NIR',   'SWIR1', 'SWIR2','Fmask']);

// add the median composite to the map
Map.addLayer(landsatHLS.median().clip(geometry), {
  bands: ['red', 'green', 'blue'],
  min:0.01,
  max:0.18,
}, 'Landsat HLS');

// import Sentinel HLS
var sentinelHLS = ee.ImageCollection("NASA/HLS/HLSS30/v002")
    .filterBounds(geometry)
    .filter(ee.Filter.date(d1, d2))
    .filter(ee.Filter.lt('CLOUD_COVERAGE',cloudCoverThreshold))
    .map(maskHSLclouds)
    .sort('CLOUD_COVERAGE', false)
    .select(['B2',  'B3',   'B4', 'B8', 'B11',  'B12',  'Fmask'], 
            ['blue','green','red','NIR','SWIR1','SWIR2','Fmask'])

// add the median composite to the map
Map.addLayer(sentinelHLS.median().clip(geometry), {
  bands: ['red', 'green', 'blue'],
  min:0.01,
  max:0.18,
}, 'Sentinel HLS');

// merge the two HLS image collections
var combinedHLS = landsatHLS.merge(sentinelHLS)

// add the median composite to the map
Map.addLayer(combinedHLS.median().clip(geometry), {
  bands: ['red', 'green', 'blue'],
  min:0.01,
  max:0.18,
}, 'combined HLS');

// functions
// -------------------------------------------------------------------

// write a function for masking clouds from HLS images
function maskHSLclouds(image) {
  // define the QA band
  var qa = image.select('Fmask'); 
  // extract the bits related to cloud sin the QA band
  var cloudBitMask = 1 << 1;  
  var cloudAdjBitMask = 1 << 2; 
  var cloudShadowBitMask = 1 << 3; 
  // slect only pixels that are not clouds
  var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
            .and(qa.bitwiseAnd(cloudShadowBitMask).eq(0))
            // .and(qa.bitwiseAnd(cloudAdjBitMask).eq(0));
  // return the cloud masked image
  return image.updateMask(mask);
}

Planet

// setup
// -------------------------------------------------------------------

// threshold for percent cloud cover in a single scene
var cloudCoverThreshold = 100

// dates of interest
var d1 = '2016-1-1'
var d2 = '2016-12-31'

// center the map on the AOI
Map.centerObject(geometry, 7);

// imagery
// -------------------------------------------------------------------

// import Planet NICFI
// This collection is not publicly accessible. To sign up for access,
// please see https://developers.planet.com/docs/integrations/gee/nicfi
var nicfiCollection = ee.ImageCollection('projects/planet-nicfi/assets/basemaps/africa');

// preprocess the image collection
var nicfi = nicfiCollection
  // filter for images that intersect with the AOI
  .filterBounds(geometry)
  // filter for dates of interest
  .filter(ee.Filter.date(d1, d2))
  // mask out clouds 
  .map(function(image) {
    // select and rename the bands we want to use
    var blue = image.select('B'); 
    var green = image.select('G');  
    var red = image.select('R'); 
    var NIR = image.select('N');
    // calculate a brightness value by averaging all the bands
    var brightness = image.expression(
    '(b1 + b2 + b3 + b4) / 4', {
      'b1': blue, 'b2': green, 'b3': red, 'b4': NIR
    })
    // Create a cloud mask by setting thresholds for each band (high reflectance in all bands suggests clouds)
    var cloudMask = blue
    .lt(1100)  
    .and(green.lt(1600))  
    .and(red.lt(2200)) 
    // .and(NIR.lt(3500)) 
    .and(brightness.lt(2200));  
    // Apply the cloud mask to the image
    return image.updateMask(cloudMask).addBands(brightness);
  })
  // get the median composite
  .median()
  // clip to AOI
  .clip(geometry)

// define visualization parameters
var vis = {'bands':['R','G','B'],'min':64,'max':5454,'gamma':1.8};
var NDVIvis = {min:-0.55,max:0.8,palette: ['8bc4f9', 'c9995c', 'c7d270','8add60','097210']};

// add to map
Map.addLayer(
  nicfi, 
  vis, 
  '2021-03 mosaic');
  
// calculate NDVI and add to map
Map.addLayer(
  nicfi.normalizedDifference(['N','R']).rename('NDVI'),
  NDVIvis, 
  'NDVI');

2025 --- Spatial Informatics Group / NovaSphere --- Financial support from Environment and Climate Change Canada