Skip to main content

Examples

On this page, there are example evalscripts that will help with understanding the basics of writing evalscripts. There are additional examples for getting started on the custom scripts repository.

note

All the scripts utilize the Sentinel-2 L2A and Analysis Ready PlanetScope data collections. For more examples, follow the Introduction to Custom Scripts on Planet Insights Platform course on Planet University.

Returning a True Color Image

  • Code Version 3 must be specified in the custom script header using //VERSION=3.
  • The setup() is a mandatory function used to specify the input bands used, the output shape, and the format of your response. In this example, there are three bands in the input; the return also contains three bands.
  • The evaluatePixel() is a mandatory function that performs the required operations per pixel.
  • Note that the returned object's shape (3 elements) matches the specified shape in the output defined in the setup() function.
//VERSION=3

function setup() {
return {
input: ['B02', 'B03', 'B04'],
output: {
bands: 3,
sampleType: 'AUTO',
},
};
}

function evaluatePixel(sample) {
return [sample.B04, sample.B03, sample.B02];
}

The output of the above script results in the following image:

True color

True Color with Color Correction

Basic color correction can be performed in the return function, like in the example below, which brightens and increases the contrast of the image returned.

return [
2.5 * sample.B04 - 0.07,
2.5 * sample.B03 - 0.07,
2.5 * sample.B02 - 0.07,
];

Output:

True color correction

Calculating Raw NDVI Values

Spectral indices can be generated within an evalscript.

  • Note that this script only uses the two bands required to calculate NDVI in its input.
  • In addition, the index is only a 1-band image, so the output is defined as one band.
  • Note that the sampleType in the output definition has changed from AUTO to FLOAT32, meaning the output can contain float values (decimals).
  • Within the evaluatePixel() function the index() function is applied to these 2 bands to return NDVI values.
//VERSION=3
function setup() {
return {
input: ['B04', 'B08'],
output: {
bands: 1,
sampleType: 'FLOAT32',
},
};
}

function evaluatePixel(samples) {
return [index(samples.B08, samples.B04)];
}

Calculating NDVI and Returning an Interpolated Colormap

To visualize output, calculate the spectral index and return interpolated colormaps instead of the raw NDVI values.

  • Compared to the previous example, the output is now three bands rather than 1.
  • The index function is now defined as a variable using let NDVI.
  • The return is now defined by the valueInterpolate() function. This requires three inputs: the input values (in this case, NDVI), the intervals, an array of numbers in ascending order defining intervals, and the output interval for the given value/interval of the intervals array.
  • In this example, five intervals and five arrays are defined with the RGB values to create the colormap.
//VERSION=3
function setup() {
return {
input: ['B04', 'B08'],
output: {
bands: 3,
sampleType: 'FLOAT32',
},
};
}

function evaluatePixel(samples) {
let NDVI = index(samples.B08, samples.B04);
return valueInterpolate(
NDVI,
[-1, 0, 0.2, 0.5, 1],
[
[0, 0, 0],
[1, 1, 0.88],
[0.57, 0.75, 0.32],
[0.31, 0.54, 0.18],
[0.06, 0.33, 0.04],
],
);
}

Output:

NDVI interpolated colormap

Masking Out Cloudy Pixels

Data masks may be used, for example, to exclude cloudy pixels from visualizations.

  • If visualizing Sentinel-2 L2A data, use the SCL band to perform the masking.
  • In the following example, if the SCL band has one of the following values: 8, 9, or 10, those pixels will be black.
  • Alternatively, if visualizing Analysis Ready Planetscope, utilize the cloudmask band.
//VERSION=3

function setup() {
return {
input: ['B04', 'B08', 'SCL'],
output: {
bands: 3,
sampleType: 'AUTO',
},
};
}

function evaluatePixel(samples) {
let NDVI = index(samples.B08, samples.B04);
if ([8, 9, 10].includes(samples.SCL)) {
return [0, 0, 0];
} else {
return valueInterpolate(
NDVI,
[-1, 0, 0.2, 0.5, 1],
[
[0, 0, 0],
[1, 1, 0.88],
[0.57, 0.75, 0.32],
[0.31, 0.54, 0.18],
[0.06, 0.33, 0.04],
],
);
}
}

Output:

NDVI cloud mask

Calculating the Mean NDVI Value During a Given Time Period

Multi-temporal analysis can also be performed and will output aggregated products using evalscripts. This example explains how to calculate the mean NDVI value over a given time period.

  • By default, evalscripts use SIMPLE mosaicking, meaning only one satellite scene will be used. Note that mosaicking is explicitly set to ORBIT in this example so that you can use multiple scenes.
  • The calculation of NDVI is defined within a function named calcNDVI().
  • The evalscript then loops through the scenes in the evalscript, performing this function on each of the scenes.
  • Lastly, the mean is calculated using the sum (number of scenes) and the cumulative count of NDVI values across those scenes.
//VERSION=3
function setup() {
return {
input: [{ bands: ['B04', 'B08', 'dataMask'] }],
output: {
bands: 1,
},
mosaicking: 'ORBIT',
};
}

function calcNDVI(sample) {
var NDVI = (sample.B08 - sample.B04) / (sample.B08 + sample.B04);
return NDVI;
}

function evaluatePixel(samples) {
var sum = 0;
var count = 0;
for (var i = 0; i < samples.length; i++) {
if (samples[i].dataMask != 0) {
var ndvi = calcNDVI(samples[i]);
sum = sum + ndvi;
count++;
}
}
var average = sum / count;

return [average];
}

Comparing Two Dates to Perform Change Detection

A common task is to compare imagery from two dates — for example, to compute the change in NDVI between a "before" and an "after" acquisition. The recommended way to do this is with data fusion: request the same data collection twice, each with its own time range, and give each input an id (here before and after). This has two advantages:

  • The dates live in the request, not in the evalscript. You can run the same registered evalscript for any pair of dates by changing only the request body — no code edit or re-upload.
  • It avoids extra input processing. Each input is filtered to a single date by its timeRange, so only the two scenes you need are read, rather than loading the full stack and discarding scenes in the script.

In the evalscript, the two inputs are accessed by their id as keys of the samples object. With SIMPLE mosaicking each key is an array holding a single mosaic, so the date's data is samples.before[0] and samples.after[0].

//VERSION=3
function setup() {
return {
input: [
{ datasource: 'before', bands: ['B04', 'B08', 'dataMask'] },
{ datasource: 'after', bands: ['B04', 'B08', 'dataMask'] },
],
output: { bands: 1, sampleType: 'FLOAT32' },
mosaicking: 'SIMPLE',
};
}

function calcNDVI(sample) {
return (sample.B08 - sample.B04) / (sample.B08 + sample.B04);
}

function evaluatePixel(samples) {
var before = samples.before[0];
var after = samples.after[0];
if (before.dataMask === 0 || after.dataMask === 0) {
return [NaN];
}
return [calcNDVI(after) - calcNDVI(before)];
}

The request body declares the two inputs and matches each id to a datasource in the evalscript. The example below uses the Analysis-Ready PlanetScope Sandbox Data collection over a cropland area in Iowa, comparing bare soil in spring to peak crop growth in summer.

{
"input": {
"bounds": {
"bbox": [-93.84, 41.16, -93.78, 41.22],
"properties": {
"crs": "http://www.opengis.net/def/crs/EPSG/0/4326"
}
},
"data": [
{
"type": "byoc-3f605f75-86c4-411a-b4ae-01c896f0e54e",
"id": "before",
"dataFilter": {
"timeRange": {
"from": "2022-04-26T00:00:00Z",
"to": "2022-04-26T23:59:59Z"
}
}
},
{
"type": "byoc-3f605f75-86c4-411a-b4ae-01c896f0e54e",
"id": "after",
"dataFilter": {
"timeRange": {
"from": "2022-08-05T00:00:00Z",
"to": "2022-08-05T23:59:59Z"
}
}
}
]
},
"output": {
"width": 512,
"height": 512,
"responses": [
{ "identifier": "default", "format": { "type": "image/tiff" } }
]
}
}
tip

To compare two dates of the same data collection, set the same collection type for both inputs and only change the timeRange, as shown above. To compare across different collections (for example, Sentinel-2 against Landsat), give each input its own type.