I am processing 2A -level Sentinel2 products (directly from Copernicus using the sentinelsat package in Python). Some of them contain clouds, and I want to mask those clouds. Since the cloud masks in the SCL (Scene Classification Layer) product that come with the 2A products from Copernicus are not great, I want to use masks generated by s2cloudless.

To apply the s2cloudless package, I need the corresponding 1C products because the algorithm only works on the 1C product. When I download the product metadata for my bounding box using sentinelsat's SentinelAPI 'query' function, I get about 6000 results (of which exactly half is 2A and the other half 1C). I do not understand however which 2A product corresponds to which 1C product (so essentially; which 2A product was produced from which 1C product). I need this so I know to which 2a product the cloud mask I produce using the 1C product will belong. The 2A products do have a 'level1cpdiidentifier', though these do not correspond to the 1C product identifiers and there is no description I could find that explains how I can use this identifier. Can anyone explain to me how I can best connect the 2A products to their 1C counterparts?

I've also tried directly downloading the pre-calculated s2cloudless masks from Earth Engine and sentinelhub for the L2A products I already have (I would like to do this without interrupting my current workflow). This did not seem to work without getting a paid subscription.

  • Is GEE asking you for a paid subscription? Is the footprint of your AOI too big? I'd use GEE python API for downloading the data. You can use a service account for avoiding authentication in each run
    – aldo_tapia
    Commented Jan 18, 2023 at 12:28

3 Answers 3


You are analyzing S2 L2A products and you want to use a S2 L1C output. I'd do it in two steps: 1) download S2L2A with sentinelsat. 2) Download S2L1C cloud mask from google earth eninge.

First step (downloading s2 L2A, producttype is the key for getting only L2A):

from sentinelsat import SentinelAPI, read_geojson, geojson_to_wkt
from datetime import date

api = SentinelAPI('user', 'pass', 'https://apihub.copernicus.eu/apihub')

footprint = geojson_to_wkt(read_geojson('some_area.geojson'))

products = api.query(footprint,
                     date=(date(yyyy, mm, dd),date(yyyy, mm, dd)),


Second step (downloading cloud mask from gee):

import ee
# ! pip install geetools if you don't have it
from geetools import batch 


sentinel = ee.ImageCollection("COPERNICUS/S2_CLOUD_PROBABILITY")

# coordinates of the polygon to extract
yourarea = ee.Geometry.Polygon(
    [[[x1, y1],
      [x2, y2],
      [x3, y3],
      [x4, y4],
      [x1, y1]]])

# date range
SrtDate = 'yyyy-mm-dd'
EndDate = 'yyyy-mm-dd'

collection = (sentinel

exported_images = batch.Export.imagecollection.toDrive(
    crs= 'EPSG:32XXX', # the CRS of your zone

Then you can merge both products

  • 1
    Thanks again. Do you have any tips on how to best merge the products? I am struggling with that. I was expecting there to be some product ID that would uniquely identify the corresponding mask from EE, but I can't find a unique one. Commented Mar 1, 2023 at 13:00
  • Yes, sensing date is a common characteristic between both images. If you list files, you can extract it with something like [val.split('_')[0] for val in s2clouds_filenames] and [val.split('_')[2] for val in s2l2a_filenames]. Then, depending on the library you are using, you can stack the cloud band to 10, 20 or 60m scene
    – aldo_tapia
    Commented Mar 1, 2023 at 17:27
  • Thank you for the tip! Sensing date is indeed common, though not unique. The relationship is N to N. I've also tried sensing date + productdiscriminator, but even then it is not unique. I am trying to add tile_id as well, though no results. Let's for example say I want the cloud mask from EE for ESA's id ='S2A_MSIL2A_20211231T105441_N0301_R051_T31UEV_20211231T135006'. These both result in nothing: imageCollection.filterMetadata('PRODUCT_ID', 'equals', id) or ee.Image('COPERNICUS/S2/20211231T105441_20211231T135006_T31UEV').getInfo(). Any tips? Commented Mar 2, 2023 at 9:50
  • Use sensing datetime (yyyymmddTHHMMSS date string) + tile. For instance, a S2SR band has this name: T19JBG_20210309T143731_B02_10m, the corresponding cloud band has this name: 20210302T144731_20210302T145301_T19JBG, using both JBG and 20210302T144731 you can assign each cloud image to SR scene
    – aldo_tapia
    Commented Mar 2, 2023 at 12:31
  • 1
    Maybe, just maybe, it turned out I was using the wrong CRS and therefore the filter failed and I didn't notice because it times out instead of returns nothing ;). Thank you so much for all your help! I will finish my code, and then post it here later with a reference to you for posterity. Commented Mar 8, 2023 at 9:30

The names for L1A and L2A products are very similar. Look for the L1C product which has the same aquisition datetime and tile, it should be sufficient

Here is the naming convention of S2 products: https://sentinels.copernicus.eu/web/sentinel/user-guides/sentinel-2-msi/naming-convention

For example, one L2A derived from the L1C:


Only the processing level changes in the name.


Both answers below are very helpful, and helped me understand the process better. I deducted the following viable methods based on aldo_tapia's suggestions. The following works well for me, but if someone has better methods please comment on my code or add an answer:

satimg_cloud = imageCollection.filter(
              ee.Filter.stringEndsWith('system:index', tileid)) \

The 'tileid' and 'datatakestarttime' are metadata you can find in your ESA image; the API returns that information (with those exact names) and are unique to your ESA satellite image. You can get the image in this collection using .first(), and the probabilities by selecting the cloud probability band specifically. Alternatively, you can also filter the collection beforehand on cloud probabilities only (for example using ee.ImageCollection('COPERNICUS/S2_CLOUD_PROBABILITY')).

You could also do the following after filtering the image collection per the first code snippet above, though this will include an additional server call with getInfo():

ee_img = ee.Image("COPERNICUS/S2_CLOUD_PROBABILITY/" + str(satimg_cloud.aggregate_array('system:index').getInfo()[0]))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.