Data availability with statistical API

Hi, I would like to retrive current multispectral statistical data from sentinel2 with a time granularity of an hour. Is it possible??
Actually I am recovering null data asking for vigour indexes (EVI, SAVI etc etc) in a period between now and now-1hour. If I ask for the same data in a longer period I get effective results.
Can someone help me?

Hi Davide,

Thanks for the question; are you aware that Sentinel-2 has a revisit time of 5 days? Therefore, it does not make sense to request data with a granularity of 1 hour. You can find out more about the sensor here.

Can you elaborate on your use case that requires this granularity?

I know that, it is fine to have the same data for approximately 5 days, but those are connected with other inputs that make every records different. It is also a coding need.
Thank you for the comment!

OK thanks for the clarification. If it is essential to have this granularity then some pre-knowledge of the acquisition time would need to be known. This can be retrieved from a Catalog API request. From this you could tailor your Statistical API request to a specific time period of 1 hour.

1 Like

Hi,

To reclarify this:

the aggregation intervals should be at least one day long as stated in the documentation. Apologies for any confusion that may have been caused.

1 Like

Hi,
thank you, that’s clear.
Anyway I’m still having an issue… I’m receiving an empy object or object with nan values, depending on the aggregation interval (always grater than 1 hour). These the API request details:

  • area: 40m square, Italy,
  • periodo: 1 day aggregation time, starting from 1 day before the API request to the instant of the API request
  • sentinel2 constellation

I tested several time aggregation and starting and interval day and time references.

I’m going to upload the subscription plan to obtain data from planetScope

What’s wrong??

Hi Davide,

Please share the code that you are using, so that we can replicate the issue that you are having.

Ok! Here my script, i’m working in a NodeRed environment.

function generateSquare(lat, lon) {
  // It generates a square of dimension 20x20
  var earthRadius = 6378137; // Earth radius, meters
  var dn = 10;  // 20/2, the input point is the center of the square
  var de = 10;  // 20/2, the input point is the center of the square
  // Square coordinates, in degree
  var dLat = (dn / earthRadius) * 180 / Math.PI;
  var dLon = (de / (earthRadius * Math.cos(Math.PI * lat / 180)) ) * 180 / Math.PI;
  return [lat - dLat, lon - dLon, lat + dLat, lon + dLon] // Bottom left and top right couples of coordinates
}
var coordinates = generateSquare(msg.lat, msg.lon)

var now = new Date()
now.setMinutes(0,0,0)
var to_ = now.toISOString()
now.setDate(now.getDate() - 1)
var from_ = now.toISOString()
var timeRange = {
  "from": from_,
  "to": to_
}
var evalscript = `function evaluatePixel(sample) {
    var L = 1.0 //function of the soil
    var G = 2.5
    var C1 = 6.0 //function of aereosol
    var C2 = 7.5 //function of atmosferic interferences
    var gndvi, ndwi, savi, evi, nmdi, ndmi;

    if (sample.dataMask === 1 && Number.isFinite(sample.B08)) {
      
      if (Number.isFinite(sample.B03)) {
        gndvi = [(sample.B08-sample.B03)/(sample.B08+sample.B03)]
        ndwi = [(sample.B03-sample.B08)/(sample.B03+sample.B08)]

      } else {
        gndvi = [null]
        ndwi = [null]
      }

      if (Number.isFinite(sample.B04)) {
        savi = [(sample.B08-sample.B04)*(1+L)/(sample.B08+sample.B04+L)]
        
        if (Number.isFinite(sample.B02)) {
          evi = [G*(sample.B08 - sample.B04)/(sample.B08+ C1*sample.B04- C2*sample.B02 + L)]
        } else {
          evi = [null]
        }

        if (Number.isFinite(sample.B12)) {
          nmdi = [(sample.B08-sample.B12)/(sample.B08+sample.B12+sample.B04)]
        } else {
          nmdi = [null]
        }

      } else {
        savi = [null]
        evi = [null]
        nmdi = [null]
      }

      if (Number.isFinite(sample.B12)) {
        ndmi =  [(sample.B08-sample.B12)/(sample.B08+sample.B12)]
        
      } else { 
        ndmi = [null] 
      }    
    } else {
      gndvi = [null]
      ndwi = [null]
      savi = [null]
      evi = [null]
      nmdi = [null]
      ndmi = [null]
    }
    return {
      gndvi: gndvi,   
      evi: evi,
      savi: savi,
      ndmi: ndmi,
      nmdi: nmdi,
      ndwi: ndwi
    }
  }`

var stats_request = {
  "input": {
    "bounds": {
      "bbox": coordinates,
    "properties": {
        "crs": "http://www.opengis.net/def/crs/EPSG/0/32633"
        }
    },
    "data": [
      {
        "type": "sentinel-2-l2a",
        "dataFilter":{
          "mosaickingOrder": "leastRecent"
        }
      }
    ]
  },
  "aggregation": {
    "timeRange": timeRange,
    "aggregationInterval": {
        "of": "P10D"
    },
    "evalscript": evalscript,
    "resx": 10,
    "resy": 10
  }
}

var headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'Authorization': 'Bearer '+ JSON.parse(msg.payload).access_token
}

NodeRed settings:

msg.url = "https://services.sentinel-hub.com/api/v1/statistics";
msg.method = "POST";
msg.payload = stats_request
msg.headers = headers
return msg;

Hi Davide,

I am not familiar with NodeRed, so this might be difficult to replicate. However, for me to test this I need an example that I can replicate. I can support you on using the Sentinel Hub APIs, but naturally have less understanding of your wider workflow. If you can provide a Statistical API request that includes the time range, aggregation period and area of interest then that would be appreciated.

Hi,
basically in this workflow nodered sustitute any other HTTP request library. Here the details:
The URL is https://services.sentinel-hub.com/api/v1/statistics
The HTTP method is POST
The headers is the var headers previously reported
The payload is instead the var stats_request previously reported.
Area of interest is a bbox as reported in the payload and computed with generate square
Time range is a json as reported in the payload, with from and to keys
Aggregation also is reported in the payload

Here my updated script. I’m still receving 0 or Nan depending on the aggregation time: short aggregation time (1 day) gives me 0, long aggregation time (10 days) give me NaN.
From the documentation this is related with no data, it suggests to use dataMask, just implemented.

So my question now is if it is possible to collect data with a short-medium aggregation time, referring to a small area, a few hunderd of meters square.
Input example:

  • 1 day aggeration time, 5 days aggregation time, 10 days aggregation time
  • lat 44.5541, lon 8.0572
  • area = 20mX20m

This is what I would like to do. If it is not possible where should I work on ?
Can planetScope satellite system help me?

function generateSquare(lat, lon) {
  // It generates a square of dimension 20x20
  var earthRadius = 6378137; // Earth radius, meters
  var dn = 10;  // 20/2, the input point is the center of the square
  var de = 10;  // 20/2, the input point is the center of the square
  // Square coordinates, in degree
  var dLat = (dn / earthRadius) * 180 / Math.PI;
  var dLon = (de / (earthRadius * Math.cos(Math.PI * lat / 180)) ) * 180 / Math.PI;
  return [lat - dLat, lon - dLon, lat + dLat, lon + dLon] // Bottom left and top right couples of coordinates
}
var coordinates = generateSquare(msg.lat, msg.lon)

var now = new Date()
now.setMinutes(0,0,0)
var to_ = now.toISOString()
now.setDate(now.getDate() - 1)
var from_ = now.toISOString()
var timeRange = {
  "from": from_,
  "to": to_
}
var evalscript = 
 `//VERSION=3
  function setup() {
    return {
      input: [{
        bands: [
          "B02",
          "B03",
          "B04",
          "B08",
          "B12",
          "dataMask"
        ]
      }],
      output: [
        {
          id: "gndvi",
          bands: 1,
          sampleType: "FLOAT32"
        },
        {
          id: "evi",
          bands: 1,
          sampleType: "FLOAT32"
        },
        {
          id: "savi",
          bands: 1,
          sampleType: "FLOAT32"
        },
        {
          id: "nmdi",
          bands: 1,
          sampleType: "FLOAT32"
        },
        {
          id: "ndmi",
          bands: 1,
          sampleType: "FLOAT32"
        },
        {
          id: "ndwi",
          bands: 1,
          sampleType: "FLOAT32"
        },
        {
          id: "dataMask",
          bands: 1
        }]
    }
  }

  function evaluatePixel(sample) {
    var L = 1.0 //function of the soil
    var G = 2.5
    var C1 = 6.0 //function of aereosol
    var C2 = 7.5 //function of atmosferic interferences
    var gndvi, ndwi, savi, evi, nmdi, ndmi
    if (sample.dataMask == 1) {
      if (Number.isFinite(sample.B08)) {
        if (Number.isFinite(sample.B03)) {
          gndvi = (sample.B08-sample.B03)/(sample.B08+sample.B03)
          ndwi = (sample.B03-sample.B08)/(sample.B03+sample.B08)
        } else {
          gndvi = -1
          ndwi = -1
        }
        if (Number.isFinite(sample.B04)) {
          savi = (sample.B08-sample.B04)*(1+L)/(sample.B08+sample.B04+L)
          if (Number.isFinite(sample.B02)) {
            evi = G*(sample.B08 - sample.B04)/(sample.B08+ C1*sample.B04- C2*sample.B02 + L)
          } else {
            evi = -1
          }
          if (Number.isFinite(sample.B12)) {
            nmdi = (sample.B08-sample.B12)/(sample.B08+sample.B12+sample.B04)
          } else {
            nmdi = -1
          }
        } else {
          savi = -1
          evi = -1
          nmdi = -1
        }
        if (Number.isFinite(sample.B12)) {
          ndmi = (sample.B08-sample.B12)/(sample.B08+sample.B12)
        } else { 
          ndmi = -1
        }
      } else {
        gndvi = -1
        ndwi = -1
        savi = -1
        evi = -1
        nmdi = -1
        ndmi = -1
      }
    } else {
      gndvi = -2
      ndwi = -2
      savi = -2
      evi = -2
      nmdi = -2
      ndmi = -2
    }
    
    return {
      gndvi: [gndvi],   
      evi: [evi],
      savi: [savi],
      ndmi: [ndmi],
      nmdi: [nmdi],
      ndwi: [ndwi],
      dataMask: [sample.dataMask]
    }
  }`

var payload= {
  "input": {
    "bounds": {
      "bbox": coordinates,
    "properties": {
        "crs": "http://www.opengis.net/def/crs/EPSG/0/32633"
        }
    },
    "data": [
      {
        "type": "sentinel-2-l2a",
        "dataFilter":{
          "mosaickingOrder": "leastRecent"
        }
      }
    ]
  },
  "aggregation": {
    "timeRange": timeRange,
    "aggregationInterval": {
        "of": "P10D"
    },
    "evalscript": evalscript,
    "resx": 10,
    "resy": 10
  }
}

var headers = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
  'Authorization': 'Bearer '+ JSON.parse(msg.payload).access_token
}

Hi Davide,

I have tested your evalscript, and this appears to work absolutely fine, returning statistics for all of the indices you are calculating. Have you tried testing your script with an AOI that is constant rather than generated by your function?

Hi,
I solved the problem… the issue was that I cannot ask data about 10 day closer to the actual date. This an example:

  • actual date 20-12-2023
  • 1 day aggregation time between 18-12-2023 and 19-12-2023, empty results
  • 1 day aggregation time between 9-12-2023 and 10-12-2023, successful
    Thanak you a lot for the support

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.