Copy for LLM[View as Markdown](https://docs.planet.com/develop/evalscripts/) # Evalscript An evalscript (or custom script) is a piece of Javascript code that defines how the platform processes satellite data and what values the service returns. It is a required part of any [process](https://docs.planet.com/develop/apis/processing.md), [batch process](https://docs.planet.com/develop/apis/batch-processing.md) or [OGC request](https://docs.planet.com/platform/integrations/ogc.md). The evalscript [functions](https://docs.planet.com/develop/evalscripts/functions.md) section contains detailed explanations of parameters and functions that can be used in evalscripts. Evalscripts can use any JavaScript function or language structure and include certain [utility functions](https://docs.planet.com/develop/evalscripts/utilities.md) specific to the platform. Evalscripts run the [Chrome V8](https://v8.dev/) JavaScript engine. Evalscripts can be used to calculate a spectral index, create visualizations, do multi-temporal analysis and visualization, use data fusion (for example, to combine sensors or compare two dates for [change detection](https://docs.planet.com/develop/evalscripts/examples.md#comparing-two-dates-to-perform-change-detection)), and do statistical analysis. Other things, such as setting the resolution of output, setting the projection of output, and defining the time range of requests, are set as [request parameters](https://docs.planet.com/develop/apis/processing/reference.md) for the various APIs. ## Calculated and Output Values The **calculated** and **output** values depend on what users specify in their evalscripts (or custom scripts). By **calculated** values we are referring to the values that are returned from the `evaluatePixel()` function or from a simple script. **Output values** are values returned, after the calculated values go through formatting defined by `sampleType`. In the evalscript, calculated and output values are controlled by: * In the `setup()` function, the requested `bands` and `units` define what values are used as input for the calculation (in simple scripts, default units are used). For example, if Sentinel–2 band B04 is requested in `REFLECTANCE`, the input values will be in the range 0–1. If Sentinel–2 band B04 is requested in `DN` (digital numbers), the input values for the calculation will be in the range 0–10000. Typical value ranges can be found in our [public data documentation](https://docs.planet.com/data/public-data.md), chapter [Units](https://docs.planet.com/data/public-data/copernicus/sentinel-2.md#units) for each data collection. * The `evaluatePixel()` function defines the actual calculation (in simple scripts, the entire script is its equivalent). [Browser](https://insights.planet.com/analyze/browser/) uses double precision for all calculations and rounds only the final calculated values before they are outputted. * The value of the `sampleType` parameter in the `setup()` function defines the format of the output values. Possible values are `AUTO`, `UINT8`, `UINT16` and `FLOAT32`. See our [sampleType documentation](https://docs.planet.com/develop/evalscripts/functions.md#sampletype) for more details. When the `sampleType` is not specified (for example, in simple scripts), the default value `AUTO` will be used. `sampleType.AUTO` takes calculated values from the interval 0–1 and stretches them to 0– 255. If your calculated values are not in the range 0–1, make sure you either scale them to this range in the `evaluatePixel()` function or specify another `sampleType`. ### Example 1: NDVI In this example, we want to output values of the NDVI index, calculated based on Sentinel–2 data. Our `evaluatePixel()` function is: ``` function evaluatePixel(sample) { let NDVI = (sample.B08 – sample.B04)/( sample.B08 + sample.B04) return [NDVI] } ``` The requested units in this example do not have any influence on the calculated values (first column) of the NDVI. The output values returned by [Browser](https://insights.planet.com/analyze/browser/) (columns 2, 3, 4 and 5) for different `sampleTypes` are: | Calculated Value | sampleType.AUTO | sampleType.UINT8 | sampleType.UINT16 | sampleType.FLOAT32 | | ---------------- | --------------- | ---------------- | ----------------- | ------------------ | | -1 | 0 | 0 | 0 | -1 | | 0 | 0 | 0 | 0 | 0 | | 0.25 | 64 | 0 | 0 | 0.25 | | 1 | 255 | 1 | 1 | 1 | Use `sampleType:"FLOAT32"` to return full floating -1 to 1 values. See the example [here](https://docs.planet.com/data/public-data/copernicus/sentinel-2/examples.md#exact-ndvi-values-using-a-floating-point-geotiff). If you do not need values, but a visualization, you can use `sampleType:"AUTO"`, but make sure to either: * map the NDVI values to the 0–1 interval in the `evaluatePixel()` function, for example: ``` function evaluatePixel(sample) { let NDVI = (sample.B08 – sample.B04)/( sample.B08 + sample.B04) return [(NDVI+1)/2] } ``` * use a [visualizer](https://docs.planet.com/develop/evalscripts/utilities.md#visualizers) or a color visualization function, for example, [valueInterpolate](https://docs.planet.com/develop/evalscripts/utilities.md#valueinterpolate). ### Example 2: Sentinel–2 band B04 In this example, we want to output raw values of Sentinel–2 band 4. Our `evaluatePixel()` function looks like this: ``` function evaluatePixel(sample) { return [sample.B04]; } ``` If we request units: `REFLECTANCE`, the output values (columns 2, 3, 4 and 5) returned by [Browser](https://insights.planet.com/analyze/browser/) for different `sampleTypes` are: | Calculated Value | sampleType.AUTO | sampleType.UINT8 | sampleType.UINT16 | sampleType.FLOAT32 | | ---------------- | --------------- | ---------------- | ----------------- | ------------------ | | 0 | 0 | 0 | 0 | 0 | | 0.25 | 64 | 0 | 0 | 0.25 | | 0.5 | 128 | 1 | 1 | 0.5 | | 1 | 255 | 1 | 1 | 1 | | 1.05 | 255 | 1 | 1 | 1.05 | If we request units: `DN`, the output values returned by Sentinel Hub for different `sampleTypes` are: | Calculated Value | sampleType.AUTO | sampleType.UINT8 | sampleType.UINT16 | sampleType.FLOAT32 | | ---------------- | --------------- | ---------------- | ----------------- | ------------------ | | 0 | 0 | 0 | 0 | 0 | | 2500 | 255 | 255 | 2500 | 2500 | | 5000 | 255 | 255 | 5000 | 5000 | | 10000 | 255 | 255 | 10000 | 10000 | | 10500 | 255 | 255 | 10500 | 10500 | ### Example 3: Brightness Temperature Bands Here we output a Sentinel–3 SLSTR band F1 with typical values between 250–320 representing brightness temperature in Kelvin. The `evaluatePixel()` function is: ``` function evaluatePixel(sample) { return [sample.F1]; } ``` The output values (columns 2, 3, 4, 5) returned by [Browser](https://insights.planet.com/analyze/browser/) for different `sampleTypes` are: | Calculated Value | sampleType.AUTO | sampleType.UINT8 | sampleType.UINT16 | sampleType.FLOAT32 | | ---------------- | --------------- | ---------------- | ----------------- | ------------------ | | 250 | 255 | 250 | 250 | 250 | | 255 | 255 | 255 | 255 | 255 | | 275.3 | 255 | 255 | 275 | 275.3 | | 320 | 255 | 255 | 320 | 320 | Use `sampleType:"FLOAT32"` to return original values. If integer values are still acceptable for your application, use `sampleType:"UINT16"`. If you do not need values but a visualization, you can use `sampleType:"AUTO"`, but make sure to either: * map the values to the 0–1 interval in the `evaluatePixel()` function, for example: ``` function evaluatePixel(sample) { return [sample.F1 / 320]; } ``` * use a [visualizer](https://docs.planet.com/develop/evalscripts/utilities.md#visualizers) or a color visualization function, for example, [valueInterpolate](https://docs.planet.com/develop/evalscripts/utilities.md#valueinterpolate). ## Transparency Parts of the image can be made fully or partially transparent by including the fourth output channel, the [alpha channel](https://en.wikipedia.org/wiki/Alpha_compositing). The value 0 in the alpha channel makes a pixel fully transparent, while the maximum value in the alpha channel makes it fully opaque (not transparent). The values in between will make the pixel proportionally transparent. The maximum value in the alpha channel depends on an image bit depth as specified by [sampleType](https://docs.planet.com/develop/evalscripts/functions.md#sampletype): * for `sampleType` `AUTO` or `FLOAT32`: values in the alpha channel should be from the interval \[0, 1] * for `sampleType` `UINT8`: values in the alpha channel should be from the interval \[0, 255] * for `sampleType` `UINT16`: values in the alpha channel should be from the interval \[0, 65535] PNG and TIFF are output file formats that support transparency, while JPEG does not. ### Transparent `NoData` Pixels `NoData` pixels are identified by the value 0 in the `dataMask` band. In the following evalscript, if the wetness index (NDWI) is positive, the returned value will be 0, making water areas transparent and only land visible in the returned image. ``` //VERSION=3 function setup() { return { input: ['B02', 'B03', 'B04', 'B08'], output: { bands: 4 }, }; } function evaluatePixel(sample) { let NDWI = (sample.B03 - sample.B08) / (sample.B03 + sample.B08); let transparency = 0; if (NDWI < 0) { transparency = 1; } return [2.5 * sample.B04, 2.5 * sample.B03, 2.5 * sample.B02, transparency]; } ``` [Examine in the Browser](https://insights.planet.com/analyze/browser/?lat=45.7097\&lng=13.4258\&zoom=10\&time=2020-03-19\&preset=CUSTOM\&datasource=Sentinel-2%20L2A\&layers=B01,B02,B03\&evalscript=Ly9WRVJTSU9OPTMKZnVuY3Rpb24gc2V0dXAgKCkgewoJcmV0dXJuewoJCWlucHV0OlsiQjA0IiwgIkIwMyIsICJCMDIiLCAiZGF0YU1hc2siXSwKCQlvdXRwdXQ6e2JhbmRzOiA0fQoJfQkJCn0KCgpmdW5jdGlvbiBldmFsdWF0ZVBpeGVsKHNhbXBsZXMsc2NlbmVzKSB7ICAKCiAgCiAgcmV0dXJuIFtzYW1wbGVzLkIwNCozLjUsIHNhbXBsZXMuQjAzKjMuNSwgc2FtcGxlcy5CMDIqMy41LCBzYW1wbGVzLmRhdGFNYXNrXQp9) This approach works when the `AUTO` or `FLOAT32` sample types are being requested. To achieve the same transparency, scale the output values for other sample types. See the examples below for guidance. #### Transparent `NoData` pixels and sampleType: `UINT16` When using `sampleType` `UINT16`, the range of output values in an image becomes \[0, 65535]. The value 65535 must be returned in the alpha channel for pixels that should not be transparent, as shown in the example below. ``` //VERSION=3 function setup() { return { input: ['B04', 'B03', 'B02', 'dataMask'], output: { bands: 4, sampleType: 'UINT16' }, }; } function evaluatePixel(samples, scenes) { return [ samples.B04 * 3.5 * 65535, samples.B03 * 3.5 * 65535, samples.B02 * 3.5 * 65535, samples.dataMask * 65535, ]; } ``` #### Transparent `NoData` pixels and sampleType: `UINT8` The same logic applies to `sampleType` `UINT8`, except that the range of output values in this case is \[0, 255]. The same evalscript as above but for `UINT8`: ``` //VERSION=3 function setup() { return { input: ['B04', 'B03', 'B02', 'dataMask'], output: { bands: 4, sampleType: 'UINT8' }, }; } function evaluatePixel(samples, scenes) { return [ samples.B04 * 3.5 * 255, samples.B03 * 3.5 * 255, samples.B02 * 3.5 * 255, samples.dataMask * 255, ]; } ``` ### Transparent Data Pixels To use some other condition for turning pixels transparent, return the condition in the fourth channel and output four bands in the `setup()` function. The example below shows how to return the Sentinel-2 L1C [NDVI index](https://custom-scripts.sentinel-hub.com/sentinel-2/ndvi/) and values larger than 0.6 as transparent. This example also leaves the `NoData` pixels non-transparent and thus does not need to use the `dataMask` input band. ``` //VERSION=3 function setup() { return { input: ['B02', 'B03', 'B04', 'B08'], output: { bands: 4 }, }; } function evaluatePixel(samples, scenes) { var NDVI = (samples.B08 - samples.B04) / (samples.B08 + samples.B04); return [samples.B04 * 2.5, samples.B03 * 2.5, samples.B02 * 2.5, NDVI < 0.6]; } ``` ## Data Mask Evalscripts allow control over which parts (pixels) of the image to return. This way, parts with `NoData` can be removed. The setup function allows a user to request `dataMask` as an input array element and then use it in the `evaluatePixel` function in the same manner as any other input band. #### General notes `dataMask` has a value of 0 for `NoData` pixels and 1 elsewhere. What `NoData` means: * All pixels that lie outside of the requested polygon (if specified). * All pixels where no source data was found. * All pixels where there is data explicitly set to the `NoData` value. All `NoData` pixels, as defined above, have a `dataMask` value of 0. All band values for these pixels are also 0, except for Landsat data collections, where band values for `NoData` pixels are NaN. `NoData` pixels are treated like any other in the evalscript. Their value is applied to the evalscript like any other pixel. For example, `return [sample.B04*sample.B03]` returns 0 for `NoData` pixels, while `return [sample.B04/sample.B03]` would return "Infinity" (if `sampleType` is `FLOAT32`) due to division by zero or "NaN." To treat `NoData` pixels differently, they should be handled explicitly in evalscripts. See the examples below. #### Example 1: Assign an arbitrary value (99) to `NoData` pixels ``` //VERSION=3 function setup() { return { input: ['B02', 'B03', 'B04', 'dataMask'], output: { bands: 3 }, }; } function evaluatePixel(sample) { if (sample.dataMask == 1) { return [2.5 * sample.B04, 2.5 * sample.B03, 2.5 * sample.B02]; } else { return [99, 99, 99]; } } ``` #### Example 2: Use values in `dataMask` as the transparency band note To use this example, set the `output.responses.format.type` parameter of your process API request to `image/png` or `image/tiff`. The PNG format will automatically interpret the fourth band as transparency. ``` //VERSION=3 function setup() { return { input: ['B02', 'B03', 'B04', 'dataMask'], output: { bands: 4 }, }; } function evaluatePixel(sample) { return [ 2.5 * sample.B04, 2.5 * sample.B03, 2.5 * sample.B02, sample.dataMask, ]; } ``` ## Working with Metadata in Evalscripts Metadata provided in raster format is available as additional bands in the collection. Like any other input band, this metadata can be accessed and processed in evalscripts. Basic examples and metadata are listed in the [public data section](https://docs.planet.com/data/public-data.md) for each data collection (for example, [sunAzimuthAngles](https://docs.planet.com/data/public-data/copernicus/sentinel-2.md#available-bands-and-data)). ### Check Which Metadata is Available Metadata is stored in two objects, which are called [`inputMetadata`](https://docs.planet.com/develop/evalscripts/functions.md#inputmetadata) and [`scenes`](https://docs.planet.com/develop/evalscripts/functions.md#scenes). The properties of the `scenes` object can be different depending on the selection of: * mosaicking (for example, `ORBIT` or `TILE`) * data collection (for example, Sentinel-2 L2A, Sentinel-1, Sentinel-5p) * function in the evalscript (for example, `evaluatePixel`, `preProcessScenes`, `updateOutputMetadata`) A convenient way to check which metadata is available to be requested in scenes is to write all object properties to the userdata.json file. This basic [example](https://docs.planet.com/data/public-data/copernicus/sentinel-2/examples.md#true-color-and-metadata-multi-part-response-geotiff-and-json) shows how to do this with the Processing API. ### Properties of Scenes Object and Mosaicking `ORBIT` This example shows: * Accessing metadata when mosaicking is `ORBIT` using `scenes.orbits` * Passing metadata from `scenes` to the userdata.json file using `outputMetadata.userData` in `updateOutputMetadata `function ``` evalscript = """ //VERSION=3 function setup() { return { input: ["B02", "dataMask"], mosaicking: Mosaicking.ORBIT, output: { id: "default", bands: 1 } } } function evaluatePixel(samples, scenes, inputMetadata, customData, outputMetadata) { //Average value of band B02 based on the requested scenes var sumOfValidSamplesB02 = 0 var numberOfValidSamples = 0 for (i = 0; i < samples.length; i++) { var sample = samples[i] if (sample.dataMask == 1){ sumOfValidSamplesB02 += sample.B02 numberOfValidSamples += 1 } } return [sumOfValidSamplesB02 / numberOfValidSamples] } function updateOutputMetadata(scenes, inputMetadata, outputMetadata) { outputMetadata.userData = { "inputMetadata": inputMetadata } outputMetadata.userData["orbits"] = scenes.orbits } """ request = { "input": { "bounds": { "bbox": [13.8, 45.8, 13.9, 45.9] }, "data": [{ "type": "sentinel-2-l1c", "dataFilter": { "timeRange": { "from": "2020-12-01T00:00:00Z", "to": "2020-12-06T23:59:59Z" } } }] }, "output": { "responses": [{ "identifier": "default", "format": { "type": "image/tiff" } }, { "identifier": "userdata", "format": { "type": "application/json" } } ] }, "evalscript": evalscript } ``` ### Properties of Scenes Object and Mosaicking `TILE` This example shows how to: * Access scenes metadata when mosaicking is `TILE` using `scenes.tiles` and writing it to the userdata.json file * Calculate a maximum value of band B02 and writing it to the userdata.json file. note Note that a global variable `maxValueB02` is used to assign a value to it in the `evaluatePixel` function but not to write its value to metadata in the `updateOutputMetadata` function. The advantage of this approach is that `maxValueB02` is written to metadata only once and not for each output pixel. ``` evalscript = """ //VERSION=3 function setup() { return { input: ["B02", "dataMask"], mosaicking: Mosaicking.TILE, output: { id: "default", bands: 1 } } } var maxValueB02 = 0 function evaluatePixel(samples, scenes, inputMetadata, customData, outputMetadata) { //Average value of band B02 based on the requested tiles var sumOfValidSamplesB02 = 0 var numberOfValidSamples = 0 for (i = 0; i < samples.length; i++) { var sample = samples[i] if (sample.dataMask == 1){ sumOfValidSamplesB02 += sample.B02 numberOfValidSamples += 1 if (sample.B02 > maxValueB02){ maxValueB02 = sample.B02 } } } return [sumOfValidSamplesB02 / numberOfValidSamples] } function updateOutputMetadata(scenes, inputMetadata, outputMetadata) { outputMetadata.userData = { "tiles": scenes.tiles } outputMetadata.userData.maxValueB02 = maxValueB02 } """ request = { "input": { "bounds": { "bbox": [13.8, 45.8, 13.9, 45.9] }, "data": [{ "type": "sentinel-2-l1c", "dataFilter": { "timeRange": { "from": "2020-12-01T00:00:00Z", "to": "2020-12-06T23:59:59Z" } } }] }, "output": { "responses": [{ "identifier": "default", "format": { "type": "image/tiff" } }, { "identifier": "userdata", "format": { "type": "application/json" } } ] }, "evalscript": evalscript } ``` ### Output Metadata into `userdata.json` file This example shows how to write several pieces of information to the `userdata.json` file: * A version of the software used to process the data. This information comes from `inputMetadata`. * Dates when the data used for processing were acquired. This information comes from `scene.tiles`. * Values set by the user and used for processing, such as thresholds (for example, `ndviThreshold`) and an array of values (for example, `notAllowedDates`). * Dates of all available tiles before filtering out those acquired on dates given in the notAllowedDates array. These dates are listed in the `tilesPPSDates` property of `userData`. Note how to use the global variable tilesPPS: assign it a value in `preProcessScenes` and output it in the `updateOutputMetadata` function. * Dates of all tiles available after the filtering. These dates are listed in the `tilesDates` property of `userData`. * Description of the processing implemented in the evalscript and links to external resources. ``` evalscript = """ //VERSION=3 function setup() { return { input: ["B08", "B04", "dataMask"], mosaicking: Mosaicking.TILE, output: { id: "default", bands: 1 } } } // User's inputs var notAllowedDates = ["2020-12-06", "2020-12-09"] var ndviThreshold = 0.2 var tilesPPS = [] function preProcessScenes(collections) { tilesPPS = collections.scenes.tiles collections.scenes.tiles = collections.scenes.tiles.filter(function(tile) { var tileDate = tile.date.split("T")[0]; return !notAllowedDates.includes(tileDate); }) return collections } function evaluatePixel(samples, scenes, inputMetadata, customData, outputMetadata) { var valid_ndvi_sum = 0 var numberOfValidSamples = 0 for (i = 0; i < samples.length; i++) { var sample = samples[i] if (sample.dataMask == 1){ var ndvi = (sample.B08 - sample.B04)/(sample.B08 + sample.B04) if (ndvi <= ndviThreshold){ valid_ndvi_sum += ndvi numberOfValidSamples += 1 } } } return [valid_ndvi_sum / numberOfValidSamples] } function updateOutputMetadata(scenes, inputMetadata, outputMetadata) { outputMetadata.userData = { "inputMetadata.serviceVersion": inputMetadata.serviceVersion } outputMetadata.userData.description = "The evalscript calculates average ndvi " + "in a requested time period. Data collected on notAllowedDates is excluded. " + "ndvi values greater than ndviThreshold are excluded. " + "More about ndvi: https://www.indexdatabase.de/db/i-single.php?id=58." // Extract dates for all available tiles (before filtering) var tilePPSDates = [] for (i = 0; i < tilesPPS.length; i++){ tilePPSDates.push(tilesPPS[i].date) } outputMetadata.userData.tilesPPSDates = tilePPSDates // Extract dates for tiles after filtering out tiles with "notAllowedDates" var tileDates = [] for (i = 0; i < scenes.tiles.length; i++){ tileDates.push(scenes.tiles[i].date) } outputMetadata.userData.tilesDates = tileDates outputMetadata.userData.notAllowedDates = notAllowedDates outputMetadata.userData.ndviThreshold = ndviThreshold } """ request = { "input": { "bounds": { "bbox": [13.8, 45.8, 13.9, 45.9] }, "data": [{ "type": "sentinel-2-l1c", "dataFilter": { "timeRange": { "from": "2020-12-01T00:00:00Z", "to": "2020-12-15T23:59:59Z" } } }] }, "output": { "responses": [{ "identifier": "default", "format": { "type": "image/tiff" } }, { "identifier": "userdata", "format": { "type": "application/json" } } ] }, "evalscript": evalscript } ``` note You can download a Jupyter Notebook with all the examples [here](https://docs.planet.com/develop/evalscripts/metadata_sh_docs.ipynb). ## Additional Resources [📓Python Notebook - Interactive Intro to Evalscripts](https://github.com/planetlabs/notebooks/tree/master/jupyter-notebooks/workflows/introduction_to_evalscripts) [Learn how to write custom evalscripts](https://github.com/planetlabs/notebooks/tree/master/jupyter-notebooks/workflows/introduction_to_evalscripts) [📖Custom Scripts: Faster, Cheaper, Better!](https://medium.com/sentinel-hub/custom-scripts-faster-cheaper-better-83f73894658a) [Explore a blog on good scripting practices.](https://medium.com/sentinel-hub/custom-scripts-faster-cheaper-better-83f73894658a) [📖SampleType: what’s all the fuss about?](https://medium.com/sentinel-hub/sampletype-whats-all-the-fuss-about-d7348b4de647) [Explore a blog post on sampleType.](https://medium.com/sentinel-hub/sampletype-whats-all-the-fuss-about-d7348b4de647) [🎓Introduction to Custom Scripts on the Planet Insights Platform](https://university.planet.com/introduction-to-custom-scripts-on-the-planet-insights-platform?next=%2Fintroduction-to-custom-scripts-on-the-planet-insights-platform%2F2069666) [Check out the Jupyter notebooks tutorial on GitHub for using evalscripts.](https://university.planet.com/introduction-to-custom-scripts-on-the-planet-insights-platform?next=%2Fintroduction-to-custom-scripts-on-the-planet-insights-platform%2F2069666)