Sentinel-2 snow cover#

Import libraries#

# Sentinel Hub
from sentinelhub import (SHConfig, SentinelHubRequest, DataCollection, MimeType, CRS, BBox, bbox_to_dimensions, geometry)

# Geospatial libraries
import geopandas as gpd

from datetime import date

import folium

Sentinel-hub authentication#

config = SHConfig()
config.sh_client_id = %env SH_CLIENT_ID
config.sh_client_secret = %env SH_CLIENT_SECRET

Input definition#

Read the shapefile on which to compute the snow cover map and plot it

# Read the file with geopandas and convert it to EPSG:4326
aoi_df = gpd.read_file('ADO_DSC_ITH1_0025.geojson')
aoi_df = aoi_df.to_crs(4326)

# Create a basemap with folium
m = folium.Map(
    location=[aoi_df.to_crs(4326).iloc[0].geometry.centroid.y, aoi_df.to_crs(4326).iloc[0].geometry.centroid.x],
    zoom_start=11)

# Add the polygon to the map
geo_j = folium.GeoJson(data=aoi_df.to_json())
geo_j.add_to(m)

# Show the map
m
Make this Notebook Trusted to load map: File -> Trust Notebook

Select a date

start_date = date(2018, 4, 1)
end_date = date(2018, 4, 15)

Snowcover map generation#

Generate the snow cover map on the selected AOI and date from S2L1C

evalscript_snowcover = """

//VERSION=3

function setup() {
  return {
    input: ["B03", "B04", "B08", "B11", "CLM", "dataMask"],
    output: {bands: 1, sampleType: "UINT8"}
  };
}

function calc_snow_mask(NDVI, NDSI, B03){
 
  let si = (NDSI >= 0.4) ? 1 : (Math.abs(NDVI - 0.1) <= 0.025 ? 1 : 0);
  let br = B03 > 0.3;
 
  return si && br;
}

function evaluatePixel(sample) {
  
  // Calculate indices
  var NDSI = (sample.B03 - sample.B11) / (sample.B03 + sample.B11);
  var NDVI = (sample.B08 - sample.B04) / (sample.B08 + sample.B04);
  
  // Calculate snowmask
  var snow = calc_snow_mask(NDVI, NDSI, sample.B03);  
  
  // Create the snow mask with clouds
  if (sample.dataMask == 0){
      return [0];
  }
  else if (sample.CLM == 1){
      return [3];
  }
  else if (snow == 1){
      return [1];
  }
  else if (snow == 0){
      return [2];
  }
  

}
"""

aoi = geometry.Geometry(aoi_df.geometry.iloc[0], CRS.WGS84)

request_snowcover = SentinelHubRequest(
    evalscript=evalscript_snowcover,
    input_data=[
        SentinelHubRequest.input_data(
            data_collection=DataCollection.SENTINEL2_L1C,
            time_interval=(start_date, end_date),
        )
    ],
    responses=[SentinelHubRequest.output_response('default', MimeType.TIFF)],
    geometry=aoi,
    size=bbox_to_dimensions(aoi.bbox, resolution=100),
    config=config
)

snowcover = request_snowcover.get_data()
len(snowcover)
1

Generate the corresponding RGB image

# The evalscript for RGB map creation
evalscript_rgb = """
 //VERSION=3
function setup(){
  return{
    input: ["B02", "B03", "B04", "dataMask"],
    output: {bands: 4}
  }
}

function evaluatePixel(sample){
  // Set gain for visualisation
  let gain = 2.5;
  // Return RGB
  return [sample.B04 * gain, sample.B03 * gain, sample.B02 * gain, sample.dataMask];
}
"""
request_rgb = SentinelHubRequest(
    evalscript=evalscript_rgb,
    input_data=[
        SentinelHubRequest.input_data(
            data_collection=DataCollection.SENTINEL2_L1C,
            time_interval=(date, date),
        )],
    responses=[
        SentinelHubRequest.output_response('default', MimeType.TIFF)
    ],
    geometry=aoi,
    size=bbox_to_dimensions(aoi.bbox, resolution=10),
    config=config
)
rgb_response = request_rgb.get_data()
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/download/handlers.py:41, in fail_user_errors.<locals>.new_download_func(self, request)
     40 try:
---> 41     return download_func(self, request)
     42 except requests.HTTPError as exception:

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/download/sentinelhub_client.py:90, in SentinelHubDownloadClient._execute_download(self, request)
     88     continue
---> 90 response.raise_for_status()
     92 LOGGER.debug("Successful %s request to %s", request.request_type.value, request.url)

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/requests/models.py:1021, in Response.raise_for_status(self)
   1020 if http_error_msg:
-> 1021     raise HTTPError(http_error_msg, response=self)

HTTPError: 400 Client Error: Bad Request for url: https://services.sentinel-hub.com/api/v1/process

The above exception was the direct cause of the following exception:

DownloadFailedException                   Traceback (most recent call last)
Cell In[10], line 32
      2 evalscript_rgb = """
      3  //VERSION=3
      4 function setup(){
   (...)
     16 }
     17 """
     18 request_rgb = SentinelHubRequest(
     19     evalscript=evalscript_rgb,
     20     input_data=[
   (...)
     30     config=config
     31 )
---> 32 rgb_response = request_rgb.get_data()

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/base.py:110, in DataRequest.get_data(self, save_data, redownload, data_filter, max_threads, decode_data, raise_download_errors, show_progress)
     88 """Get requested data either by downloading it or by reading it from the disk (if it
     89 was previously downloaded and saved).
     90 
   (...)
    107     shape ``[height, width, channels]``.
    108 """
    109 self._preprocess_request(save_data, True)
--> 110 return self._execute_data_download(
    111     data_filter,
    112     redownload,
    113     max_threads,
    114     raise_download_errors,
    115     decode_data=decode_data,
    116     show_progress=show_progress,
    117 )

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/base.py:184, in DataRequest._execute_data_download(self, data_filter, redownload, max_threads, raise_download_errors, decode_data, show_progress)
    179     raise ValueError("data_filter parameter must be a list of indices")
    181 client = self.download_client_class(
    182     redownload=redownload, raise_download_errors=raise_download_errors, config=self.config
    183 )
--> 184 data_list = client.download(
    185     filtered_download_list, max_threads=max_threads, decode_data=decode_data, show_progress=show_progress
    186 )
    188 if is_repeating_filter:
    189     data_list = [copy.deepcopy(data_list[index]) for index in mapping_list]

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/download/sentinelhub_client.py:62, in SentinelHubDownloadClient.download(self, *args, **kwargs)
     60 self.lock = Lock()
     61 try:
---> 62     return super().download(*args, **kwargs)
     63 finally:
     64     self.lock = None

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/download/client.py:113, in DownloadClient.download(self, download_requests, max_threads, decode_data, show_progress)
    111     else:
    112         for future in as_completed(download_list):
--> 113             data_list[future_order[future]] = self._process_download_future(future)
    115 if isinstance(download_requests, DownloadRequest):
    116     return data_list[0]

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/download/client.py:126, in DownloadClient._process_download_future(self, future)
    124 if self.raise_download_errors:
    125     traceback = sys.exc_info()[2]
--> 126     raise download_exception.with_traceback(traceback)
    128 warnings.warn(str(download_exception), category=SHRuntimeWarning)
    129 return None

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/download/client.py:122, in DownloadClient._process_download_future(self, future)
    120 """Unpacks the future and correctly handles exceptions"""
    121 try:
--> 122     return future.result()
    123 except DownloadFailedException as download_exception:
    124     if self.raise_download_errors:

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/concurrent/futures/_base.py:439, in Future.result(self, timeout)
    437     raise CancelledError()
    438 elif self._state == FINISHED:
--> 439     return self.__get_result()
    441 self._condition.wait(timeout)
    443 if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/concurrent/futures/_base.py:391, in Future.__get_result(self)
    389 if self._exception:
    390     try:
--> 391         raise self._exception
    392     finally:
    393         # Break a reference cycle with the exception in self._exception
    394         self = None

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/concurrent/futures/thread.py:58, in _WorkItem.run(self)
     55     return
     57 try:
---> 58     result = self.fn(*self.args, **self.kwargs)
     59 except BaseException as exc:
     60     self.future.set_exception(exc)

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/download/client.py:133, in DownloadClient._single_download_decoded(self, request)
    131 def _single_download_decoded(self, request: DownloadRequest) -> Any:
    132     """Downloads a response and decodes it into data. By decoding a single response"""
--> 133     response = self._single_download(request)
    134     return None if response is None else response.decode()

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/download/client.py:146, in DownloadClient._single_download(self, request)
    144 no_local_data = self.redownload or response_path is None or not os.path.exists(response_path)
    145 if no_local_data:
--> 146     response = self._execute_download(request)
    147 else:
    148     if not request.return_data or response_path is None:

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/download/handlers.py:67, in retry_temporary_errors.<locals>.new_download_func(self, request)
     65 for attempt_num in range(download_attempts):
     66     try:
---> 67         return download_func(self, request)
     68     except requests.RequestException as exception:
     69         if not (
     70             _is_temporary_problem(exception)
     71             or (
   (...)
     74             )
     75         ):

File /home/conda/master-asi-conae/315434deb1b1e05eacacc41e1b0529b1837100f46402a32041d77ba54ae6c5f3-20230213-125825-013239-74-edc-2022.10-14/lib/python3.9/site-packages/sentinelhub/download/handlers.py:47, in fail_user_errors.<locals>.new_download_func(self, request)
     42 except requests.HTTPError as exception:
     43     if (
     44         exception.response.status_code < requests.status_codes.codes.INTERNAL_SERVER_ERROR
     45         and exception.response.status_code != requests.status_codes.codes.TOO_MANY_REQUESTS
     46     ):
---> 47         raise DownloadFailedException(
     48             _create_download_failed_message(exception, request.url), request_exception=exception
     49         ) from exception
     50     raise exception from exception

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": "Invalid request", "code": "COMMON_BAD_PAYLOAD", "errors": [{"parameter": "output->width", "invalidValue": 2881, "violation": "must be less than or equal to 2500", "description": "The request image width. Must be an integer between 1 and 2500. <br />*Only one pair of parameters \"width\"/\"height\" or \"resx\"/\"resy\" must be set at the same time.*"}, {"parameter": "output->height", "invalidValue": 3058, "violation": "must be less than or equal to 2500", "description": "The request image height. Must be an integer between 1 and 2500. <br />*Only one pair of parameters \"width\"/\"height\" or \"resx\"/\"resy\" must be set at the same time.*"}]}"

Show the result#

# Create a basemap with folium
m = folium.Map(
    location=aoi.bbox.middle[::-1],
    zoom_start=11)

# Color map for the snow cover (0 no data: transparent, 1 snow: white, 2 snow-free: green, 3 cloud: gray)
cmap = ((0, 0, 0, 0), (1, 1, 1, 1), (0, 0.6, 0.3, 1), (0.5, 0.5, 0.5, 1))

# Add the snow cover to the map
folium.raster_layers.ImageOverlay(
    snowcover[0],
    bounds=[aoi.bbox.lower_left[::-1], aoi.bbox.upper_right[::-1]],
    name='snow cover',
    colormap=lambda x: cmap[x]
).add_to(m)

# Add the Sentinel-2 RGB to the map
folium.raster_layers.ImageOverlay(
    rgb_response[0]/255,
    bounds=[aoi.bbox.lower_left[::-1], aoi.bbox.upper_right[::-1]],
    name='RGB',
).add_to(m)

# Add the layer control
folium.LayerControl().add_to(m)

# Show the map
m
snowcover
[array([[0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        ...,
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0],
        [0, 0, 0, ..., 0, 0, 0]], dtype=uint8)]
# Create a basemap with folium
m = folium.Map(
    location=aoi.bbox.middle[::-1],
    zoom_start=11)

# Color map for the snow cover (0 no data: transparent, 1 snow: white, 2 snow-free: green, 3 cloud: gray)
cmap = ((0, 0, 0, 0), (1, 1, 1, 1), (0, 0.6, 0.3, 1), (0.5, 0.5, 0.5, 1))

# Add the snow cover to the map
folium.raster_layers.ImageOverlay(
    snowcover[0],
    bounds=[aoi.bbox.lower_left[::-1], aoi.bbox.upper_right[::-1]],
    name='snow cover',
    colormap=lambda x: cmap[x]
).add_to(m)
<folium.raster_layers.ImageOverlay at 0x7ff35780afa0>
m
Make this Notebook Trusted to load map: File -> Trust Notebook