Dear John,
How to set up a median composite script
With a few modifications your script works:
-
You have specified the mosaicking
parameter in the wrong place: it should be outside of output
in your setup
function (see documentation)
-
When using mosaicking: "ORBIT"
, samples
becomes an array that you need to loop through (as mentionned here)
Here is a working version if that helps:
function median(values) {
if (values.length === 0) return 0;
if (values.length === 1) return values[0];
values.sort(function(a, b) {
return a - b;
});
var half = Math.floor(values.length / 2);
return values[half];
}
function evaluatePixel(samples) {
// Initialise arrays
var VV_samples = [];
var VH_samples = [];
// Loop through orbits and add data
for (let i=0; i<samples.length; i++){
// Ignore noData
if (samples[i].dataMask != 0){
VV_samples.push(samples[i].VV);
VH_samples.push(samples[i].VH);
}
}
const factor = 65535;
if (VV_samples.length == 0){
var VV_median = [factor];
} else {
var VV_median = median(VV_samples) * factor;
}
if (VH_samples.length == 0){
var VH_median = [factor];
} else{
var VH_median = median(VH_samples) * factor;
}
return [VV_median, VH_median];
}
function setup() {
return {
input: [{
bands: [
"VV",
"VH",
"dataMask",
]
}],
output: {
bands: 2,
sampleType:"UINT16"
},
mosaicking: "ORBIT"
}
}
For your second question:
How to run the script for each quarter within a year (currently I’m planning to just call the function 4 times with different start and end dates
You can run the script for a whole year and return the quarterly medians by setting up the Evalscript. I have written an example for VV
below, you would just need to adapt it to do VH
as well. Disclaimer: there are probably more efficient ways to code this, I am just proposing a way that works for me. If any one has improvements, they are most welcome (for example you could optimise it to just have to set the year etc…)!
//VERSION=3
function setup() {
return {
input: [{
bands: [
"VV",
"dataMask",
]
}],
// You can add multiple outputs here (e.g. VV & VH)
output: {
id: "VV",
bands: 4,
sampleType:"UINT16",
},
mosaicking: "ORBIT"
};
}
function median(values) {
if (values.length === 0) return 0;
if (values.length === 1) return values[0];
values.sort(function(a, b) {
return a - b;
});
var half = Math.floor(values.length / 2);
return values[half];
}
function check_quarter(indate, q1, q2, q3, q4){
// Check if date is in one of the intervals
if (indate >= q1[0] && indate <= q1[1]){
return 1;
} else if (indate >= q2[0] && indate <= q2[1]){
return 2;
} else if (indate >= q3[0] && indate <= q3[1]){
return 3;
} else if (indate >= q4[0] && indate <= q4[1]){
return 4;
} else {
throw new Error("Date out of range!");
}
}
function evaluatePixel(samples, scenes) {
// Set date intervals
var q1 = [new Date("2020-01-01"), new Date("2020-03-31")];
var q2 = [new Date("2020-04-01"), new Date("2020-06-30")];
var q3 = [new Date("2020-07-01"), new Date("2020-09-30")];
var q4 = [new Date("2020-10-01"), new Date("2020-12-31")];
// Initialise object with arrays
var VV_samples = {q1: [], q2: [], q3:[], q4:[]};
// Loop through orbits and add data
for (var i = 0; i < samples.length; i++) {
// Get quarter
quarter = check_quarter(new Date(scenes[i].date), q1, q2, q3, q4)
// Assign to correct array
if (samples[i].dataMask !=0){
if (quarter == 1){
VV_samples.q1.push(samples[i].VV);
} else if (quarter == 2){
VV_samples.q2.push(samples[i].VV);
} else if (quarter == 3){
VV_samples.q3.push(samples[i].VV);
} else if (quarter == 4){
VV_samples.q4.push(samples[i].VV);
}
}
}
const factor = 65535;
// Make an object to contain median values
var VV_median = {};
// Compute median for each quarter
for (const key in VV_samples) {
if (VV_samples.hasOwnProperty(key)) {
if (VV_samples[key].length == 0){
VV_median[key] = [factor];
} else {
VV_median[key] = median(VV_samples[key]) * factor;
}
}
}
return {
VV: Object.values(VV_median)
}
}
Of course in your payload you will have to set the date correctly not to get an error. Below is an example:
curl -X POST https://services.sentinel-hub.com/api/v1/process
-H 'Content-Type: application/json'
-H 'Authorization: Bearer <my-token>'
-d '{
"input": {
"bounds": {
"bbox": [
12.452079,
41.860948,
12.462509,
41.868378
]
},
"data": [
{
"dataFilter": {
"timeRange": {
"from": "2020-01-01T00:00:00Z",
"to": "2020-12-31T23:59:59Z"
}
},
"type": "sentinel-1-grd"
}
]
},
"output": {
"width": 86.47203647638206,
"height": 82.71038165936085,
"responses": [
{
"identifier": "VV",
"format": {
"type": "image/tiff"
}
}
]
},
"evalscript": "//VERSION=3\nfunction setup() {\n return {\n input: [{\n bands: [\n \"VV\",\n \"dataMask\",\n ]\n }],\n // You can add multiple outputs here (e.g. VV & VH)\n output: {\n id: \"VV\",\n bands: 4,\n sampleType:\"UINT16\",\n },\n mosaicking: \"ORBIT\"\n };\n}\n\nfunction median(values) {\n if (values.length === 0) return 0;\n if (values.length === 1) return values[0];\n values.sort(function(a, b) {\n return a - b;\n });\n var half = Math.floor(values.length / 2);\n return values[half];\n}\n\nfunction check_quarter(indate, q1, q2, q3, q4){\n // Check if date is in one of the intervals\n if (indate >= q1[0] && indate <= q1[1]){\n return 1;\n } else if (indate >= q2[0] && indate <= q2[1]){\n return 2;\n } else if (indate >= q3[0] && indate <= q3[1]){\n return 3;\n } else if (indate >= q4[0] && indate <= q4[1]){\n return 4;\n } else {\n throw new Error(\"Date out of range!\");\n }\n}\n\nfunction evaluatePixel(samples, scenes) {\n // Set date intervals\n var q1 = [new Date(\"2020-01-01\"), new Date(\"2020-03-31\")];\n var q2 = [new Date(\"2020-04-01\"), new Date(\"2020-06-30\")];\n var q3 = [new Date(\"2020-07-01\"), new Date(\"2020-09-30\")];\n var q4 = [new Date(\"2020-10-01\"), new Date(\"2020-12-31\")];\n \n // Initialise object with arrays\n var VV_samples = {q1: [], q2: [], q3:[], q4:[]};\n\n // Loop through orbits and add data\n for (var i = 0; i < samples.length; i++) {\n // Get quarter\n quarter = check_quarter(new Date(scenes[i].date), q1, q2, q3, q4)\n // Assign to correct array\n if (samples[i].dataMask !=0){\n if (quarter == 1){\n VV_samples.q1.push(samples[i].VV);\n } else if (quarter == 2){\n VV_samples.q2.push(samples[i].VV);\n } else if (quarter == 3){\n VV_samples.q3.push(samples[i].VV);\n } else if (quarter == 4){\n VV_samples.q4.push(samples[i].VV);\n }\n }\n }\n\n const factor = 65535;\n\n // Make an object to contain median values\n var VV_median = {};\n \n // Compute median for each quarter\n for (const key in VV_samples) {\n if (VV_samples.hasOwnProperty(key)) {\n if (VV_samples[key].length == 0){\n VV_median[key] = [factor];\n } else {\n VV_median[key] = median(VV_samples[key]) * factor;\n }\n }\n }\n\n return {\n VV: Object.values(VV_median)\n }\n}"
}'
Let us know if you have any other questions!