Sentinel-1 data access methods (x-cuben aws, ...)

Hi,

I am new to SentinelHub and trying to figure out the best way to retrieve data. My first attempt was to use xcube-sh with the following code:

cube_config = CubeConfig(dataset_name='S1GRD',
   band_names=['VV', 'VH', 'localIncidenceAngle', 'scatteringArea', 'shadowMask'],
   tile_size=img_size,
   crs = "http://www.opengis.net/def/crs/EPSG/0/4326",
   spatial_res=spatial_res,           
   bbox = bbox,      
   time_range = time_interval,
   time_period='2D'
)
cube = open_cube(cube_config)

With this method, I have no problem fetching cube.VH.values but get an error (error 400: Bad request …) when trying to fetch cube.VV.values. I’ve checked on EObrowser and VV data exist for the time range and zone fetched so I have trouble understanding the issue.

Also I don’t really know if the data fetched is orthorectified or not.

Therefore I have 2 questions:
→ is there a reason why cube.VV.values could failed and not cube.VH.values ? Is there a way to know if the data is orthorectified or not ?
→ If I use an approach that doesn’t use xcube-sh, what would be the correct way to retrieve sentinel-1 data that is orthorectified (with AWS or WmsRequest for example) ?

I’ve seen an example here: Sentinel Hub OGC services — Sentinel Hub 3.9.0 documentation so this may be a good start but I don’t understand why the evaluatePixel function returns [sample.VV, 2 * sample.VH, sample.VV / sample.VH / 100.0, sample.dataMask] instead of something like [sample.VV, sample.VH, sample.dataMask]

Thank you very much in advance

PS: My AOI is the border between Russia and China.

Hi @julietteC ,

It seems that your code is trying to fetch VV from partial dual product VH, which for sure there’s no VV is unavailable in this product. Would you mind providing the parameters you used to configure the cube in your code, i.e., tile_size, spatial_res, bbox and timerange? I will try to reproduce the error and investigate the issue.

The current cube configuration fetches the data without orthorectification. I’m currently investigating how to request orthorectified data with cube and will bet back to you soon.

Regarding the second question, Sentinel Hub offers different APIs which allow you access Sentinel-1 data. One of the easiest method is via our Processing API. You could start with Requests builder, which is a user friendly GUI for the APIs. Here is a step-to-step guide for the new users of Sentinel Hub services. By copying this example curl request to Requests Builder, you’ll see how to set up the processing parameters for orthorectified Sentinel-1 data. I would also suggest having a look at the Processing API webinar, which should give you a general ideal what our services are about.

The OGC services you mentioned also provide access to the data. It allows you to access the data via sending a HTTP request. You could try out the service here. The return of the evaluatePixel function you mentioned in the example is simply for visualisation. You can definitely return sample.VV and sample.VH to obtain the original data.

Last but not least, the documentation to which you point also contains a lot of examples. It is the official python interface of Sentinel Hub services and should be useful if you’re more familiar with python.

1 Like

Hello,

Thank you for your answer !
Here are my exact parameters:

tile_size = (512,512)
spatial_res = 0.00018
bbox = (133.8, 48.2, 134.3, 48.4) 
time_interval = ["2017-04-07", "2021-12-31"].

I indeed assumed it was the data without orthorectification but couldn’t find a way to get the orthorectified one, if you find a way please let me know !

I’ve looked at the request builder and I think I understand better the way it works but I also realized that my query may generate way to much requests for my current subscription plan so I’ve been trying to find good practices in order to formulate my query in a way that saves processing and request units.

I looked at the examples provided and I actually have a follow up question: in the examples given, it seems like the correct way to retrieve data over several timestamps is to create a list of threads for specific dates and request them one after the other, is there a way to request several dates within an interval instead ?
The reason for my remark is the following: looking at EO browser, it seems the revisit time is not always the same, so if I generate a list of dates with let’s say a 6 days interval, I might be quering a lot of empty dates and missing some.

In any case, thank you very much for your answer, I’ll check out the webinar right now !

Hi @julietteC ,

I tested with the exact same cube configuration to retrieve VV and VH data and both worked fine with me. Would you mind trying it again and see if it works for you? Below is the code snippet:

cube_config = CubeConfig(dataset_name='S1GRD',
   band_names=['VV', 'VH', 'localIncidenceAngle', 'scatteringArea', 'shadowMask'],
   tile_size=(512,512),
   crs="http://www.opengis.net/def/crs/EPSG/0/4326",
   spatial_res=0.00018,           
   bbox=(133.8, 48.2, 134.3, 48.4),      
   time_range=["2017-04-07", "2021-12-31"],
   time_period='2D'
)
cube = open_cube(cube_config)
cube.VV.sel(time="2017-04-21", method="nearest").plot.imshow(vmin=0, vmax=1)

If it still doesn’t work, you could try to create a new pair of Sentinel Hub OAuth credentials and use it in the code as below:

cube = open_cube(cube_config, client_id=<your_sh_client_id>, client_secret=<your_sh_client_secret>)

Then please share the last 4 digits of your sh_client_id with me and I will take a look at the log of your request to see if I can figure out what’s going wrong.

Regarding to your follow up questions, there is no need to generate a list of specific dates and make requests one after another to retrieve time series. In the example I shared, it specified a time range and use mosaicking SIMPLE (this is the default option which is not written in the example script). This mosaicking method will flatten the mosaicked image so only a single sample is obtained. There are for sure more than one acquisition in the specified time range. In this case, the mosaickingOrder defines which acquisition will be used. The default option is mostRecent, so with the example request you get the most recent data within the time range.

To obtain the time series, here is an example script which shows how to request for Sentinel-2 NDVI time series. We also have a webinar and a documentation for Evalscripts. I’d suggest going through these materials and it should give you a better understanding of Evalscripts!

Hi and thank you for your answer,
I went through the webinars and looked at the custom-script provided and then tried to come up with my own script (inspired by Normalized difference vegetation index time series | Sentinel-Hub custom scripts mostly). Because I’d like to use the orthorectified and terrain corrected product I can’t use xcube anymore so I tried using the request builder. My goal is to retrieve data every 12 days for the time interval specified, I would also like to have access to the date of acquisition of my data. Here is my script so far :

evalscript = """
//VERSION=3

function setup() {
  return {
    input: [
      {
        bands: ["VV","VH","localIncidenceAngle","shadowMask","dataMask"],                  
      }
    ],
    output: [
      {
        id: "default",
         bands: 4,
        sampleType: "AUTO",        
      },    
    ],
    mosaicking: "ORBIT",
  };
}

function preProcessScenes(collections) {
  var allowedDates = [];
  var start_date = new Date("2017-04-07")
  var end_date = new Date("2021-03-31")

  for(allowedDates,dt=start_date; dt<=end_date; dt.setDate(dt.getDate()+12)){
        allowedDates.push(new Date(dt));
    }
  collections.scenes.orbits = collections.scenes.orbits.filter(function (orbit) 
  {
    var orbitDateFrom = orbit.dateFrom.split("T")[0];
    return allowedDates.includes(orbitDateFrom);
  })
  return collections
}

function updateOutput(outputs, collection) {
      Object.values(outputs).forEach((output) => {
          output.bands = collection.scenes.length;
      });
  }

function updateOutputMetadata(scenes, outputMetadata) {
      var dds = [];
      for (i=0; i<scenes.length; i++){
        dds.push(scenes[i].date)
      }
      outputMetadata.userData = { "acquisition_dates":  JSON.stringify(dds) }
  }

function evaluatePixel(samples) {
    var n_observations = samples.length;
    let vv_values = new Array(n_observations).fill(0);
    let vh_values = new Array(n_observations).fill(0);
    let incidence_values = new Array(n_observations).fill(0);
    let shadowmask_values = new Array(n_observations).fill(0);
    for (let i = 0; i < n_observations; i++) {
      var sample = samples[i]
      if(sample.dataMask ==1){
        vv_values[i]= sample.VV
        vh_values[i] = sample.VH
        incidence_values[i]= sample.localIncidentAngle
        shadowmask_values[i]= sample.shadowMask
      }
    }

    return [vv_values, vh_values, incidence_values, shadowmask_values];
  }


"""
bbox = BBox(bbox=[133.805, 48.201, 134.285, 48.391], crs=CRS.WGS84)

request = SentinelHubRequest(
    evalscript=evalscript,
    data_folder = "datasets",
    input_data=[
        SentinelHubRequest.input_data(
            data_collection=DataCollection.SENTINEL1_IW_ASC,          
            time_interval=('2017-04-07', '2021-03-31'),          
            other_args={"dataFilter": {"resolution": "HIGH"},"processing": {"backCoeff": "GAMMA0_TERRAIN","orthorectify": True,"demInstance": "COPERNICUS_30"}}
        ),
    ],
    responses=[
        SentinelHubRequest.output_response('default', MimeType.TIFF),
    ],
    bbox=bbox,
    size=[512, 304.068],
    config=config
)

response = request.get_data(save_data=True)

but I get this error:

DownloadFailedException: Failed to download from:
https://services.sentinel-hub.com/api/v1/process
with HTTPError:
400 Client Error: Bad Request for url: https://services.sentinel-hub.com/api/v1/process
Server response: "{"status": 400, "reason": "Bad Request", "message": "Output with 0 bands is not valid.", "code": "COMMON_BAD_PAYLOAD"}"

I know the error comes from my script but I haven’t been able to find it yet, do you have any idea what could cause the output to have 0 bands ?

Thank you in advance !

Hi Juliette,

Is there a way to know in advance how many requests/PU is the script gonna consume? Or is the only way to try to calculate on my own ?

Based on the parameters of your request, you can calculate how many request/PU by looking at this documentation page. There isn’t yet a service that calculate this. You can also see typical conversion rates between PUs and km2 in the FAQ. And lastly a few tips on rate limiting.

if my script is indeed correct, it may be worth adding it to the custom-script list to help others

Custom scripts is a collaborative platform and we are always happy to see new contributions. See the dedicated page on how to add your script!

1 Like

Okay thank you, I’ll try to estimate the cost manually then.
Thank you for the link, I will publish my script if it works as expected.

For future readers, here is a correct implementation:

evalscript = """
//VERSION=3

var earliest_date = '2017-04-09'; var latest_date = '2017-04-30';
function setup() {
  return {
    input: [
      {
        bands: ["VV","VH","localIncidenceAngle","scatteringArea", "shadowMask","dataMask"],                  
      }
    ],
    output:[
        {
          id: "VV",
          sampleType: "FLOAT32",
          bands: 1
        },
        {
          id: "VH",
          sampleType: "FLOAT32",
          bands: 1
        },
        {
          id: "LocalIncidenceAngle",
          sampleType: "UINT8",
          bands: 1
        },
        {
          id: "ScatteringArea",
          sampleType: "UINT8",
          bands: 1
        },
        {
          id: "ShadowMask",
          sampleType: "UINT8",
          bands: 1
        }
    ],
      mosaicking : "ORBIT"
  };
}

function updateOutput(outputs, collection) {
  Object.values(outputs).forEach((output) => {
    output.bands = collection.scenes.length;
  });
}

function updateOutputMetadata(scenes, inputMetadata, outputMetadata) {
      var dds = [];
      for (i=0; i<scenes.length; i++){
        dds.push(scenes[i].date)
      }
      outputMetadata.userData = { "acquisition_dates":  JSON.stringify(dds) }
  }


function preProcessScenes (collections) {
  var allowedDates = []; 
  for(allowedDates,dt=new Date(earliest_date); dt<=new Date(latest_date); dt.setDate(dt.getDate()+12)){
    allowedDates.push(new Date(dt).toISOString().split('T')[0]);
  }
  collections.scenes.orbits = collections.scenes.orbits.filter(function (orbit) {
      var orbitDateFrom = orbit.dateFrom.split("T")[0];
      return allowedDates.includes(orbitDateFrom);
  })
  return collections
}

function evaluatePixel(samples) {
  var n_observations = samples.length;
  let vv = new Array(n_observations).fill(NaN);
  let vh = new Array(n_observations).fill(NaN);
  let localIncidenceAngle = new Array(n_observations).fill(NaN);
  let scatteringArea = new Array(n_observations).fill(NaN);
  let shadowMask = new Array(n_observations).fill(NaN);
    
  for(i=0; i<n_observations; i++){
    var sample = samples[i];
    if(sample.dataMask ==1){
      vv[i] = sample.VV ;
      vh[i] = sample.VH ;
      localIncidenceAngle[i] = sample.localIncidenceAngle ;
      scatteringArea[i] = sample.scatteringArea ;
      shadowMask[i] = sample.shadowMask ;
    }
  }
                       
    return {
      VV: vv,
      VH: vh,
      LocalIncidenceAngle: localIncidenceAngle,
      ScatteringArea: scatteringArea,
      ShadowMask: shadowMask
    };
  }

"""
bbox = BBox(bbox=[133.805, 48.201, 134.285, 48.391], crs=CRS.WGS84)

request = SentinelHubRequest(
    evalscript=evalscript,
    data_folder = "datasets",
    input_data=[
        SentinelHubRequest.input_data(
            data_collection=DataCollection.SENTINEL1_IW,          
            time_interval=('2017-04-09', '2017-04-30'),  
            mosaicking_order="leastRecent",
            other_args={"dataFilter": {"resolution": "HIGH"},"processing": {"backCoeff": "GAMMA0_TERRAIN","orthorectify": True,"demInstance": "COPERNICUS_30"}}
        ),
    ],
    responses=[
      SentinelHubRequest.output_response('VV', MimeType.TIFF),
      SentinelHubRequest.output_response('VH', MimeType.TIFF),
      SentinelHubRequest.output_response('LocalIncidenceAngle', MimeType.TIFF),
      SentinelHubRequest.output_response('ScatteringArea', MimeType.TIFF),
      SentinelHubRequest.output_response('ShadowMask', MimeType.TIFF),
      SentinelHubRequest.output_response('userdata', MimeType.JSON)
    ],
    bbox=bbox,
    size=[512, 304.068],
    config=config
)

I’ll try to add this script to the list of custom-scripts ASAP :slight_smile:

My last interrogation concerns this issue which was raised a couple of months ago here: Sentinel-1 Mosaicking order is always most recent, I’ll try to check on my side but if somebody from sentinel-hub can confirm whether the issue has been solved it would be great !

Hi @julietteC ,

Glad to see you make your scripts work and thank you for your effort sharing the script for others.

Regarding the issue you mentioned, it was a bug related to EO Browser and it won’t affect the output of your scripts if you’re directly interacting with APIs. If you want to make sure the order of your acquisition, you could also add the following script to your preProcessScenes function to sort the acquisitions:

collections.scenes.orbits.sort(function (s1, s2) {
    var date1 = new Date(s1.dateFrom);
    var date2 = new Date(s2.dateFrom);
    return date1 - date2})
1 Like

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