NDVI difference between two dates

//VERSION=3
// Script to extract NDVI difference between the latest acquisition and the acquisition 10-day prior to the latest within a specified time range
// Returns visualized RGB values
function setup() {
    return {
        input: [{
            bands: ["B04", "B08", "SCL", "dataMask"],
            units: "DN"
        }],
        output: { bands: 4 },
        mosaicking: Mosaicking.ORBIT
    }

}

function evaluatePixel(samples) {
    // ndvi difference
    let latest = samples[0];
    let prior = samples[1];
    let dataMask = latest.dataMask * prior.dataMask;
    const diff = dataMask === 1 ? index(latest.B08, latest.B04) - index(prior.B08, prior.B04) : NaN;

    // visualisation
    const ramps = [
        [-2, 0x8e0152],
        [-1, 0xc51b7d],
        [-0.5, 0xde77ae],
        [0, 0xf7f7f7],
        [0.5, 0x7fbc41],
        [1, 0x4d9221],
        [2, 0x276419]
    ]
    const visualizer = new ColorRampVisualizer(ramps);
    let imgVals = visualizer.process(diff);
    imgVals.push(dataMask)

    return imgVals
}

function preProcessScenes(collections) {
    // sort from most recent to least recent
    collections.scenes.orbits = collections.scenes.orbits.sort(
        (s1, s2) => new Date(s2.dateFrom) - new Date(s1.dateFrom)
    );

    let scenes = collections.scenes.orbits;
    let latest;
    let closest;
    latest = closest = scenes[0];

    // timestamp of 10-day prior to latest acquisition
    let target = new Date(new Date(latest.dateFrom).getTime() - 10 * 24 * 3600 * 1000);

    // find closet timestamp to the target
    let diff = Number.POSITIVE_INFINITY;
    for (let i = 1; i < scenes.length; i++) {
        current = new Date(scenes[i].dateFrom);
        if (Math.abs(current - target) >= diff) { break; }
        diff = Math.abs(current - target);
        closest = scenes[i];
    }

    // filter collections to keep the latest acquisition and the closest acquisitions to the target
    collections.scenes.orbits = collections.scenes.orbits.filter(function (orbit) {
        var orbitDateFrom = orbit.dateFrom;
        return [latest.dateFrom, closest.dateFrom].includes(orbitDateFrom);
    })
    return collections
}
//VERSION=3
// Script to extract NDVI difference between the latest acquisition and the acquisition 10-day prior to the latest within a specified time range
// To be used on EO Browser. Makes statistics work in EOB
function setup() {
    return {
        input: [{
            bands: ["B04", "B08", "SCL", "dataMask"],
            units: "DN"
        }],
        output: [
            { id: "default", bands: 4 },
            { id: "index", bands: 1, sampleType: "FLOAT32" },
            { id: "eobrowserStats", bands: 2, sampleType: "FLOAT32" },
            { id: "dataMask", bands: 1 }
        ],
        mosaicking: Mosaicking.ORBIT
    }

}

function evaluatePixel(samples) {
    // ndvi difference
    let latest = samples[0];
    let prior = samples[1];
    let dataMask = latest.dataMask * prior.dataMask;
    const diff = dataMask === 1 ? index(latest.B08, latest.B04) - index(prior.B08, prior.B04) : NaN;

    // visualisation
    const ramps = [
        [-2, 0x8e0152],
        [-1, 0xc51b7d],
        [-0.5, 0xde77ae],
        [0, 0xf7f7f7],
        [0.5, 0x7fbc41],
        [1, 0x4d9221],
        [2, 0x276419]
    ]
    const visualizer = new ColorRampVisualizer(ramps);
    let imgVals = visualizer.process(diff);
    imgVals.push(dataMask)

    return {
        default: imgVals,
        index: [diff],
        eobrowserStats: [diff, isCloud(samples.SCL) ? 1 : 0],
        dataMask: [dataMask]
    };
}

function preProcessScenes(collections) {
    // sort from most recent to least recent
    collections.scenes.orbits = collections.scenes.orbits.sort(
        (s1, s2) => new Date(s2.dateFrom) - new Date(s1.dateFrom)
    );

    let scenes = collections.scenes.orbits;
    let latest;
    let closest;
    latest = closest = scenes[0];

    // timestamp of 10-day prior to latest acquisition
    let target = new Date(new Date(latest.dateFrom).getTime() - 10 * 24 * 3600 * 1000);

    // find closet timestamp to the target
    let diff = Number.POSITIVE_INFINITY;
    for (let i = 1; i < scenes.length; i++) {
        current = new Date(scenes[i].dateFrom);
        if (Math.abs(current - target) >= diff) { break; }
        diff = Math.abs(current - target);
        closest = scenes[i];
    }

    // filter collections to keep the latest acquisition and the closest acquisitions to the target
    collections.scenes.orbits = collections.scenes.orbits.filter(function (orbit) {
        var orbitDateFrom = orbit.dateFrom;
        return [latest.dateFrom, closest.dateFrom].includes(orbitDateFrom);
    })
    return collections
}

function isCloud(scl) {
    if (scl == 3) { // SC_CLOUD_SHADOW
        return false;
    } else if (scl == 9) { // SC_CLOUD_HIGH_PROBA
        return true;
    } else if (scl == 8) { // SC_CLOUD_MEDIUM_PROBA
        return true;
    } else if (scl == 7) { // SC_CLOUD_LOW_PROBA
        return false;
    } else if (scl == 10) { // SC_THIN_CIRRUS
        return true;
    } else if (scl == 11) { // SC_SNOW_ICE
        return false;
    } else if (scl == 1) { // SC_SATURATED_DEFECTIVE
        return false;
    } else if (scl == 2) { // SC_DARK_FEATURE_SHADOW
        return false;
    }
    return false;
}
//VERSION=3
// Script to extract NDVI difference between the latest acquisition and the acquisition 10-day prior to the latest within a specified time range
// Returns raw (Float32) values
function setup() {
    return {
      input: [{
        bands: ["B04", "B08", "SCL", "dataMask"],
        units: "DN"
      }],
      output: {
        bands: 1,
        sampleType: SampleType.FLOAT32
      },
      mosaicking: Mosaicking.ORBIT
    }
    
  }
  
  function evaluatePixel(samples) {
    // ndvi difference
    let latest = samples[0];
    let prior = samples[1];
    let combinedMask = latest.dataMask * prior.dataMask;
    const diff = combinedMask === 1 ? index(latest.B08, latest.B04) - index(prior.B08, prior.B04) : NaN;
    return [diff];
  }

  function preProcessScenes (collections) {
    // sort from most recent to least recent
    collections.scenes.orbits = collections.scenes.orbits.sort(
      (s1, s2) => new Date(s2.dateFrom) - new Date(s1.dateFrom)
    );
    
    let scenes = collections.scenes.orbits;
    let latest;
    let closest;
    latest = closest = scenes[0];

    // timestamp of 10-day prior to latest acquisition
    let target = new Date(new Date(latest.dateFrom).getTime() - 10*24*3600*1000);

    // find closet timestamp to the target
    let diff = Number.POSITIVE_INFINITY;
    for (let i = 1; i < scenes.length; i++) {
      current = new Date(scenes[i].dateFrom);
      if (Math.abs(current - target) >= diff) {break;}
      diff = Math.abs(current - target);
      closest = scenes[i];
    }
    collections.scenes.orbits = [latest, closest];
    return collections
}

Evaluate and Visualize

General description

This script aims to obtain the diffence of NDVI between the latest acquisition and the acquisition 10-day prior to the latest on within a specified time period. Multi-temporal analysis is common in the Earth Observation field. Here we take NDVI as an example and demonstrate how to calculate the difference of NDVI between two acquisitions using mosaicking: ORBIT and preProcessScenes in one single request.

To implement multi-temporal analysis in the Evalscript, we apply ORBIT mosaicking to query daily mosaic in the specified time period. Then, by using the optional preProcessScenes function, we find out the acquisition acquired on the date closest to the date 10-day prior to the latest acquisition and filter the out the other unused acquisitions to save Processing Units. Last but not least, in the evaluatePixel function we initialise a combined mask to ensure the difference of NDVI between two acquisitions exists only if there is data on both dates.

Note: The example script is used to obtain the raw value of NDVI difference. For visualisation purpose, please follow the EO Browser link in the Evaluate and visualize section. The visualisation script contains 4 outputs: default, index, eobrowserStats and dataMask. The default layer is a visualisation layer to visualise NDVI difference in EO Browser. The index layer is the actual value of the NDVI difference. The eobrowserStats and the dataMask layer is configured to activate statistical features on EO Browser. Please see the FAQ for more details.

Author of the script

Chung-Xiang Hong

Description of representative images

The following image shows the NDVI difference between the latest acquisition and the acquisition 10-day prior to the latest one during the time period from 29th of June, 2023 to 29th July, 2023. NDVI difference example