Skip to content

Commit

Permalink
Merge pull request #322 from willrayeo/planetscope-veg-indices
Browse files Browse the repository at this point in the history
Planetscope veg indices
  • Loading branch information
chorng committed Jul 1, 2024
2 parents f17f358 + 312e540 commit 4b3fa33
Show file tree
Hide file tree
Showing 11 changed files with 490 additions and 0 deletions.
45 changes: 45 additions & 0 deletions planet_scope/max_ndvi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: 'Max Multitemporal NDVI'
parent: Planetscope
grand_parent: Planet
layout: script
permalink: /planet_scope/max_ndvi/
nav_exclude: true
scripts:
- - Visualization
- script.js
- - EO Browser
- eob.js
- - Raw Values
- raw.js
examples:
- zoom: '15'
lat: '-16.60556'
lng: '-48.80419'
datasetId: 'ccb1f8f0-e5bf-4c31-afe5-d8803bcbde2a'
fromTime: '2022-11-01T00:00:00.000Z'
toTime: '2023-11-19T23:59:59.999Z'
platform:
- EOB
evalscripturl: https://raw.githubusercontent.com/sentinel-hub/custom-scripts/master/planet_scope/max_ndvi/script.js
additionalQueryParams:
- - themeId
- PLANET_SANDBOX
---

## General description

The script evaluates the NDVI for each scene of the past month and returns the highest NDVI value for every pixel. In short, it returns the highest NDVI values of the past month for every pixel. The script runs on-the-fly, since
it doesn't require preprocessing. It can be used as a cloud free background or an input for further analysis, such as change detection and classification. [Find out more.](https://www.sentinel-hub.com/max_service){:target="_blank"}
Note that multi-temporal processing needs to be enabled for this script to run.

## Author of the script

William Ray

## Description of representative images

![figure 1](fig/fig1.png)



84 changes: 84 additions & 0 deletions planet_scope/max_ndvi/eob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//VERSION=3

//Basic initialization setup function
function setup() {
return {
//List of all bands, that will be used in the script, either for visualization or for choosing best pixel
input: [{
bands: [
"Red",
"NIR",
]
}],
//This can always be the same if one is doing RGB images
output: [
{ id: "default", bands: 4 },
{ id: "index", bands: 1, sampleType: "FLOAT32" },
{ id: "eobrowserStats", bands: 2, sampleType: "FLOAT32" },
{ id: "dataMask", bands: 1 }
],
mosaicking: "ORBIT"
}
}

/*
In this function we limit the scenes, which are used for processing.
These are based also on input variables.
E.g. if one sets date "2017-03-01" ("TO date") and cloud coverage filter 30%,
all scenes older than 2017-03-01 with cloud coverage 30% will be checked against
further conditions in this function (in this function it is currently limited to 3 months).
The more scenes there are, longer it will take to process the data.
After 60 seconds of processing, there will be a timeout.
*/

function preProcessScenes(collections) {
collections.scenes.orbits = collections.scenes.orbits.filter(function (orbit) {
var orbitDateFrom = new Date(orbit.dateFrom)
return orbitDateFrom.getTime() >= (collections.to.getTime() - 3 * 31 * 24 * 3600 * 1000);
})
return collections
}

function calcNDVI(sample) {
var denom = sample.Red + sample.NIR;
return ((denom != 0) ? (sample.NIR - sample.Red) / denom : NaN);
}
function evaluatePixel(samples) {
var max = Number.NEGATIVE_INFINITY;
for (let i = 0; i < samples.length; i++) {
var ndvi = calcNDVI(samples[i]);
max = ndvi > max ? ndvi : max;
}
let max_exists = 0;
if (isFinite(max)) {
max_exists = 1;
}
let imgVals;
if (max < -1.1) { imgVals = [0, 0, 0]; }
else if (max < - 0.2) { imgVals = [0.75, 0.75, 0.75]; }
else if (max < - 0.1) { imgVals = [0.86, 0.86, 0.86]; }
else if (max < 0) { imgVals = [1, 1, 0.88]; }
else if (max < 0.025) { imgVals = [1, 0.98, 0.8]; }
else if (max < 0.05) { imgVals = [0.93, 0.91, 0.71]; }
else if (max < 0.075) { imgVals = [0.87, 0.85, 0.61]; }
else if (max < 0.1) { imgVals = [0.8, 0.78, 0.51]; }
else if (max < 0.125) { imgVals = [0.74, 0.72, 0.42]; }
else if (max < 0.15) { imgVals = [0.69, 0.76, 0.38]; }
else if (max < 0.175) { imgVals = [0.64, 0.8, 0.35]; }
else if (max < 0.2) { imgVals = [0.57, 0.75, 0.32]; }
else if (max < 0.25) { imgVals = [0.5, 0.7, 0.28]; }
else if (max < 0.3) { imgVals = [0.44, 0.64, 0.25]; }
else if (max < 0.35) { imgVals = [0.38, 0.59, 0.21]; }
else if (max < 0.4) { imgVals = [0.31, 0.54, 0.18]; }
else if (max < 0.45) { imgVals = [0.25, 0.49, 0.14]; }
else if (max < 0.5) { imgVals = [0.19, 0.43, 0.11]; }
else if (max < 0.55) { imgVals = [0.13, 0.38, 0.07]; }
else if (max < 0.6) { imgVals = [0.06, 0.33, 0.04]; }
else { imgVals = [0, 0.27, 0]; }
return {
default: imgVals.concat(max_exists),
index: [max],
eobrowserStats: [max, max_exists],
dataMask: [max_exists]
}
}
Binary file added planet_scope/max_ndvi/fig/fig1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions planet_scope/max_ndvi/raw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//VERSION=3

//Basic initialization setup function
function setup() {
return {
//List of all bands, that will be used in the script, either for visualization or for choosing best pixel
input: [{
bands: [
"Red",
"NIR"
]
}],
//This can always be the same if one is doing RGB images
output: { bands: 1 },
mosaicking: "ORBIT"
}
}

/*
In this function we limit the scenes, which are used for processing.
These are based also on input variables.
E.g. if one sets date "2017-03-01" ("TO date") and cloud coverage filter 30%,
all scenes older than 2017-03-01 with cloud coverage 30% will be checked against
further conditions in this function (in this function it is currently limited to 3 months).
The more scenes there are, longer it will take to process the data.
After 60 seconds of processing, there will be a timeout.
*/

function preProcessScenes(collections) {
collections.scenes.orbits = collections.scenes.orbits.filter(function (orbit) {
var orbitDateFrom = new Date(orbit.dateFrom)
return orbitDateFrom.getTime() >= (collections.to.getTime() - 3 * 31 * 24 * 3600 * 1000);
})
return collections
}

function calcNDVI(sample) {
var denom = sample.Red + sample.NIR;
return ((denom != 0) ? (sample.NIR - sample.Red) / denom : NaN);
}
function evaluatePixel(samples) {
var max = Number.NEGATIVE_INFINITY;
for (let i = 0; i < samples.length; i++) {
var ndvi = calcNDVI(samples[i]);
max = ndvi > max ? ndvi : max;
}
return [max];
}
72 changes: 72 additions & 0 deletions planet_scope/max_ndvi/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//VERSION=3

//Basic initialization setup function
function setup() {
return {
//List of all bands, that will be used in the script, either for visualization or for choosing best pixel
input: [{
bands: [
"Red",
"NIR"
]
}],
//This can always be the same if one is doing RGB images
output: { bands: 4 },
mosaicking: "ORBIT"
}
}

/*
In this function we limit the scenes, which are used for processing.
These are based also on input variables.
E.g. if one sets date "2017-03-01" ("TO date") and cloud coverage filter 30%,
all scenes older than 2017-03-01 with cloud coverage 30% will be checked against
further conditions in this function (in this function it is currently limited to 3 months).
The more scenes there are, longer it will take to process the data.
After 60 seconds of processing, there will be a timeout.
*/

function preProcessScenes(collections) {
collections.scenes.orbits = collections.scenes.orbits.filter(function (orbit) {
var orbitDateFrom = new Date(orbit.dateFrom)
return orbitDateFrom.getTime() >= (collections.to.getTime() - 3 * 31 * 24 * 3600 * 1000);
})
return collections
}

function calcNDVI(sample) {
var denom = sample.Red + sample.NIR;
return ((denom != 0) ? (sample.NIR - sample.Red) / denom : NaN);
}
function evaluatePixel(samples) {
var max = Number.NEGATIVE_INFINITY;
for (let i = 0; i < samples.length; i++) {
var ndvi = calcNDVI(samples[i]);
max = ndvi > max ? ndvi : max;
}
let max_exists = 0;
if (isFinite(max)) {
max_exists = 1;
}
if (max < -1.1) { return [0, 0, 0, max_exists]; }
else if (max < - 0.2) { return [0.75, 0.75, 0.75, max_exists]; }
else if (max < - 0.1) { return [0.86, 0.86, 0.86, max_exists]; }
else if (max < 0) { return [1, 1, 0.88, max_exists]; }
else if (max < 0.025) { return [1, 0.98, 0.8, max_exists]; }
else if (max < 0.05) { return [0.93, 0.91, 0.71, max_exists]; }
else if (max < 0.075) { return [0.87, 0.85, 0.61, max_exists]; }
else if (max < 0.1) { return [0.8, 0.78, 0.51, max_exists]; }
else if (max < 0.125) { return [0.74, 0.72, 0.42, max_exists]; }
else if (max < 0.15) { return [0.69, 0.76, 0.38, max_exists]; }
else if (max < 0.175) { return [0.64, 0.8, 0.35, max_exists]; }
else if (max < 0.2) { return [0.57, 0.75, 0.32, max_exists]; }
else if (max < 0.25) { return [0.5, 0.7, 0.28, max_exists]; }
else if (max < 0.3) { return [0.44, 0.64, 0.25, max_exists]; }
else if (max < 0.35) { return [0.38, 0.59, 0.21, max_exists]; }
else if (max < 0.4) { return [0.31, 0.54, 0.18, max_exists]; }
else if (max < 0.45) { return [0.25, 0.49, 0.14, max_exists]; }
else if (max < 0.5) { return [0.19, 0.43, 0.11, max_exists]; }
else if (max < 0.55) { return [0.13, 0.38, 0.07, max_exists]; }
else if (max < 0.6) { return [0.06, 0.33, 0.04, max_exists]; }
else { return [0, 0.27, 0, max_exists]; }
}
43 changes: 43 additions & 0 deletions planet_scope/ndvi_difference/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: NDVI difference between two dates
parent: Planetscope
grand_parent: Planet
layout: script
permalink: /planet_scope/ndvi_difference/
nav_exclude: true
scripts:
- - Visualization
- script.js
- - EO Browser
- eob.js
- - Raw Values
- raw.js
examples:
- zoom: '14'
lat: '52.24714'
lng: '9.20886'
datasetId: Planetscope
fromTime: '2023-04-02T00:00:00.000Z'
toTime: '2023-06-01T23:59:59.999Z'
platform:
- EOB
evalscripturl: https://raw.githubusercontent.com/sentinel-hub/custom-scripts/master/planet_scope/ndvi_difference/script.js
additionalQueryParams:
- - themeId
- PLANET_SANDBOX
---

## 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`](https://docs.sentinel-hub.com/api/latest/evalscript/v3/#mosaicking) and [`preProcessScenes`](https://docs.sentinel-hub.com/api/latest/evalscript/v3/#preprocessscenes-function-optional) 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](#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](https://www.sentinel-hub.com/faq/#how-configure-your-layers-statistical-info-eo-browser) for more details.

## Author of the script

William Ray

## 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 2nd of April, 2023 to 2nd June, 2023.
![NDVI difference example](fig/fig1.png)
77 changes: 77 additions & 0 deletions planet_scope/ndvi_difference/eob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//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: ["Red", "NIR", "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.NIR, latest.Red) - index(prior.NIR, prior.Red) : 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);
return {
default: imgVals.concat(dataMask),
index: [diff],
eobrowserStats: [diff, dataMask],
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
}
Binary file added planet_scope/ndvi_difference/fig/fig1..png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 4b3fa33

Please sign in to comment.