How to download LST from Sentinel Hub API

How to download sentinel 3 based LST from custom scripts. The first problem is when we make a layer in sentinel hub configuration utility, we dont have an option to choose two sources of data i.e OLCI and SLSTR. Pls help in correctly making this layer.

We have posted thi problem previously as well, but to no help till now.

Hi Jags, unfortunately using data fusion is still not possible when using the Configuration Utility so you would not be able to combine the data collections using this method.

You can still create an Processing API request using Data Fusion with the two data collections if you wish.


We are not able to apply this option as seen in the screenshot.
Our goal is to eventually download this LST from sentinel 3. if not through configuration utility, kindly suggest a way using WCS request

I have tried to generate a processing API as

This is script may only work with sentinelhub.version >= ‘3.4.0’

from sentinelhub import SentinelHubRequest, DataCollection, MimeType, CRS, BBox, SHConfig, Geometry

evalscript = “”"
//VERSION=3

function setup() {
return {
input: [
{
bands: [“S8”],
datasource:“DataCollection.SENTINEL3_SLSTR”
},
{
bands: [“B06”,“B08”,“B17”],
datasource:“DataCollection.SENTINEL3_OLCI”
}
],
output: [
{
id: “default”,
bands: 1,
sampleType: “FLOAT32”,
noDataValue: 0,
},
],
mosaicking: “SIMPLE”,
};
}

// VERSION 3

/**
This script is directly based on the Landsat-8 Land Surface Temperature Mapping script by Mohor Gartner
Land Surface Temperature (LST) Mapping Script | Sentinel Hub custom scripts
since the script uses Landsat TIRS B10 for brightness temperature
mapping and Landsat OLI NDVI to scale for emissivity, this can be followed using
Sentinel-3 SLSTR S08 and Sentinel-3 OLCI NDVI

in order to use this script you have to enable “use additional datasets (advanced)”
and set S-3 OLCI and S-3 SLSTR as the primary and additional dataset.

Aliases should be

  • Sentinel-3 OLCI=S3OLCI
  • Sentinel-3 SLSTR=S3SLSTR

STARTING OPTIONS
for analysis of one image (EO Browser), choose option=0. In case of MULTI-TEMPORAL analyis,
option values are following:
0 - outputs average LST in selected timeline (% of cloud coverage should be low, e.g. < 10%)
1 - outputs maximum LST in selected timeline (% of cloud coverage can be high)
2 - THIS OPTION IS CURRENTLY NOT FUNCTIONAL - outputs standard deviation LST in selected timeline;
minTemp and highTemp are overwritten with values 0 and 10 (% of cloud coverage should be low, e.g. < 5%)
*/
var option = 0;

// minimum and maximum values for output colour chart red to white for temperature in °C. Option 2 overwrites this selection!
var minC = 0;
var maxC = 50;

////INPUT DATA - FOR BETTER RESULTS, THE DATA SHOULD BE ADJUSTED
// NVDIs for bare soil and NDVIv for full vegetation
// Note: NVDIs for bare soil and NDVIv for full vegetation are needed to
// be evaluated for every scene. However in the custom script, default values are set regarding:
// https://profhorn.meteor.wisc.edu/wxwise/satmet/lesson3/ndvi.html
// https://www.researchgate.net/post/Can_anyone_help_me_to_define_a_range_of_NDVI_value_to_extract_bare_soil_pixels_for_Landsat_TM
// NVDIs=0.2, NDVIv=0.8
// other source suggests global values: NVDIs=0.2, NDVIv=0.5;
// https://www.researchgate.net/publication/296414003_Algorithm_for_Automated_Mapping_of_Land_Surface_Temperature_Using_LANDSAT_8_Satellite_Data
var NDVIs = 0.2;
var NDVIv = 0.8;

// emissivity
var waterE = 0.991;
var soilE = 0.966;
var vegetationE = 0.973;
//var buildingE=0.962;
var C = 0.009; //surface roughness, https://www.researchgate.net/publication/331047755_Land_Surface_Temperature_Retrieval_from_LANDSAT-8_Thermal_Infrared_Sensor_Data_and_Validation_with_Infrared_Thermometer_Camera

//central/mean wavelength in meters, Sentinel-3 SLSTR B08 (almost the same as Landsat B10)
var bCent = 0.000010854;

// rho =hc/sigma=PlanckCvelocityLight/BoltzmannC
var rho = 0.01438; // m K

//// visualization
// if result should be std dev (option=2), overwrite minMaxC.
if (option == 2) {
minC = 0;
maxC = 25;
}
let viz = ColorGradientVisualizer.createRedTemperature(minC, maxC);

//this is where you set up the evalscript to access the bands of the two datasets in the fusion

function setup() {
return {
input: [
{ datasource: “S3SLSTR”, bands: [“S8”] },
{ datasource: “S3OLCI”, bands: [“B06”, “B08”, “B17”] }],
output: [
{ id: “default”, bands: 3, sampleType: SampleType.AUTO }
],
mosaicking: “ORBIT”
}
}

//emissivity calc (Unchanged from Landsat script)
//https://www.researchgate.net/publication/296414003_Algorithm_for_Automated_Mapping_of_Land_Surface_Temperature_Using_LANDSAT_8_Satellite_Data
//(PDF) Investigating Land Surface Temperature Changes Using Landsat Data in Konya, Turkey | Osman ORhan - Academia.edu
function LSEcalc(NDVI, Pv) {
var LSE;
if (NDVI < 0) {
//water
LSE = waterE;
} else if (NDVI < NDVIs) {
//soil
LSE = soilE;
} else if (NDVI > NDVIv) {
//vegetation
LSE = vegetationE;
} else {
//mixtures of vegetation and soil
LSE = vegetationE * Pv + soilE * (1 - Pv) + C;
}
return LSE;
}

function evaluatePixel(samples) {
// starting values max, avg, stdev, reduce N, N for multi-temporal
var LSTmax = -999;
var LSTavg = 0;
var LSTstd = 0;
var reduceNavg = 0;
var N = samples.S3SLSTR.length;

//to caputure all values of one pixel for for whole timeline in mosaic order
var LSTarray = [];

// multi-temporal: loop all samples in selected timeline
for (let i = 0; i < N; i++) {
//// for LST S8
var Bi = samples.S3SLSTR[i].S8;
var B06i = samples.S3OLCI[i].B06;
var B08i = samples.S3OLCI[i].B08;
var B17i = samples.S3OLCI[i].B17;

// some images have errors, whole area is either B10<173K or B10>65000K. Also errors, where B06 and B17 =0. Therefore no processing if that happens, in addition for average and stdev calc, N has to be reduced!
if ((Bi > 173 && Bi < 65000) && (B06i > 0 && B08i > 0 && B17i > 0)) {
  // ok image
  //1 Kelvin to C
  var S8BTi = Bi - 273.15;
  //2 NDVI - Normalized Difference vegetation Index - based on this custom script: https://custom-scripts.sentinel-hub.com/sentinel-3/ndvi/
  var NDVIi = (B17i - B08i) / (B17i + B08i);
  //3 PV - proportional vegetation
  var PVi = Math.pow(((NDVIi - NDVIs) / (NDVIv - NDVIs)), 2);
  //4 LSE land surface emmisivity  
  var LSEi = LSEcalc(NDVIi, PVi);
  //5 LST
  var LSTi = (S8BTi / (1 + (((bCent * S8BTi) / rho) * Math.log(LSEi))));

  ////temporary calculation
  //avg
  LSTavg = LSTavg + LSTi;
  //max
  if (LSTi > LSTmax) { LSTmax = LSTi; }
  //array
  LSTarray.push(LSTi);
} else {
  // image NOT ok
  ++reduceNavg;
}

}
// correct N value if some images have errors and are not analysed
N = N - reduceNavg;

// calc final avg value
LSTavg = LSTavg / N;

// calc final stdev value
for (let i = 0; i < LSTarray.length; i++) {
LSTstd = LSTstd + (Math.pow(LSTarray[i] - LSTavg, 2));
}
LSTstd = (Math.pow(LSTstd / (LSTarray.length - 1), 0.5));

// WHICH LST to output, it depends on option variable: 0 for one image analysis (OE Browser); MULTI-TEMPORAL: 0->avg; 1->max; 2->stdev
let outLST = (option == 0)
? LSTavg
: (option == 1)
? LSTmax
: LSTstd;

//// output to image
return viz.process(outLST);
}

“”"
bbox = BBox(bbox=[75.866267, 30.516428, 75.90102, 30.545409], crs=CRS.WGS84)

request = SentinelHubRequest(
evalscript=evalscript,
input_data=[
SentinelHubRequest.input_data(
data_collection=DataCollection.SENTINEL3_SLSTR,
time_interval=(‘2023-07-01’, ‘2024-03-03’),
),
SentinelHubRequest.input_data(
data_collection=DataCollection.SENTINEL3_OLCI,
time_interval=(‘2023-07-01’, ‘2023-07-03’),
),
],
responses=[
SentinelHubRequest.output_response(‘default’, MimeType.TIFF),
],
bbox=bbox,
size=[3.3328097651607824, 3.22615016267937],
config=config
)

response = request.get_data()

But in the end I get an error

DownloadFailedException: Failed to download from:
https://creodias.sentinel-hub.com/oauth/token
with HTTPError:
503 Server Error: Service Unavailable for url: https://creodias.sentinel-hub.com/oauth/token
Server response: “503 Service Unavailable No server is available to handle this request.”

Hi,

All OGC services rely on creating a configuration using the configuration utility tool. In the example provided, you can follow the instructions below the evalscript to produce the layer in EO Browser but this needs to be manually configured when you are using EO Browser. A sharing link could be generated to share with colleagues though.

this example below will work for you:

curl -X POST https://creodias.sentinel-hub.com/api/v1/process \
 -H 'Content-Type: multipart/form-data' \
 -H 'Authorization: Bearer <access_token>' \
 --form-string 'request={
  "input": {
    "bounds": {
      "bbox": [
        12.44693,
        41.870072,
        12.541001,
        41.917096
      ]
    },
    "data": [
      {
        "dataFilter": {
          "timeRange": {
            "from": "2023-07-01T00:00:00Z",
            "to": "2023-07-02T23:59:59Z"
          }
        },
        "type": "sentinel-3-olci",
        "id": "S3OLCI"
      },
      {
        "dataFilter": {
          "timeRange": {
            "from": "2023-07-01T00:00:00Z",
            "to": "2023-07-02T23:59:59Z"
          }
        },
        "type": "sentinel-3-slstr",
        "id": "S3SLSTR"
      }
    ]
  },
  "output": {
    "width": 512,
    "height": 343.697,
    "responses": [
      {
        "identifier": "default",
        "format": {
          "type": "image/tiff"
        }
      }
    ]
  }
}' \
 --form-string 'evalscript=// VERSION 3

/**
  This script is directly based on the Landsat-8 Land Surface Temperature Mapping script by Mohor Gartner
  https://custom-scripts.sentinel-hub.com/landsat-8/land_surface_temperature_mapping/
  since the script uses Landsat TIRS B10 for brightness temperature 
  mapping and Landsat OLI NDVI to scale for emissivity, this can be followed using 
  Sentinel-3 SLSTR S08 and Sentinel-3 OLCI NDVI

  in order to use this script you have to enable "use additional datasets (advanced)"
  and set S-3 OLCI and S-3 SLSTR as the primary and additional dataset.

  Aliases should be 
   - Sentinel-3 OLCI=S3OLCI
   - Sentinel-3 SLSTR=S3SLSTR

  STARTING OPTIONS
  for analysis of one image (EO Browser), choose option=0. In case of MULTI-TEMPORAL analyis, 
  option values are following:
  0 - outputs average LST in selected timeline (% of cloud coverage should be low, e.g. < 10%)
  1 - outputs maximum LST in selected timeline (% of cloud coverage can be high)
  2 - THIS OPTION IS CURRENTLY NOT FUNCTIONAL - outputs standard deviation LST in selected timeline; 
      minTemp and highTemp are overwritten with values 0 and 10 (% of cloud coverage should be low, e.g. < 5%)
*/
var option = 0;

// minimum and maximum values for output colour chart red to white for temperature in °C. Option 2 overwrites this selection!
var minC = 0;
var maxC = 50;


////INPUT DATA - FOR BETTER RESULTS, THE DATA SHOULD BE ADJUSTED
// NVDIs for bare soil and NDVIv for full vegetation
// Note: NVDIs for bare soil and NDVIv for full vegetation are needed to 
//       be evaluated for every scene. However in the custom script, default values are set regarding:
// https://profhorn.meteor.wisc.edu/wxwise/satmet/lesson3/ndvi.html 
// https://www.researchgate.net/post/Can_anyone_help_me_to_define_a_range_of_NDVI_value_to_extract_bare_soil_pixels_for_Landsat_TM
// NVDIs=0.2, NDVIv=0.8
// other source suggests global values: NVDIs=0.2, NDVIv=0.5; 
// https://www.researchgate.net/publication/296414003_Algorithm_for_Automated_Mapping_of_Land_Surface_Temperature_Using_LANDSAT_8_Satellite_Data
var NDVIs = 0.2;
var NDVIv = 0.8;

// emissivity
var waterE = 0.991;
var soilE = 0.966;
var vegetationE = 0.973;
//var buildingE=0.962;
var C = 0.009; //surface roughness, https://www.researchgate.net/publication/331047755_Land_Surface_Temperature_Retrieval_from_LANDSAT-8_Thermal_Infrared_Sensor_Data_and_Validation_with_Infrared_Thermometer_Camera

//central/mean wavelength in meters, Sentinel-3 SLSTR B08 (almost the same as Landsat B10)
var bCent = 0.000010854;

// rho =h*c/sigma=PlanckC*velocityLight/BoltzmannC
var rho = 0.01438; // m K

//// visualization
// if result should be std dev (option=2), overwrite minMaxC.
if (option == 2) {
  minC = 0;
  maxC = 25;
}
let viz = ColorGradientVisualizer.createRedTemperature(minC, maxC);

//this is where you set up the evalscript to access the bands of the two datasets in the fusion

function setup() {
  return {
    input: [
      { datasource: "S3SLSTR", bands: ["S8"] },
      { datasource: "S3OLCI", bands: ["B06", "B08", "B17"] }],
    output: [
      { id: "default", bands: 3, sampleType: SampleType.AUTO }
    ],
    mosaicking: "ORBIT"
  }
}


//emissivity calc (Unchanged from Landsat script)
//https://www.researchgate.net/publication/296414003_Algorithm_for_Automated_Mapping_of_Land_Surface_Temperature_Using_LANDSAT_8_Satellite_Data
//https://www.academia.edu/27239873/Investigating_Land_Surface_Temperature_Changes_Using_Landsat_Data_in_Konya_Turkey
function LSEcalc(NDVI, Pv) {
  var LSE;
  if (NDVI < 0) {
    //water
    LSE = waterE;
  } else if (NDVI < NDVIs) {
    //soil
    LSE = soilE;
  } else if (NDVI > NDVIv) {
    //vegetation
    LSE = vegetationE;
  } else {
    //mixtures of vegetation and soil
    LSE = vegetationE * Pv + soilE * (1 - Pv) + C;
  }
  return LSE;
}

function evaluatePixel(samples) {
  // starting values max, avg, stdev, reduce N, N for multi-temporal
  var LSTmax = -999;
  var LSTavg = 0;
  var LSTstd = 0;
  var reduceNavg = 0;
  var N = samples.S3SLSTR.length;

  //to caputure all values of one pixel for for whole timeline in mosaic order
  var LSTarray = [];

  // multi-temporal: loop all samples in selected timeline
  for (let i = 0; i < N; i++) {
    //// for LST S8
    var Bi = samples.S3SLSTR[i].S8;
    var B06i = samples.S3OLCI[i].B06;
    var B08i = samples.S3OLCI[i].B08;
    var B17i = samples.S3OLCI[i].B17;

    // some images have errors, whole area is either B10<173K or B10>65000K. Also errors, where B06 and B17 =0. Therefore no processing if that happens, in addition for average and stdev calc, N has to be reduced!
    if ((Bi > 173 && Bi < 65000) && (B06i > 0 && B08i > 0 && B17i > 0)) {
      // ok image
      //1 Kelvin to C
      var S8BTi = Bi - 273.15;
      //2 NDVI - Normalized Difference vegetation Index - based on this custom script: https://custom-scripts.sentinel-hub.com/sentinel-3/ndvi/
      var NDVIi = (B17i - B08i) / (B17i + B08i);
      //3 PV - proportional vegetation
      var PVi = Math.pow(((NDVIi - NDVIs) / (NDVIv - NDVIs)), 2);
      //4 LSE land surface emmisivity  
      var LSEi = LSEcalc(NDVIi, PVi);
      //5 LST
      var LSTi = (S8BTi / (1 + (((bCent * S8BTi) / rho) * Math.log(LSEi))));

      ////temporary calculation
      //avg
      LSTavg = LSTavg + LSTi;
      //max
      if (LSTi > LSTmax) { LSTmax = LSTi; }
      //array
      LSTarray.push(LSTi);
    } else {
      // image NOT ok
      ++reduceNavg;
    }
  }
  // correct N value if some images have errors and are not analysed
  N = N - reduceNavg;

  // calc final avg value
  LSTavg = LSTavg / N;

  // calc final stdev value
  for (let i = 0; i < LSTarray.length; i++) {
    LSTstd = LSTstd + (Math.pow(LSTarray[i] - LSTavg, 2));
  }
  LSTstd = (Math.pow(LSTstd / (LSTarray.length - 1), 0.5));

  // WHICH LST to output, it depends on option variable: 0 for one image analysis (OE Browser); MULTI-TEMPORAL: 0->avg; 1->max; 2->stdev
  let outLST = (option == 0)
    ? LSTavg
    : (option == 1)
      ? LSTmax
      : LSTstd;

  //// output to image
  return viz.process(outLST);
}'

Hi William,

It’s a postman link.

Could you help on generating the Python request? because this will not work on Jupyter notebook

I am not working on EO browser but Jupyter notebook to generate results through API

You can parse the Curl Request into Request Builder. You can then convert it into either a Python Request or a SH-PY request.

I have posted that screen shot only earlier and it shows error 503

I have used this sh-py request and in the end it says download failed error:

This is script may only work with sentinelhub.version >= ‘3.4.0’

from sentinelhub import SentinelHubRequest, DataCollection, MimeType, CRS, BBox, SHConfig, Geometry

Credentials

config = SHConfig()
config.sh_client_id = ‘xxxxx’
config.sh_client_secret = ‘xxxxx’

evalscript = “”"
// VERSION 3

var option = 0;
var minC = 0;
var maxC = 50;
var NDVIs = 0.2;
var NDVIv = 0.8;
var waterE = 0.991;
var soilE = 0.966;
var vegetationE = 0.973;
var C = 0.009;
var bCent = 0.000010854;
var rho = 0.01438;

if (option == 2) {
minC = 0;
maxC = 25;
}
let viz = ColorGradientVisualizer.createRedTemperature(minC, maxC);

function setup() {
return {
input: [
{ datasource: “S3SLSTR”, bands: [“S8”] },
{ datasource: “S3OLCI”, bands: [“B06”, “B08”, “B17”] }],
output: [
{ id: “default”, bands: 3, sampleType: SampleType.AUTO }
],
mosaicking: “ORBIT”
}
}

function LSEcalc(NDVI, Pv) {
var LSE;
if (NDVI < 0) {

LSE = waterE;

} else if (NDVI < NDVIs) {

LSE = soilE;

} else if (NDVI > NDVIv) {

LSE = vegetationE;

} else {

LSE = vegetationE * Pv + soilE * (1 - Pv) + C;

}
return LSE;
}

function evaluatePixel(samples) {

var LSTmax = -999;
var LSTavg = 0;
var LSTstd = 0;
var reduceNavg = 0;
var N = samples.S3SLSTR.length;

var LSTarray = [];

for (let i = 0; i < N; i++) {

var Bi = samples.S3SLSTR[i].S8;
var B06i = samples.S3OLCI[i].B06;
var B08i = samples.S3OLCI[i].B08;
var B17i = samples.S3OLCI[i].B17;


if ((Bi > 173 && Bi < 65000) && (B06i > 0 && B08i > 0 && B17i > 0)) {
 
  var S8BTi = Bi - 273.15;
 
  var NDVIi = (B17i - B08i) / (B17i + B08i);
 
  var PVi = Math.pow(((NDVIi - NDVIs) / (NDVIv - NDVIs)), 2);
  
  var LSEi = LSEcalc(NDVIi, PVi);
 
  var LSTi = (S8BTi / (1 + (((bCent * S8BTi) / rho) * Math.log(LSEi))));

 
  LSTavg = LSTavg + LSTi;
 
  if (LSTi > LSTmax) { LSTmax = LSTi; }
 
  LSTarray.push(LSTi);
} else {
 
  ++reduceNavg;
}

}

N = N - reduceNavg;

LSTavg = LSTavg / N;

for (let i = 0; i < LSTarray.length; i++) {
LSTstd = LSTstd + (Math.pow(LSTarray[i] - LSTavg, 2));
}
LSTstd = (Math.pow(LSTstd / (LSTarray.length - 1), 0.5));

let outLST = (option == 0)
? LSTavg
: (option == 1)
? LSTmax
: LSTstd;

return viz.process(outLST);
}
“”"
bbox = BBox(bbox=[75.851318, 30.49868, 75.922752, 30.555166], crs=CRS.WGS84)

request = SentinelHubRequest(
evalscript=evalscript,
input_data=[
SentinelHubRequest.input_data(
data_collection=DataCollection.SENTINEL3_SLSTR,
time_interval=(‘2023-08-30’, ‘2023-08-31’),
),
SentinelHubRequest.input_data(
data_collection=DataCollection.SENTINEL3_OLCI,
time_interval=(‘2023-08-30’, ‘2023-08-31’),
),
],
responses=[
SentinelHubRequest.output_response(‘default’, MimeType.TIFF),
],
bbox=bbox,
size=[6.851764938055562, 6.287992756948245],
config=config
)

response = request.get_data()

DownloadFailedException: Failed to download from:
https://creodias.sentinel-hub.com/oauth/token
with HTTPError:
503 Server Error: Service Unavailable for url: https://creodias.sentinel-hub.com/oauth/token
Server response: “503 Service Unavailable No server is available to handle this request.”

Hi, in the code block you shared you did not give the data collections identifiers which are required when using Data Fusion as documented here.

Here’s a working request:

request = SentinelHubRequest(
    evalscript=evalscript,
    input_data=[
        SentinelHubRequest.input_data(
            data_collection=DataCollection.SENTINEL3_OLCI,          
            identifier="S3OLCI",
            time_interval=('2023-06-01', '2023-06-08'),          
        ),
       SentinelHubRequest.input_data(
            data_collection=DataCollection.SENTINEL3_SLSTR,          
            identifier="S3SLSTR",
            time_interval=('2023-06-01', '2023-06-08'),          
        ),
    ],
    responses=[
        SentinelHubRequest.output_response('default', MimeType.JPG),
    ],
    bbox=bbox,
    size=[512, 343.697],
    config=config
)

I have successfully run the code, but in the end the error is that credits service is unavailable. Is there something we can do about this?

Please check your method for authentication. The URL you are using to obtain a token is not correct. It should be https://services.sentinel-hub.com and not https://creodias.sentinel-hub.com

The docs on this can be found here and specifically for SH-PY here.

Thanks William, I was able to download LST.

Just for understanding the final output number range from 0.67 to 0.70. Since it’s a temperature layer it could be either in celsius or kelvin. Do you know the conversion factor of these values to visualise in temperature units?

Reading the custom scripts page, it is in Celsius I believe. For more information on the bands, please read through the docs [1][2]