1""" 2Main module for collecting data 3""" 4 5import datetime 6import os 7import logging 8import copy 9from abc import ABC, abstractmethod 10 11from .config import SHConfig 12from .ogc import OgcImageService 13from .fis import FisService 14from .geopedia import GeopediaWmsService, GeopediaImageService 15from .aws import AwsProduct, AwsTile 16from .aws_safe import SafeProduct, SafeTile 17from .data_collections import handle_deprecated_data_source 18from .download import DownloadRequest, DownloadClient, AwsDownloadClient, SentinelHubDownloadClient 19from .os_utils import make_folder 20from .constants import MimeType, CustomUrlParam, ServiceType, CRS, HistogramType 21from .data_collections import DataCollection 22 23LOGGER = logging.getLogger(__name__) 24 25 26class DataRequest(ABC): 27 """ Abstract class for all Sentinel Hub data requests. 28 29 Every data request type can write the fetched data to disk and then read it again (and hence avoid the need to 30 download the same data again). 31 """ 32 def __init__(self, download_client_class, *, data_folder=None, config=None): 33 """ 34 :param download_client_class: A class implementing a download client 35 :type download_client_class: type 36 :param data_folder: location of the directory where the fetched data will be saved. 37 :type data_folder: str 38 :param config: A custom instance of config class to override parameters from the saved configuration. 39 :type config: SHConfig or None 40 """ 41 self.download_client_class = download_client_class 42 self.data_folder = data_folder 43 self.config = config or SHConfig() 44 45 self.download_list = [] 46 self.folder_list = [] 47 self.create_request() 48 49 @abstractmethod 50 def create_request(self): 51 """ An abstract method for logic of creating download requests 52 """ 53 raise NotImplementedError 54 55 def get_download_list(self): 56 """ 57 Returns a list of download requests for requested data. 58 59 :return: List of data to be downloaded 60 :rtype: list(sentinelhub.DownloadRequest) 61 """ 62 return self.download_list 63 64 def get_filename_list(self): 65 """ Returns a list of file names (or paths relative to `data_folder`) where the requested data will be saved 66 or read from, if it has already been downloaded and saved. 67 68 :return: A list of filenames 69 :rtype: list(str) 70 """ 71 return [request.get_relative_paths()[1] for request in self.download_list] 72 73 def get_url_list(self): 74 """ 75 Returns a list of urls for requested data. 76 77 :return: List of URLs from where data will be downloaded. 78 :rtype: list(str) 79 """ 80 return [request.url for request in self.download_list] 81 82 def is_valid_request(self): 83 """ Checks if initialized class instance successfully prepared a list of items to download 84 85 :return: `True` if request is valid and `False` otherwise 86 :rtype: bool 87 """ 88 return isinstance(self.download_list, list) and \ 89 all(isinstance(request, DownloadRequest) for request in self.download_list) 90 91 def get_data(self, *, save_data=False, redownload=False, data_filter=None, max_threads=None, 92 decode_data=True, raise_download_errors=True): 93 """ Get requested data either by downloading it or by reading it from the disk (if it 94 was previously downloaded and saved). 95 96 :param save_data: flag to turn on/off saving of data to disk. Default is `False`. 97 :type save_data: bool 98 :param redownload: if `True`, download again the requested data even though it's already saved to disk. 99 Default is `False`, do not download if data is already available on disk. 100 :type redownload: bool 101 :param data_filter: Used to specify which items will be returned by the method and in which order. E.g. with 102 ``data_filter=[0, 2, -1]`` the method will return only 1st, 3rd and last item. Default filter is `None`. 103 :type data_filter: list(int) or None 104 :param max_threads: Maximum number of threads to be used for download in parallel. The default is 105 `max_threads=None` which will use the number of processors on the system multiplied by 5. 106 :type max_threads: int or None 107 :param decode_data: If `True` (default) it decodes data (e.g., returns image as an array of numbers); 108 if `False` it returns binary data. 109 :type decode_data: bool 110 :param raise_download_errors: If `True` any error in download process should be raised as 111 ``DownloadFailedException``. If `False` failed downloads will only raise warnings and the method will 112 return list with `None` values in places where the results of failed download requests should be. 113 :type raise_download_errors: bool 114 :return: requested images as numpy arrays, where each array corresponds to a single acquisition and has 115 shape ``[height, width, channels]``. 116 :rtype: list of numpy arrays 117 """ 118 self._preprocess_request(save_data, True) 119 return self._execute_data_download(data_filter, redownload, max_threads, raise_download_errors, 120 decode_data=decode_data) 121 122 def save_data(self, *, data_filter=None, redownload=False, max_threads=None, raise_download_errors=False): 123 """ Saves data to disk. If ``redownload=True`` then the data is redownloaded using ``max_threads`` workers. 124 125 :param data_filter: Used to specify which items will be returned by the method and in which order. E.g. with 126 `data_filter=[0, 2, -1]` the method will return only 1st, 3rd and last item. Default filter is `None`. 127 :type data_filter: list(int) or None 128 :param redownload: data is redownloaded if ``redownload=True``. Default is `False` 129 :type redownload: bool 130 :param max_threads: Maximum number of threads to be used for download in parallel. The default is 131 `max_threads=None` which will use the number of processors on the system multiplied by 5. 132 :type max_threads: int or None 133 :param raise_download_errors: If `True` any error in download process should be raised as 134 ``DownloadFailedException``. If `False` failed downloads will only raise warnings. 135 :type raise_download_errors: bool 136 """ 137 self._preprocess_request(True, False) 138 self._execute_data_download(data_filter, redownload, max_threads, raise_download_errors) 139 140 def _execute_data_download(self, data_filter, redownload, max_threads, raise_download_errors, decode_data=True): 141 """ Calls download module and executes the download process 142 143 :param data_filter: Used to specify which items will be returned by the method and in which order. E.g. with 144 `data_filter=[0, 2, -1]` the method will return only 1st, 3rd and last item. Default filter is `None`. 145 :type data_filter: list(int) or None 146 :param redownload: data is redownloaded if ``redownload=True``. Default is `False` 147 :type redownload: bool 148 :param max_threads: Maximum number of threads to be used for download in parallel. The default is 149 `max_threads=None` which will use the number of processors on the system multiplied by 5. 150 :type max_threads: int or None 151 :param raise_download_errors: If `True` any error in download process should be raised as 152 ``DownloadFailedException``. If `False` failed downloads will only raise warnings. 153 :type raise_download_errors: bool 154 :param decode_data: If `True` (default) it decodes data (e.g., returns image as an array of numbers); 155 if `False` it returns binary data. 156 :type decode_data: bool 157 :return: List of data obtained from download 158 :rtype: list 159 """ 160 is_repeating_filter = False 161 if data_filter is None: 162 filtered_download_list = self.download_list 163 elif isinstance(data_filter, (list, tuple)): 164 try: 165 filtered_download_list = [self.download_list[index] for index in data_filter] 166 except IndexError as exception: 167 raise IndexError('Indices of data_filter are out of range') from exception 168 169 filtered_download_list, mapping_list = self._filter_repeating_items(filtered_download_list) 170 is_repeating_filter = len(filtered_download_list) < len(mapping_list) 171 else: 172 raise ValueError('data_filter parameter must be a list of indices') 173 174 client = self.download_client_class( 175 redownload=redownload, 176 raise_download_errors=raise_download_errors, 177 config=self.config 178 ) 179 data_list = client.download(filtered_download_list, max_threads=max_threads, decode_data=decode_data) 180 181 if is_repeating_filter: 182 data_list = [copy.deepcopy(data_list[index]) for index in mapping_list] 183 184 return data_list 185 186 @staticmethod 187 def _filter_repeating_items(download_list): 188 """ Because of data_filter some requests in download list might be the same. In order not to download them again 189 this method will reduce the list of requests. It will also return a mapping list which can be used to 190 reconstruct the previous list of download requests. 191 192 :param download_list: List of download requests 193 :type download_list: list(sentinelhub.DownloadRequest) 194 :return: reduced download list with unique requests and mapping list 195 :rtype: (list(sentinelhub.DownloadRequest), list(int)) 196 """ 197 unique_requests_map = {} 198 mapping_list = [] 199 unique_download_list = [] 200 for download_request in download_list: 201 if download_request not in unique_requests_map: 202 unique_requests_map[download_request] = len(unique_download_list) 203 unique_download_list.append(download_request) 204 mapping_list.append(unique_requests_map[download_request]) 205 return unique_download_list, mapping_list 206 207 def _preprocess_request(self, save_data, return_data): 208 """ Prepares requests for download and creates empty folders 209 210 :param save_data: Tells whether to save data or not 211 :type save_data: bool 212 :param return_data: Tells whether to return data or not 213 :type return_data: bool 214 """ 215 if not self.is_valid_request(): 216 raise ValueError('Cannot obtain data because request is invalid') 217 218 if save_data and self.data_folder is None: 219 raise ValueError('Request parameter `data_folder` is not specified. ' 220 'In order to save data please set `data_folder` to location on your disk.') 221 222 for download_request in self.download_list: 223 download_request.save_response = save_data 224 download_request.return_data = return_data 225 download_request.data_folder = self.data_folder 226 227 if save_data: 228 for folder in self.folder_list: 229 make_folder(os.path.join(self.data_folder, folder)) 230 231 232class OgcRequest(DataRequest): 233 """ The base class for OGC-type requests (WMS and WCS) where all common parameters are defined 234 """ 235 def __init__(self, layer, bbox, *, time='latest', service_type=None, data_collection=None, 236 size_x=None, size_y=None, maxcc=1.0, image_format=MimeType.PNG, custom_url_params=None, 237 time_difference=datetime.timedelta(seconds=-1), data_source=None, **kwargs): 238 """ 239 :param layer: An ID of a layer configured in Sentinel Hub Dashboard. It has to be configured for the same 240 instance ID which will be used for this request. Also the satellite collection of the layer in Dashboard 241 must match the one given by `data_collection` parameter 242 :type layer: str 243 :param bbox: Bounding box of the requested image. Coordinates must be in the specified coordinate reference 244 system. 245 :type bbox: geometry.BBox 246 :param time: time or time range for which to return the results, in ISO8601 format 247 (year-month-date, for example: ``2016-01-01``, or year-month-dateThours:minutes:seconds format, 248 i.e. ``2016-01-01T16:31:21``). When a single time is specified the request will return data for that 249 specific date, if it exists. If a time range is specified the result is a list of all scenes between the 250 specified dates conforming to the cloud coverage criteria. Most recent acquisition being first in the list. 251 For the latest acquisition use ``latest``. Examples: ``latest``, ``'2016-01-01'``, or 252 ``('2016-01-01', ' 2016-01-31')`` 253 :type time: str or (str, str) or datetime.date or (datetime.date, datetime.date) or datetime.datetime or 254 (datetime.datetime, datetime.datetime) 255 :param service_type: type of OGC service (WMS or WCS) 256 :type service_type: constants.ServiceType 257 :param data_collection: A collection of requested satellite data. It has to be the same as defined in 258 Sentinel Hub Dashboard for the given layer. 259 :type data_collection: DataCollection 260 :param size_x: number of pixels in x or resolution in x (i.e. ``512`` or ``10m``) 261 :type size_x: int or str 262 :param size_y: number of pixels in x or resolution in y (i.e. ``512`` or ``10m``) 263 :type size_y: int or str 264 :param maxcc: maximum accepted cloud coverage of an image. Float between 0.0 and 1.0. Default is ``1.0``. 265 :type maxcc: float 266 :param image_format: format of the returned image by the Sentinel Hub's WMS getMap service. Default is PNG, but 267 in some cases 32-bit TIFF is required, i.e. if requesting unprocessed raw bands. 268 Default is ``constants.MimeType.PNG``. 269 :type image_format: constants.MimeType 270 :param custom_url_params: A dictionary of CustomUrlParameters and their values supported by Sentinel Hub's WMS 271 and WCS services. All available parameters are described at 272 http://www.sentinel-hub.com/develop/documentation/api/custom-url-parameters. Note: in case of 273 `CustomUrlParam.EVALSCRIPT` the dictionary value must be a string of Javascript code that is not 274 encoded into base64. 275 :type custom_url_params: Dict[CustomUrlParameter, object] 276 :param time_difference: The time difference below which dates are deemed equal. That is, if for the given set 277 of OGC parameters the images are available at datestimes `d1<=d2<=...<=dn` then only those with 278 `dk-dj>time_difference` will be considered. The default time difference is negative (`-1s`), meaning 279 that all dates are considered by default. 280 :type time_difference: datetime.timedelta 281 :param data_folder: location of the directory where the fetched data will be saved. 282 :type data_folder: str 283 :param config: A custom instance of config class to override parameters from the saved configuration. 284 :type config: SHConfig or None 285 :param data_source: A deprecated alternative to data_collection 286 :type data_source: DataCollection 287 """ 288 self.layer = layer 289 self.bbox = bbox 290 self.time = time 291 self.data_collection = DataCollection(handle_deprecated_data_source(data_collection, data_source, 292 default=DataCollection.SENTINEL2_L1C)) 293 self.maxcc = maxcc 294 self.image_format = MimeType(image_format) 295 self.service_type = service_type 296 self.size_x = size_x 297 self.size_y = size_y 298 self.custom_url_params = custom_url_params 299 self.time_difference = time_difference 300 301 if self.custom_url_params is not None: 302 self._check_custom_url_parameters() 303 304 self.wfs_iterator = None 305 306 super().__init__(SentinelHubDownloadClient, **kwargs) 307 308 def _check_custom_url_parameters(self): 309 """ Checks if custom url parameters are valid parameters. 310 311 Throws ValueError if the provided parameter is not a valid parameter. 312 """ 313 for param in self.custom_url_params: 314 if param not in CustomUrlParam: 315 raise ValueError(f'Parameter {param} is not a valid custom url parameter. Please check and fix.') 316 317 if self.service_type is ServiceType.FIS and CustomUrlParam.GEOMETRY in self.custom_url_params: 318 raise ValueError(f'{CustomUrlParam.GEOMETRY} should not be a custom url parameter of a FIS request') 319 320 def create_request(self, reset_wfs_iterator=False): 321 """ Set download requests 322 323 Create a list of DownloadRequests for all Sentinel-2 acquisitions within request's time interval and 324 acceptable cloud coverage. 325 326 :param reset_wfs_iterator: When re-running the method this flag is used to reset/keep existing ``wfs_iterator`` 327 (i.e. instance of ``WebFeatureService`` class). If the iterator is not reset you don't have to repeat a 328 service call but tiles and dates will stay the same. 329 :type reset_wfs_iterator: bool 330 """ 331 if reset_wfs_iterator: 332 self.wfs_iterator = None 333 334 ogc_service = OgcImageService(config=self.config) 335 self.download_list = ogc_service.get_request(self) 336 self.wfs_iterator = ogc_service.get_wfs_iterator() 337 338 def get_dates(self): 339 """ Get list of dates 340 341 List of all available Sentinel-2 acquisitions for given bbox with max cloud coverage and the specified 342 time interval. When a single time is specified the request will return that specific date, if it exists. 343 If a time range is specified the result is a list of all scenes between the specified dates conforming to 344 the cloud coverage criteria. Most recent acquisition being first in the list. 345 346 :return: list of all available Sentinel-2 acquisition times within request's time interval and 347 acceptable cloud coverage. 348 :rtype: list(datetime.datetime) or [None] 349 """ 350 return OgcImageService(config=self.config).get_dates(self) 351 352 def get_tiles(self): 353 """ Returns iterator over info about all satellite tiles used for the OgcRequest 354 355 :return: Iterator of dictionaries containing info about all satellite tiles used in the request. In case of 356 DataCollection.DEM it returns None. 357 :rtype: Iterator[dict] or None 358 """ 359 return self.wfs_iterator 360 361 362class WmsRequest(OgcRequest): 363 """ Web Map Service request class 364 365 Creates an instance of Sentinel Hub WMS (Web Map Service) GetMap request, 366 which provides access to Sentinel-2's unprocessed bands (B01, B02, ..., B08, B8A, ..., B12) 367 or processed products such as true color imagery, NDVI, etc. The only difference is that in 368 the case of WMS request the user specifies the desired image size instead of its resolution. 369 370 It is required to specify at least one of `width` and `height` parameters. If only one of them is specified the 371 the other one will be calculated to best fit the bounding box ratio. If both of them are specified they will be used 372 no matter the bounding box ratio. 373 374 More info available at: 375 https://www.sentinel-hub.com/develop/documentation/api/ogc_api/wms-parameters 376 """ 377 def __init__(self, *, width=None, height=None, **kwargs): 378 """ 379 :param width: width (number of columns) of the returned image (array) 380 :type width: int or None 381 :param height: height (number of rows) of the returned image (array) 382 :type height: int or None 383 :param layer: An ID of a layer configured in Sentinel Hub Dashboard. It has to be configured for the same 384 instance ID which will be used for this request. Also the satellite collection of the layer in Dashboard 385 must match the one given by `data_collection` parameter 386 :type layer: str 387 :param bbox: Bounding box of the requested image. Coordinates must be in the specified coordinate reference 388 system. 389 :type bbox: geometry.BBox 390 :param time: time or time range for which to return the results, in ISO8601 format 391 (year-month-date, for example: ``2016-01-01``, or year-month-dateThours:minutes:seconds format, 392 i.e. ``2016-01-01T16:31:21``). When a single time is specified the request will return data for that 393 specific date, if it exists. If a time range is specified the result is a list of all scenes between the 394 specified dates conforming to the cloud coverage criteria. Most recent acquisition being first in the list. 395 For the latest acquisition use ``latest``. Examples: ``latest``, ``'2016-01-01'``, or 396 ``('2016-01-01', ' 2016-01-31')`` 397 :type time: str or (str, str) or datetime.date or (datetime.date, datetime.date) or datetime.datetime or 398 (datetime.datetime, datetime.datetime) 399 :param data_collection: A collection of requested satellite data. It has to be the same as defined in 400 Sentinel Hub Dashboard for the given layer. Default is Sentinel-2 L1C. 401 :type data_collection: DataCollection 402 :param size_x: number of pixels in x or resolution in x (i.e. ``512`` or ``10m``) 403 :type size_x: int or str 404 :param size_y: number of pixels in x or resolution in y (i.e. ``512`` or ``10m``) 405 :type size_y: int or str 406 :param maxcc: maximum accepted cloud coverage of an image. Float between 0.0 and 1.0. Default is ``1.0``. 407 :type maxcc: float 408 :param image_format: format of the returned image by the Sentinel Hub's WMS getMap service. Default is PNG, but 409 in some cases 32-bit TIFF is required, i.e. if requesting unprocessed raw bands. 410 Default is ``constants.MimeType.PNG``. 411 :type image_format: constants.MimeType 412 :param custom_url_params: A dictionary of CustomUrlParameters and their values supported by Sentinel Hub's WMS 413 and WCS services. All available parameters are described at 414 http://www.sentinel-hub.com/develop/documentation/api/custom-url-parameters. Note: in case of 415 `CustomUrlParam.EVALSCRIPT` the dictionary value must be a string of Javascript code that is not 416 encoded into base64. 417 :type custom_url_params: Dict[CustomUrlParameter, object] 418 :param time_difference: The time difference below which dates are deemed equal. That is, if for the given set 419 of OGC parameters the images are available at datestimes `d1<=d2<=...<=dn` then only those with 420 `dk-dj>time_difference` will be considered. The default time difference is negative (`-1s`), meaning 421 that all dates are considered by default. 422 :type time_difference: datetime.timedelta 423 :param data_folder: location of the directory where the fetched data will be saved. 424 :type data_folder: str 425 :param config: A custom instance of config class to override parameters from the saved configuration. 426 :type config: SHConfig or None 427 :param data_source: A deprecated alternative to data_collection 428 :type data_source: DataCollection 429 """ 430 super().__init__(service_type=ServiceType.WMS, size_x=width, size_y=height, **kwargs) 431 432 433class WcsRequest(OgcRequest): 434 """ Web Coverage Service request class 435 436 Creates an instance of Sentinel Hub WCS (Web Coverage Service) GetCoverage request, 437 which provides access to Sentinel-2's unprocessed bands (B01, B02, ..., B08, B8A, ..., B12) 438 or processed products such as true color imagery, NDVI, etc., as the WMS service. The 439 only difference is that in the case of WCS request the user specifies the desired 440 resolution of the image instead of its size. 441 442 More info available at: 443 https://www.sentinel-hub.com/develop/documentation/api/ogc_api/wcs-request 444 """ 445 def __init__(self, *, resx='10m', resy='10m', **kwargs): 446 """ 447 :param resx: resolution in x (resolution of a column) given in meters in the format (examples ``10m``, 448 ``20m``, ...). Default is ``10m``, which is the best native resolution of some Sentinel-2 bands. 449 :type resx: str 450 :param resy: resolution in y (resolution of a row) given in meters in the format 451 (examples ``10m``, ``20m``, ...). Default is ``10m``, which is the best native resolution of some 452 Sentinel-2 bands. 453 :type resy: str 454 :param layer: An ID of a layer configured in Sentinel Hub Dashboard. It has to be configured for the same 455 instance ID which will be used for this request. Also the satellite collection of the layer in Dashboard 456 must match the one given by `data_collection` parameter 457 :type layer: str 458 :param bbox: Bounding box of the requested image. Coordinates must be in the specified coordinate reference 459 system. 460 :type bbox: geometry.BBox 461 :param time: time or time range for which to return the results, in ISO8601 format 462 (year-month-date, for example: ``2016-01-01``, or year-month-dateThours:minutes:seconds format, 463 i.e. ``2016-01-01T16:31:21``). When a single time is specified the request will return data for that 464 specific date, if it exists. If a time range is specified the result is a list of all scenes between the 465 specified dates conforming to the cloud coverage criteria. Most recent acquisition being first in the list. 466 For the latest acquisition use ``latest``. Examples: ``latest``, ``'2016-01-01'``, or 467 ``('2016-01-01', ' 2016-01-31')`` 468 :type time: str or (str, str) or datetime.date or (datetime.date, datetime.date) or datetime.datetime or 469 (datetime.datetime, datetime.datetime) 470 :param data_collection: A collection of requested satellite data. It has to be the same as defined in Sentinel 471 Hub Dashboard for the given layer. Default is Sentinel-2 L1C. 472 :type data_collection: DataCollection 473 :param size_x: number of pixels in x or resolution in x (i.e. ``512`` or ``10m``) 474 :type size_x: int or str 475 :param size_y: number of pixels in x or resolution in y (i.e. ``512`` or ``10m``) 476 :type size_y: int or str 477 :param maxcc: maximum accepted cloud coverage of an image. Float between 0.0 and 1.0. Default is ``1.0``. 478 :type maxcc: float 479 :param image_format: format of the returned image by the Sentinel Hub's WMS getMap service. Default is PNG, but 480 in some cases 32-bit TIFF is required, i.e. if requesting unprocessed raw bands. 481 Default is ``constants.MimeType.PNG``. 482 :type image_format: constants.MimeType 483 :param custom_url_params: A dictionary of CustomUrlParameters and their values supported by Sentinel Hub's WMS 484 and WCS services. All available parameters are described at 485 http://www.sentinel-hub.com/develop/documentation/api/custom-url-parameters. Note: in case of 486 `CustomUrlParam.EVALSCRIPT` the dictionary value must be a string of Javascript code that is not 487 encoded into base64. 488 :type custom_url_params: Dict[CustomUrlParameter, object] 489 :param time_difference: The time difference below which dates are deemed equal. That is, if for the given set 490 of OGC parameters the images are available at datestimes `d1<=d2<=...<=dn` then only those with 491 `dk-dj>time_difference` will be considered. The default time difference is negative (`-1s`), meaning 492 that all dates are considered by default. 493 :type time_difference: datetime.timedelta 494 :param data_folder: location of the directory where the fetched data will be saved. 495 :type data_folder: str 496 :param config: A custom instance of config class to override parameters from the saved configuration. 497 :type config: SHConfig or None 498 :param data_source: A deprecated alternative to data_collection 499 :type data_source: DataCollection 500 """ 501 super().__init__(service_type=ServiceType.WCS, size_x=resx, size_y=resy, **kwargs) 502 503 504class FisRequest(OgcRequest): 505 """ The Statistical info (or feature info service, abbreviated FIS) request class 506 507 The Statistical info (or feature info service, abbreviated FIS), performs elementary statistical 508 computations---such as mean, standard deviation, and histogram approximating the distribution of reflectance 509 values---on remotely sensed data for a region specified in a given spatial reference system across different 510 bands and time ranges. 511 512 A quintessential usage example would be querying the service for basic statistics and the distribution of NDVI 513 values for a polygon representing an agricultural unit over a time range. 514 515 More info available at: 516 https://www.sentinel-hub.com/develop/documentation/api/ogc_api/wcs-request 517 """ 518 def __init__(self, layer, time, geometry_list, *, resolution='10m', bins=None, histogram_type=None, **kwargs): 519 """ 520 :param layer: An ID of a layer configured in Sentinel Hub Dashboard. It has to be configured for the same 521 instance ID which will be used for this request. Also the satellite collection of the layer in Dashboard 522 must match the one given by `data_collection` parameter 523 :type layer: str 524 :param time: time or time range for which to return the results, in ISO8601 format 525 (year-month-date, for example: ``2016-01-01``, or year-month-dateThours:minutes:seconds format, 526 i.e. ``2016-01-01T16:31:21``). Examples: ``'2016-01-01'``, or ``('2016-01-01', ' 2016-01-31')`` 527 :type time: str or (str, str) or datetime.date or (datetime.date, datetime.date) or datetime.datetime or 528 (datetime.datetime, datetime.datetime) 529 :param geometry_list: A WKT representation of a geometry describing the region of interest. 530 Note that WCS 1.1.1 standard is used here, so for EPSG:4326 coordinates should be in latitude/longitude 531 order. 532 :type geometry_list: list, [geometry.Geometry or geometry.Bbox] 533 :param resolution: Specifies the spatial resolution, in meters per pixel, of the image from which the statistics 534 are to be estimated. When using CRS=EPSG:4326 one has to add the "m" suffix to 535 enforce resolution in meters per pixel (e.g. RESOLUTION=10m). 536 :type resolution: str 537 :param bins: The number of bins (a positive integer) in the histogram. If this parameter is absent no histogram 538 is computed. 539 :type bins: str 540 :param histogram_type: type of histogram 541 :type histogram_type: HistogramType 542 :param data_collection: A collection of requested satellite data. It has to be the same as defined in Sentinel 543 Hub Dashboard for the given layer. Default is Sentinel-2 L1C. 544 :type data_collection: DataCollection 545 :param maxcc: maximum accepted cloud coverage of an image. Float between 0.0 and 1.0. Default is ``1.0``. 546 :type maxcc: float 547 :param custom_url_params: Dictionary of CustomUrlParameters and their values supported by Sentinel Hub's WMS 548 and WCS services. All available parameters are described at 549 http://www.sentinel-hub.com/develop/documentation/api/custom-url-parameters. Note: in 550 case of constants.CustomUrlParam.EVALSCRIPT the dictionary value must be a string 551 of Javascript code that is not encoded into base64. 552 :type custom_url_params: Dict[CustomUrlParameter, object] 553 :param data_folder: location of the directory where the fetched data will be saved. 554 :type data_folder: str 555 :param config: A custom instance of config class to override parameters from the saved configuration. 556 :type config: SHConfig or None 557 :param data_source: A deprecated alternative to data_collection 558 :type data_source: DataCollection 559 """ 560 self.geometry_list = geometry_list 561 self.resolution = resolution 562 self.bins = bins 563 self.histogram_type = HistogramType(histogram_type) if histogram_type else None 564 565 super().__init__(bbox=None, layer=layer, time=time, service_type=ServiceType.FIS, **kwargs) 566 567 def create_request(self): 568 """ Set download requests 569 570 Create a list of DownloadRequests for all Sentinel-2 acquisitions within request's time interval and 571 acceptable cloud coverage. 572 """ 573 fis_service = FisService(config=self.config) 574 self.download_list = fis_service.get_request(self) 575 576 def get_dates(self): 577 """ This method is not supported for FIS request 578 """ 579 raise NotImplementedError 580 581 def get_tiles(self): 582 """ This method is not supported for FIS request 583 """ 584 raise NotImplementedError 585 586 587class GeopediaRequest(DataRequest): 588 """ The base class for Geopedia requests where all common parameters are defined. 589 """ 590 def __init__(self, layer, service_type, *, bbox=None, theme=None, image_format=MimeType.PNG, **kwargs): 591 """ 592 :param layer: Geopedia layer which contains requested data 593 :type layer: str 594 :param service_type: Type of the service, supported are ``ServiceType.WMS`` and ``ServiceType.IMAGE`` 595 :type service_type: constants.ServiceType 596 :param bbox: Bounding box of the requested data 597 :type bbox: geometry.BBox 598 :param theme: Geopedia's theme endpoint string for which the layer is defined. Only required by WMS service. 599 :type theme: str 600 :param image_format: Format of the returned image by the Sentinel Hub's WMS getMap service. Default is 601 ``constants.MimeType.PNG``. 602 :type image_format: constants.MimeType 603 :param data_folder: Location of the directory where the fetched data will be saved. 604 :type data_folder: str 605 :param config: A custom instance of config class to override parameters from the saved configuration. 606 :type config: SHConfig or None 607 """ 608 self.layer = layer 609 self.service_type = service_type 610 611 self.bbox = bbox 612 if bbox.crs is not CRS.POP_WEB: 613 raise ValueError('Geopedia Request at the moment supports only bounding boxes with coordinates in ' 614 f'{CRS.POP_WEB}') 615 616 self.theme = theme 617 self.image_format = MimeType(image_format) 618 619 super().__init__(DownloadClient, **kwargs) 620 621 @abstractmethod 622 def create_request(self): 623 raise NotImplementedError 624 625 626class GeopediaWmsRequest(GeopediaRequest): 627 """ Web Map Service request class for Geopedia 628 629 Creates an instance of Geopedia's WMS (Web Map Service) GetMap request, which provides access to WMS layers in 630 Geopedia. 631 """ 632 def __init__(self, layer, theme, bbox, *, width=None, height=None, **kwargs): 633 """ 634 :param layer: Geopedia layer which contains requested data 635 :type layer: str 636 :param theme: Geopedia's theme endpoint string for which the layer is defined. 637 :type theme: str 638 :param bbox: Bounding box of the requested data 639 :type bbox: geometry.BBox 640 :param width: width (number of columns) of the returned image (array) 641 :type width: int or None 642 :param height: height (number of rows) of the returned image (array) 643 :type height: int or None 644 :param image_format: Format of the returned image by the Sentinel Hub's WMS getMap service. Default is 645 ``constants.MimeType.PNG``. 646 :type image_format: constants.MimeType 647 :param data_folder: Location of the directory where the fetched data will be saved. 648 :type data_folder: str 649 :param config: A custom instance of config class to override parameters from the saved configuration. 650 :type config: SHConfig or None 651 """ 652 self.size_x = width 653 self.size_y = height 654 655 super().__init__(layer=layer, theme=theme, bbox=bbox, service_type=ServiceType.WMS, **kwargs) 656 657 def create_request(self): 658 """ Set download requests 659 660 Create a list of DownloadRequests for all Sentinel-2 acquisitions within request's time interval and 661 acceptable cloud coverage. 662 """ 663 gpd_service = GeopediaWmsService(config=self.config) 664 self.download_list = gpd_service.get_request(self) 665 666 667class GeopediaImageRequest(GeopediaRequest): 668 """ Request to access data in a Geopedia vector / raster layer. 669 """ 670 def __init__(self, *, image_field_name, keep_image_names=True, gpd_session=None, **kwargs): 671 """ 672 :param image_field_name: Name of the field in the data table which holds images 673 :type image_field_name: str 674 :param keep_image_names: If `True` images will be saved with the same names as in Geopedia otherwise Geopedia 675 hashes will be used as names. If there are multiple images with the same names in the Geopedia layer this 676 parameter should be set to `False` to prevent images being overwritten. 677 :type keep_image_names: bool 678 :param layer: Geopedia layer which contains requested data 679 :type layer: str 680 :param bbox: Bounding box of the requested data 681 :type bbox: geometry.BBox 682 :param image_format: Format of the returned image by the Sentinel Hub's WMS getMap service. Default is 683 ``constants.MimeType.PNG``. 684 :type image_format: constants.MimeType 685 :param gpd_session: Optional parameter for specifying a custom Geopedia session, which can also contain login 686 credentials. This can be used for accessing private Geopedia layers. By default it is set to `None` and a 687 basic Geopedia session without credentials will be created. 688 :type gpd_session: GeopediaSession or None 689 :param data_folder: Location of the directory where the fetched data will be saved. 690 :type data_folder: str 691 :param config: A custom instance of config class to override parameters from the saved configuration. 692 :type config: SHConfig or None 693 """ 694 self.image_field_name = image_field_name 695 self.keep_image_names = keep_image_names 696 self.gpd_session = gpd_session 697 698 self.gpd_iterator = None 699 700 super().__init__(service_type=ServiceType.IMAGE, **kwargs) 701 702 def create_request(self, reset_gpd_iterator=False): 703 """ Set a list of download requests 704 705 Set a list of DownloadRequests for all images that are under the 706 given property of the Geopedia's Vector layer. 707 708 :param reset_gpd_iterator: When re-running the method this flag is used to reset/keep existing ``gpd_iterator`` 709 (i.e. instance of ``GeopediaFeatureIterator`` class). If the iterator is not reset you don't have to 710 repeat a service call but tiles and dates will stay the same. 711 :type reset_gpd_iterator: bool 712 """ 713 if reset_gpd_iterator: 714 self.gpd_iterator = None 715 716 gpd_service = GeopediaImageService(config=self.config) 717 self.download_list = gpd_service.get_request(self) 718 self.gpd_iterator = gpd_service.get_gpd_iterator() 719 720 def get_items(self): 721 """ Returns iterator over info about data used for this request 722 723 :return: Iterator of dictionaries containing info about data used in 724 this request. 725 :rtype: Iterator[dict] or None 726 """ 727 return self.gpd_iterator 728 729 730class AwsRequest(DataRequest): 731 """ The base class for Amazon Web Service request classes. Common parameters are defined here. 732 733 Collects and provides data from AWS. 734 735 AWS database is available at: 736 http://sentinel-s2-l1c.s3-website.eu-central-1.amazonaws.com/ 737 """ 738 def __init__(self, *, bands=None, metafiles=None, safe_format=False, **kwargs): 739 """ 740 :param bands: List of Sentinel-2 bands for request. If `None` all bands will be obtained 741 :type bands: list(str) or None 742 :param metafiles: list of additional metafiles available on AWS 743 (e.g. ``['metadata', 'tileInfo', 'preview/B01', 'TCI']``) 744 :type metafiles: list(str) 745 :param safe_format: flag that determines the structure of saved data. If `True` it will be saved in .SAFE format 746 defined by ESA. If `False` it will be saved in the same structure as the structure at AWS. 747 :type safe_format: bool 748 :param data_folder: location of the directory where the fetched data will be saved. 749 :type data_folder: str 750 :param config: A custom instance of config class to override parameters from the saved configuration. 751 :type config: SHConfig or None 752 """ 753 self.bands = bands 754 self.metafiles = metafiles 755 self.safe_format = safe_format 756 757 self.aws_service = None 758 super().__init__(AwsDownloadClient, **kwargs) 759 760 @abstractmethod 761 def create_request(self): 762 raise NotImplementedError 763 764 def get_aws_service(self): 765 """ 766 :return: initialized AWS service class 767 :rtype: aws.AwsProduct or aws.AwsTile or aws_safe.SafeProduct or aws_safe.SafeTile 768 """ 769 return self.aws_service 770 771 772class AwsProductRequest(AwsRequest): 773 """ AWS Service request class for an ESA product 774 775 List of available products: 776 http://sentinel-s2-l1c.s3-website.eu-central-1.amazonaws.com/#products/ 777 """ 778 def __init__(self, product_id, *, tile_list=None, **kwargs): 779 """ 780 :param product_id: original ESA product identification string 781 (e.g. ``'S2A_MSIL1C_20170414T003551_N0204_R016_T54HVH_20170414T003551'``) 782 :type product_id: str 783 :param tile_list: list of tiles inside the product to be downloaded. If parameter is set to `None` all 784 tiles inside the product will be downloaded. 785 :type tile_list: list(str) or None 786 :param bands: List of Sentinel-2 bands for request. If `None` all bands will be obtained 787 :type bands: list(str) or None 788 :param metafiles: list of additional metafiles available on AWS 789 (e.g. ``['metadata', 'tileInfo', 'preview/B01', 'TCI']``) 790 :type metafiles: list(str) 791 :param safe_format: flag that determines the structure of saved data. If `True` it will be saved in .SAFE format 792 defined by ESA. If `False` it will be saved in the same structure as the structure at AWS. 793 :type safe_format: bool 794 :param data_folder: location of the directory where the fetched data will be saved. 795 :type data_folder: str 796 :param config: A custom instance of config class to override parameters from the saved configuration. 797 :type config: SHConfig or None 798 """ 799 self.product_id = product_id 800 self.tile_list = tile_list 801 802 super().__init__(**kwargs) 803 804 def create_request(self): 805 if self.safe_format: 806 self.aws_service = SafeProduct(self.product_id, tile_list=self.tile_list, bands=self.bands, 807 metafiles=self.metafiles, config=self.config) 808 else: 809 self.aws_service = AwsProduct(self.product_id, tile_list=self.tile_list, bands=self.bands, 810 metafiles=self.metafiles, config=self.config) 811 812 self.download_list, self.folder_list = self.aws_service.get_requests() 813 814 815class AwsTileRequest(AwsRequest): 816 """ AWS Service request class for an ESA tile 817 818 List of available products: 819 http://sentinel-s2-l1c.s3-website.eu-central-1.amazonaws.com/#tiles/ 820 """ 821 def __init__(self, *, tile=None, time=None, aws_index=None, data_collection=None, data_source=None, **kwargs): 822 """ 823 :param tile: tile name (e.g. ``'T10UEV'``) 824 :type tile: str 825 :param time: tile sensing time in ISO8601 format 826 :type time: str 827 :param aws_index: there exist Sentinel-2 tiles with the same tile and time parameter. Therefore each tile on 828 AWS also has an index which is visible in their url path. If aws_index is set to `None` the class 829 will try to find the index automatically. If there will be multiple choices it will choose the 830 lowest index and inform the user. 831 :type aws_index: int or None 832 :param data_collection: A collection of requested AWS data. Supported collections are Sentinel-2 L1C and 833 Sentinel-2 L2A. 834 :type data_collection: DataCollection 835 :param bands: List of Sentinel-2 bands for request. If `None` all bands will be obtained 836 :type bands: list(str) or None 837 :param metafiles: list of additional metafiles available on AWS 838 (e.g. ``['metadata', 'tileInfo', 'preview/B01', 'TCI']``) 839 :type metafiles: list(str) 840 :param safe_format: flag that determines the structure of saved data. If `True` it will be saved in .SAFE 841 format defined by ESA. If `False` it will be saved in the same structure as the structure at AWS. 842 :type safe_format: bool 843 :param data_folder: location of the directory where the fetched data will be saved. 844 :type data_folder: str 845 :param config: A custom instance of config class to override parameters from the saved configuration. 846 :type config: SHConfig or None 847 :param data_source: A deprecated alternative to data_collection 848 :type data_source: DataCollection 849 """ 850 self.tile = tile 851 self.time = time 852 self.aws_index = aws_index 853 self.data_collection = DataCollection(handle_deprecated_data_source(data_collection, data_source, 854 default=DataCollection.SENTINEL2_L1C)) 855 856 super().__init__(**kwargs) 857 858 def create_request(self): 859 if self.safe_format: 860 self.aws_service = SafeTile(self.tile, self.time, self.aws_index, bands=self.bands, 861 metafiles=self.metafiles, data_collection=self.data_collection, 862 config=self.config) 863 else: 864 self.aws_service = AwsTile(self.tile, self.time, self.aws_index, bands=self.bands, 865 metafiles=self.metafiles, data_collection=self.data_collection, 866 config=self.config) 867 868 self.download_list, self.folder_list = self.aws_service.get_requests() 869 870 871def get_safe_format(product_id=None, tile=None, entire_product=False, bands=None, 872 data_collection=None, data_source=None): 873 """ Returns .SAFE format structure in form of nested dictionaries. Either ``product_id`` or ``tile`` must be 874 specified. 875 876 :param product_id: original ESA product identification string. Default is `None` 877 :type product_id: str 878 :param tile: tuple containing tile name and sensing time/date. Default is `None` 879 :type tile: (str, str) 880 :param entire_product: in case tile is specified this flag determines if it will be place inside a .SAFE structure 881 of the product. Default is `False` 882 :type entire_product: bool 883 :param bands: list of bands to download. If `None` all bands will be downloaded. Default is `None` 884 :type bands: list(str) or None 885 :param data_collection: In case of tile request the collection of satellite data has to be specified. 886 :type data_collection: DataCollection 887 :param data_source: A deprecated alternative to data_collection 888 :type data_source: DataCollection 889 :return: Nested dictionaries representing .SAFE structure. 890 :rtype: dict 891 """ 892 data_collection = handle_deprecated_data_source(data_collection, data_source) 893 894 entire_product = entire_product and product_id is None 895 if tile is not None: 896 safe_tile = SafeTile(tile_name=tile[0], time=tile[1], bands=bands, data_collection=data_collection) 897 if not entire_product: 898 return safe_tile.get_safe_struct() 899 product_id = safe_tile.get_product_id() 900 if product_id is None: 901 raise ValueError('Either product_id or tile must be specified') 902 safe_product = SafeProduct(product_id, tile_list=[tile[0]], bands=bands) if entire_product else \ 903 SafeProduct(product_id, bands=bands) 904 return safe_product.get_safe_struct() 905 906 907def download_safe_format(product_id=None, tile=None, folder='.', redownload=False, entire_product=False, bands=None, 908 data_collection=None, data_source=None): 909 """ Downloads .SAFE format structure in form of nested dictionaries. Either ``product_id`` or ``tile`` must 910 be specified. 911 912 :param product_id: original ESA product identification string. Default is `None` 913 :type product_id: str 914 :param tile: tuple containing tile name and sensing time/date. Default is `None` 915 :type tile: (str, str) 916 :param folder: location of the directory where the fetched data will be saved. Default is ``'.'`` 917 :type folder: str 918 :param redownload: if `True`, download again the requested data even though it's already saved to disk. If 919 `False`, do not download if data is already available on disk. Default is `False` 920 :type redownload: bool 921 :param entire_product: in case tile is specified this flag determines if it will be place inside a .SAFE structure 922 of the product. Default is `False` 923 :type entire_product: bool 924 :param bands: list of bands to download. If `None` all bands will be downloaded. Default is `None` 925 :type bands: list(str) or None 926 :param data_collection: In case of tile request the collection of satellite data has to be specified. 927 :type data_collection: DataCollection 928 :param data_source: A deprecated alternative to data_collection 929 :type data_source: DataCollection 930 :return: Nested dictionaries representing .SAFE structure. 931 :rtype: dict 932 """ 933 data_collection = handle_deprecated_data_source(data_collection, data_source) 934 935 entire_product = entire_product and product_id is None 936 if tile is not None: 937 safe_request = AwsTileRequest(tile=tile[0], time=tile[1], data_folder=folder, bands=bands, 938 safe_format=True, data_collection=data_collection) 939 if entire_product: 940 safe_tile = safe_request.get_aws_service() 941 product_id = safe_tile.get_product_id() 942 if product_id is not None: 943 safe_request = AwsProductRequest(product_id, tile_list=[tile[0]], data_folder=folder, bands=bands, 944 safe_format=True) if entire_product else \ 945 AwsProductRequest(product_id, data_folder=folder, bands=bands, safe_format=True) 946 947 safe_request.save_data(redownload=redownload) 948