Cloudless Mosaic and Image Limit in Sentinel Processing API

am currently working on a project that involves creating cloudless mosaics from Sentinel-2 imagery. I am exploring the capabilities of the Sentinel Processing API and have a couple of specific questions regarding its functionality:

  1. Is it possible to create a cloudless mosaic using the Sentinel Processing API similar to Google Earth Engine (Creating a composite and then finding the median or n’th quantile of it)? I have tried some publicly available evalscript examples, but they did not work for my case. It is especially a cloudy area, in Google Engine I have used almost 2 years of composite and now want to automate it.
  2. For the preProcessScenes function in Evalscript, is there a limit on the number of images it can return?

Hi,

Yes it is possible to do this using Processing API. With two years of data you may experience some timing out of the API over larger areas though. Below I have an example of a script to calculate a composite of the median value:

//VERSION=3

function setup() {
    return {
      input: [{
        bands: ["B08", "B04", "B03", "B02", "CLM", "SCL"],  //Requests required bands, s2cloudless mask and scene classification layer
        units: "DN"
      }],
      output: {
        bands: 4,
        sampleType: SampleType.UINT16
      },
      mosaicking: "ORBIT"
    };
  }
  
  function filterScenes (scenes, inputMetadata) {
    return scenes.filter(function (scene) {
      return scene.date.getTime()>=(inputMetadata.to.getTime()-12*30*24*3600*1000); //Defines the time range, e.g. from 1st June until 31st October counts 5 months with 30 days, 24 hours...
    });
  }
  
  function getValue(values) {
    values.sort( function(a,b) {return a - b;} );
    return getMedian(values);
  }
  
  function getMedian(sortedValues) {
    var index = Math.floor(sortedValues.length / 2);
    return sortedValues[index];
  }
  function getDarkestPixel(sortedValues) {
    return sortedValues[0]; // darkest pixel
  }
  
  function validate (samples) {
    var scl = samples.SCL;
    var clm = samples.CLM;
  
    if (clm === 1 || clm === 255) {
          return false;
    } else if (scl === 1) { // SC_SATURATED_DEFECTIVE
          return false;
    } else if (scl === 3) { // SC_CLOUD_SHADOW
          return false;
    } else if (scl === 7) { // SC_CLOUD_LOW_PROBA
          return false;    
    } else if (scl === 8) { // SC_CLOUD_MEDIUM_PROBA
          return false;
    } else if (scl === 9) { // SC_CLOUD_HIGH_PROBA
          return false;
    } else if (scl === 10) { // SC_THIN_CIRRUS
          return false;
    } else if (scl === 11) { // SC_SNOW_ICE
      return false;
    }  else {
    return true;
    }
  }
  
  function evaluatePixel(samples, scenes) {
    var clo_b02 = []; var clo_b03 = []; var clo_b04 = []; var clo_b08 = [];
    var clo_b02_invalid = []; var clo_b03_invalid = []; var clo_b04_invalid = []; var clo_b08_invalid = [];
    var a = 0; var a_invalid = 0;
  
    for (var i = 0; i < samples.length; i++) {
      var sample = samples[i];
      if (sample.B02 > 0 && sample.B03 > 0 && sample.B04 > 0 && sample.B08 > 0) {
        var isValid = validate(sample);
  
        if (isValid) {
          clo_b02[a] = sample.B02;
          clo_b03[a] = sample.B03;
          clo_b04[a] = sample.B04;
          clo_b08[a] = sample.B08;
          a = a + 1;
        } else {
          clo_b02_invalid[a_invalid] = sample.B02;
          clo_b03_invalid[a_invalid] = sample.B03;
          clo_b04_invalid[a_invalid] = sample.B04;
          clo_b08_invalid[a_invalid] = sample.B08;
          a_invalid = a_invalid + 1;
        }
      }
    }
  
    var rValue;
    var gValue;
    var bValue;
    var nirValue;
  
    if (a > 0) {
      nirValue = getValue(clo_b08);
      rValue = getValue(clo_b04);
      gValue = getValue(clo_b03);
      bValue = getValue(clo_b02);
    } else if (a_invalid > 0) {
      nirValue = getValue(clo_b08_invalid);    
      rValue = getValue(clo_b04_invalid);
      gValue = getValue(clo_b03_invalid);
      bValue = getValue(clo_b02_invalid);
    } else {
      nirValue = 0;
      rValue = 0;
      gValue = 0;
      bValue = 0;
    }
    return [nirValue, rValue, gValue, bValue]
  }

If your area of interest is larger I would recommend one of the following:

  • for areas that are a bit too large, you can look at the large area utility: " the sentinelhub package implements utilities for splitting areas into smaller bounding boxes."
  • a second option is to use our eolearn package, which is designed to “to seamlessly access and process spatio-temporal image sequences”. Although it is geared toward ML applications, it is quite good at splitting large areas into tiles and dealing with large time-series.
  • The third option, is the most adapted for large ares/time-series: batch processing API, which you can also leverage with the Python SDK. “Sentinel Hub Batch Processing takes the geometry of a large area and divides it according to a specified tile grid. Next, it executes processing requests for each tile in the grid and stores results to a given location at AWS S3 storage. All this is efficiently executed on the server-side. Because of the optimized performance, it is significantly faster than running the same process locally.
1 Like