Temporal analysis - burned area


#1

I am attempting to do a burn scar analysis by getting the difference between the Normalized Burn Ratio of two scenes from different dates. It must be possible to do it in Playground with a custom script, but how would one go about doing that?

Regards
FC


#2

Try this script:

function    setup (dss) {
  setInputComponents([dss.B08,dss.B12]);
  setOutputComponentCount(1);
}
 
function filterScenes (scenes, inputMetadata) {
  return scenes.filter(function (scene) {
       return scene.date.getTime()>=(inputMetadata.to.getTime()-2*31*24*3600*1000) ;
    });
}
 
function calcNBR(sample) {
  var denom = sample.B08+sample.B12;
  return ((denom!=0) ? (sample.B08-sample.B12) / denom : 0.0);
}
 
function evaluatePixel(samples,scenes) { 
  var nbrpre = 0;
  var nbrpost = 0;
  
  var endMonth = scenes[0].date.getMonth();
  var startMonth = scenes[scenes.length-1].date.getMonth();
 
  for (var i=0;i<samples.length;i++) {
   
    if (scenes[i].date.getMonth()==endMonth){
      nbrpost = calcNBR(samples[i]);
    }
    if (scenes[i].date.getMonth()==startMonth){
      nbrpre = calcNBR(samples[i]);
    }
  }
  var dnbr = nbrpre - nbrpost;
 return [dnbr];
}

#3

In case you want to compare two specific dates, you might use something along the lines of:

function filterScenes (scenes, inputMetadata) {
    return scenes.filter(function (scene) {
	  var allowedDates = ["2017-11-05","2017-12-05"];
      
      var sceneDateStr = getDateInISO(scene.date);
      if (allowedDates.indexOf(sceneDateStr)>=0) return true;
      else return false;
    });
}

in the filterScenes section.
To do this in a dynamic fashion, you would want to use EVALSCRIPT parameter - e.g. prepare the script in your application, then encode it (BASE64) and then pass it as EVALSCRIPT.


#4

Thanks Grega, your help is much appreciated! I got the results I wanted with the script below:

    function setup (dss) {
      // get all bands for display and analysis
      setInputComponents([dss.B02,dss.B03,dss.B04,dss.B08,dss.B12]);
      // return as RGB
      setOutputComponentCount(3);
    }

    function filterScenes (scenes, inputMetadata) {  
      return scenes.filter(function (scene) {
        // set dates for pre-and-post fire analysis
        var allowedDates = ["2017-05-15","2017-06-24"]; // Knysna fires
        // format scene date timestamp to match allowed dates 
        var sceneDateStr = dateformat(scene.date);
        if (allowedDates.indexOf(sceneDateStr)!= -1) return true;
        else return false;
      });
    }

    // Normalized Burn Ration calculation
    function calcNBR(sample) {
      var denom = sample.B08+sample.B12;
      var nbrval = ((denom!=0) ? (sample.B08-sample.B12) / denom : 0.0);
      return nbrval;
    }

    function dateformat(d){  
      var dd = d.getDate();
      var mm = d.getMonth()+1;
      var yyyy = d.getFullYear();
      if(dd<10){dd='0'+dd}
      if(mm<10){mm='0'+mm}
      var isodate = yyyy+'-'+mm+'-'+dd;
      return isodate;
    }

    function evaluatePixel(samples,scenes) {  
      var nbrpre = 0;
      var nbrpost = 0;  
      
      // get pre-fire image
      nbrpre = calcNBR(samples[1]);
      // get post-fire image
      nbrpost = calcNBR(samples[0]);          
      // get difference 
      var dnbr = nbrpre - nbrpost;
      // set output display layers
      var NaturalColors = [3 * samples[0].B04, 3 * samples[0].B03, 3 * samples[0].B02];          
      var burnModerate = [1,0.5,0];
      var burnSevere = [1,0,0];
      return (dnbr < 0.27 ?
              NaturalColors : (dnbr < 0.66 ?
                burnModerate : burnSevere)
      );
      
    }

How to have a NDVI anomaly image
#5

That looks great. Thanks for sharing it.

An example here (make sure to switch on Temporal processing in Effects pane)


#6

A little tweaked for nicer visuals:

function setup (dss) {
  // get all bands for display and analysis
  setInputComponents([dss.B02,dss.B03,dss.B04,dss.B05,dss.B08,dss.B12]);
  // return as RGB
  setOutputComponentCount(3);
}

function stretch(val, min, max) {return (val - min) / (max - min);}

function filterScenes (scenes, inputMetadata) {  
return scenes.filter(function (scene) {
// set dates for pre-and-post fire analysis
var allowedDates = ["2017-05-15","2017-06-24"]; // Knysna fires
// format scene date timestamp to match allowed dates 
var sceneDateStr = dateformat(scene.date);
if (allowedDates.indexOf(sceneDateStr)!= -1) return true;
else return false;
  });
}

// Normalized Burn Ration calculation
function calcNBR(sample) {
  var denom = sample.B08+sample.B12;
  var nbrval = ((denom!=0) ? (sample.B08-sample.B12) / denom : 0.0);
  return nbrval;
}

function dateformat(d){  
  var dd = d.getDate();
  var mm = d.getMonth()+1;
  var yyyy = d.getFullYear();
  if(dd<10){dd='0'+dd}
  if(mm<10){mm='0'+mm}
  var isodate = yyyy+'-'+mm+'-'+dd;
  return isodate;
}

function evaluatePixel(samples,scenes) {  
  var nbrpre = 0;
  var nbrpost = 0;  
  
  // get pre-fire image
  nbrpre = calcNBR(samples[1]);
  // get post-fire image
  nbrpost = calcNBR(samples[0]);  
  // get difference 
  var dnbr = nbrpre - nbrpost;
  // set output display layers
  var stretchMin = 0.05;
  var stretchMax = 1.00;
  var NaturalColors = [stretch(2.8 * samples[0].B04 + 0.1 * samples[0].B05, stretchMin, stretchMax), stretch(2.8 * samples[0].B03 + 0.15 * samples[0].B08, stretchMin, stretchMax), stretch(2.8 * samples[0].B02, stretchMin, stretchMax)];  
  var burnModerate = [stretch(2.8 * samples[0].B04 + 0.1 * samples[0].B05, stretchMin, stretchMax)+0.5, stretch(2.8 * samples[0].B03 + 0.15 * samples[0].B08, stretchMin, stretchMax)+0.5, stretch(2.8 * samples[0].B02, stretchMin, stretchMax)];  
  var burnSevere = [stretch(2.8 * samples[0].B04 + 0.1 * samples[0].B05, stretchMin, stretchMax)+0.5, stretch(2.8 * samples[0].B03 + 0.15 * samples[0].B08, stretchMin, stretchMax), stretch(2.8 * samples[0].B02, stretchMin, stretchMax)];
  return (dnbr < 0.27 ?
  NaturalColors : (dnbr < 0.66 ?
  burnModerate : burnSevere)
  );
  
}

Link


#7

Very nice! Thanks Pierre.


#8

I have committed the script to Custom script github repository.

Thanks to both.


#9

Hi ! I found very interesting what you are doing.
I tried to run the scrypt on the playground with out success. I zoomed in a area where i know a fire scar, and changed the pre and post fire dates. Should i change any other parameter?

Thanks in advance

Nicolas


#10

Hi Nicolas,

you need to use the “temporal” playground app, which is in prototype mode, e.g.:

And then do as you suggested.
If it does not work, let me know, which area you are looking at and which are the dates, so that we can debug.

Best,
Grega


#11

So many thanks Grega! Nice job!

It could be very interesting to export the results into a vectorized polygon, do you think is possible in this environment?

Cheers

Fires in Córdoba, Argentina


#12

Looks nice, thanks for sharing.
Vectorized polygon is an output option, see:
https://sentinel-hub.com/develop/documentation/api/output-formats
You would need to configure your Sentinel Hub account properly and then call the service via API though.


#13

Hi @pierre.markuse and @fcbasson

Great script and idea, up until I came across this I had been downloading terabytes of sentinel 2 to calculate difference NBR. However, I cannot see to get the above script to work in the Sentinel Hub Playground (“Error loading image:”), I am a complete noob to this platform. Do you mind please pointing me in the right direction?

Thanks in advance.

Kind regards,
Andrew


#14

Hi Andrew,
did you use “Temporal version of Sentinel Playground” as linked here:

Or try one link directly as described here:
Temporal analysis - burned area?