Vegetation Productivity Indicator

//VERSION=3
function setup() {
    return {
        input: ["B04", "B08", "CLM", "dataMask"],
        output: { bands: 1 },
        mosaicking: "ORBIT",
    };
}

const NODATA = -32768;

// tolerance in either direction, so i.e. +- 5 days
const toleranceDays = 5;
const msInDay = 24 * 60 * 60 * 1000;
const msInYear = 365.25 * msInDay;
const msInHalfYear = msInYear / 2;
const toleranceMs = toleranceDays * msInDay;

var metadata = undefined;

function relDiff(a, b) {
    const diff = Math.abs(a - b);
    return diff > msInHalfYear ? msInYear - diff : diff;
}

function datetimeToYearEpoch(date) {
    return date - new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
}

function sortDatesDescending(d1, d2) {
    const date1 = new Date(d1.dateFrom);
    const date2 = new Date(d2.dateFrom);
    return date2 - date1;
}

function preProcessScenes(collections) {
    // sort
    let scenes = collections.scenes.orbits;
    scenes = scenes.sort(sortDatesDescending);
    let newScenes = [];
    // convert first scene to day of year
    const observed = new Date(scenes[0].dateFrom);
    const obsMs = datetimeToYearEpoch(observed);
    for (let i = 0; i < scenes.length; i++) {
        let currentDate = new Date(scenes[i].dateFrom);
        let sceneMs = datetimeToYearEpoch(currentDate);
        let dt = relDiff(obsMs, sceneMs);
        if (dt <= toleranceMs) {
            newScenes.push(scenes[i]);
        }
    }

    metadata = {
        observed: observed.toISOString(),
        historical: newScenes.slice(1).map((scene) => scene.dateFrom),
    };

    collections.scenes.orbits = newScenes;
    return collections;
}

function updateOutputMetadata(scenes, inputMetadata, outputMetadata) {
    outputMetadata.userData = metadata;
}

function percentileOfScore(data, value) {
    // Calculate the percentile rank of a value relative to a list of values.

    data.sort((a, b) => a - b);

    let lowerCount = 0;
    let sameCount = 0;

    for (let i = 0; i < data.length; i++) {
        if (data[i] < value) {
            lowerCount++;
        } else if (data[i] === value) {
            sameCount++;
        } else {
            break;
        }
    }

    return ((lowerCount + 0.5 * sameCount) / data.length) * 100;
}

function updateOutputMetadata(scenes, inputMetadata, outputMetadata) {
    outputMetadata.userData = metadata;
}

function isClear(sample) {
    return sample.CLM == 0 && sample.dataMask == 1;
}

function evaluatePixel(samples) {
    // if the first value isn't clear, stop
    if (!isClear(samples[0])) {
        return [NODATA];
    }
    const clearTs = samples.filter(isClear);
    let vpi = NODATA;
    if (clearTs.length > 1) {
        const observed = index(clearTs[0].B08, clearTs[0].B04);
        var hist_ndvi = [];
        for (let i = 1; i < clearTs.length; i++) {
            const ndvi = index(clearTs[i].B08, clearTs[i].B04);
            hist_ndvi.push(ndvi);
        }
        vpi = percentileOfScore(hist_ndvi, observed) / 100;
    }

    return [vpi];
}

Evaluate and Visualize

General description of the script

The vegetation productivity indicator (VPI) assesses the overall vegetation condition by comparing the observed NDVI to a cumulative probability plot of the long-term statistics for the same 10-day period [1]. VPI is a per-pixel and per decade percentile ranking of the observed NDVI value against its historical range of variability and reflects the probability of observing a similar NDVI value in the entire time series. Low VPI values indicate that the actual observation corresponds to the historical minimum (worst vegetation state), whereas high values correspond to the historical maximum (best situation).

Please note that in case of Sentinel 2, only a few years of history are available.

The script takes the newest (latest) available scene as the observed one. Then, for each previous year the script finds all values within toleranceDays of the most recent date.

The actual scenes (dates) used can be returned as meta-data with an API requests by replacing the responses part of the request with:

  "responses":  [{
    "identifier": "userdata",
    "format": { "type": "application/json" }
  }]

Example response:

{
  "historical":[
    "2019-05-31T00:00:00.000Z",
    "2018-05-31T00:00:00.000Z",
    "2017-05-26T00:00:00.000Z",
    "2016-06-07T00:00:00.000Z"
    ],
  "observed":"2020-05-30T00:00:00.000Z"
}

Description of representative images

Vegetation productivity indicator near Dodge City, Kansas, USA. Acquired on 30.05.2020.

Vegetation productivity indicator near Dodge City, Kansas, USA

References

[1] C. A. D. Sannier, J. C. Taylor, W. Du Plessis & K. Campbell (1998) Real-time vegetation monitoring with NOAA-AVHRR in Southern Africa for wildlife management and food security assessment, International Journal of Remote Sensing, 19:4, 621-639, DOI: 10.1080/014311698215892