2222import json
2323import logging
2424import warnings
25+ from collections .abc import Iterable
2526from contextlib import contextmanager
2627from copy import deepcopy
2728from pathlib import Path
@@ -64,6 +65,12 @@ def days_in_month(date):
6465 return td
6566
6667
68+ def _valid_channel_limit (value , expects ):
69+ if not isinstance (value , Iterable ) or len (value ) != expects :
70+ raise ValueError ('Invalid type for "channel_limits". '
71+ f'Expects Iterable of { expects } elements, but got "{ value } "' )
72+
73+
6774@contextmanager
6875def timing (description : str ) -> None :
6976 """Measure execution time of context.
@@ -131,10 +138,10 @@ def get_stac(self, collection: str) -> STAC:
131138 When collection not found, search on INPE STAC.
132139
133140 Args:
134- collection - Collection name to search
141+ collection (str): Collection name to search
135142
136143 Returns:
137- STAC client
144+ STAC: The STAC which offers the given collection.
138145 """
139146 if self .properties .get ('stac_url' ):
140147 return self ._stac (collection , self .properties ['stac_url' ], ** self .properties )
@@ -158,7 +165,7 @@ def _stac(self, collection: str, url: str, **kwargs) -> STAC:
158165 url - STAC URL
159166
160167 Returns:
161- STAC client
168+ STAC: STAC instance
162169 """
163170 try :
164171 options = dict ()
@@ -208,6 +215,13 @@ def orchestrate(self):
208215 # Validate parameters
209216 cube_parameters .validate ()
210217
218+ if self .properties .get ('channel_limits' ):
219+ channel_limits = self .properties ['channel_limits' ]
220+ # Validate the entire signature (Tuple of 3 limit elements)
221+ _valid_channel_limit (channel_limits , expects = 3 )
222+ # Validate each RGB channel (Tuple of 2 int elements)
223+ _ = [_valid_channel_limit (channel , expects = 2 ) for channel in channel_limits ]
224+
211225 # Pass the cube parameters to the data cube functions arguments
212226 props = deepcopy (cube_parameters .metadata_ )
213227 props .update (self .properties )
@@ -235,8 +249,13 @@ def orchestrate(self):
235249 self .bands = Band .query ().filter (Band .collection_id == self .warped_datacube .id ).all ()
236250
237251 bands = self .datacube_bands
238- self .band_map = {b .name : dict (name = b .name , data_type = b .data_type , nodata = b .nodata ,
239- min_value = b .min_value , max_value = b .max_value ) for b in bands }
252+ self .band_map = {
253+ b .name : dict (name = b .name , data_type = b .data_type , nodata = b .nodata ,
254+ min_value = b .min_value , max_value = b .max_value ,
255+ scale = b .scale ,
256+ scale_add = b ._metadata .get ('scale_add' ) if b ._metadata else None )
257+ for b in bands
258+ }
240259
241260 if self .properties .get ('reuse_from' ):
242261 warnings .warn (
@@ -404,6 +423,13 @@ def dispatch_celery(self):
404423
405424 warped_datacube = self .warped_datacube .name
406425
426+ if self .properties .get ('with_rgb' ):
427+ input_range = self .properties .get ('input_range' )
428+ if input_range is None :
429+ raise ValueError ('Missing valid range for RGB.' )
430+ if type (input_range ) not in (list , tuple ,):
431+ raise TypeError (f'Invalid input range for RGB. Expects Tuple[int, int], got { type (input_range )} ' )
432+
407433 quality_band = None
408434 stac_kwargs = self .properties .get ('stac_kwargs' , dict ())
409435 if self .datacube .composite_function .alias != 'IDT' :
@@ -509,6 +535,7 @@ def dispatch_celery(self):
509535 srs = grid_crs ,
510536 tile_id = tileid ,
511537 assets = assets ,
538+ platforms = self .platforms ,
512539 nodata = float (band .nodata ),
513540 bands = band_str_list ,
514541 version = self .datacube .version ,
@@ -563,6 +590,9 @@ def search_images(self, feature: dict, start: str, end: str, tile_id: str, **kwa
563590
564591 bands = self .datacube_bands
565592
593+ band_mapping = self .properties .get ('band_map' , dict ())
594+ platforms = set ()
595+
566596 # Retrieve band definition in dict format.
567597 # TODO: Should we use from STAC?
568598 collection_bands = dict ()
@@ -581,6 +611,11 @@ def search_images(self, feature: dict, start: str, end: str, tile_id: str, **kwa
581611 for dataset in self .params ['collections' ]:
582612 options ['collections' ] = [dataset ]
583613 stac = self .get_stac (dataset )
614+ stac_collection = stac .collection (dataset )
615+ if stac_collection .get ('summaries' ) and stac_collection ['summaries' ].get ('platform' ):
616+ platforms = platforms .union (set (stac_collection ['summaries' ].get ('platform' )))
617+ elif stac_collection .properties .get ('platform' ):
618+ platforms = platforms .union (set (stac_collection .properties .get ('platform' )))
584619
585620 token = ''
586621
@@ -593,21 +628,40 @@ def search_images(self, feature: dict, start: str, end: str, tile_id: str, **kwa
593628 for feature in items ['features' ]:
594629 if feature ['type' ] == 'Feature' :
595630 date = feature ['properties' ]['datetime' ][0 :10 ]
596- identifier = feature ['id' ]
597631 stac_bands = feature ['properties' ].get ('eo:bands' , [])
632+ identifier = feature ['id' ]
633+ # TODO: Add handler to deal with parse result serializer.
634+ platform = feature ['properties' ].get ('platform' )
635+ if stac .url .startswith ('https://landsatlook.usgs.gov' ):
636+ # Remove last SR sentence.
637+ identifier = f'{ identifier [:- 3 ]} { identifier [- 3 :].replace ("_SR" , "" )} '
638+ # Special treatment for missing/invalid platform values
639+ if (platform is None or isinstance (platform , list )) and _is_landsat (identifier ):
640+ platform = _detect_landsat_platform (identifier )
598641
599642 for band in bands :
600643 band_name_href = band .name
644+
645+ if platform and band_mapping :
646+ platforms .add (platform )
647+ if platform not in band_mapping or not band_mapping [platform ].get (band_name_href ):
648+ continue
649+
650+ band_name_href = band_mapping [platform ].get (band_name_href )
651+
601652 if 'CBERS' in dataset and band .common_name not in ('evi' , 'ndvi' ):
602653 band_name_href = band .common_name
603654
604655 elif band .name not in feature ['assets' ]:
605- if f'sr_{ band .name } ' not in feature ['assets' ]:
656+ # TODO: Implement asset resolver
657+ if f'{ band_name_href } .TIF' in feature ['assets' ]:
658+ band_name_href = f'{ band_name_href } .TIF'
659+ elif f'sr_{ band_name_href } ' not in feature ['assets' ]:
606660 continue
607661 else :
608662 band_name_href = f'sr_{ band .name } '
609663
610- feature_band = list (filter (lambda b : b ['name' ] == band_name_href ,stac_bands ))
664+ feature_band = list (filter (lambda b : b ['name' ] == band_name_href , stac_bands ))
611665 feature_band = feature_band [0 ] if len (feature_band ) > 0 else dict ()
612666
613667 scenes [band .name ].setdefault (date , dict ())
@@ -620,6 +674,9 @@ def search_images(self, feature: dict, start: str, end: str, tile_id: str, **kwa
620674 scene ['sceneid' ] = identifier
621675 scene ['band' ] = band .name
622676 scene ['dataset' ] = dataset
677+ scene ['platform' ] = platform
678+ if band_mapping :
679+ scene ['original_band_name' ] = band_name_href
623680
624681 link = link .replace (Config .CBERS_SOURCE_URL_PREFIX , Config .CBERS_TARGET_URL_PREFIX )
625682
@@ -634,4 +691,29 @@ def search_images(self, feature: dict, start: str, end: str, tile_id: str, **kwa
634691 scenes [band .name ][date ].setdefault (dataset , [])
635692 scenes [band .name ][date ][dataset ].append (scene )
636693
694+ self .platforms = sorted (list (platforms ))
695+
637696 return scenes
697+
698+
699+ def _is_landsat (identifier : str ) -> bool :
700+ try :
701+ _parse_landsat (identifier )
702+ return True
703+ except ValueError :
704+ return False
705+
706+
707+ def _parse_landsat (identifier : str ) -> List [str ]:
708+ entities = identifier .split ('_' )
709+ if len (entities ) != 7 or not entities [0 ].startswith ('L' ) or not entities [0 ][- 2 :].isnumeric ():
710+ raise ValueError (f'Invalid landsat scene { identifier } ' )
711+ return entities
712+
713+
714+ def _detect_landsat_platform (identifier : str ):
715+ entities = _parse_landsat (identifier )
716+ satellite = int (entities [0 ][- 2 :])
717+ for value in [4 , 5 , 7 , 8 , 9 ]:
718+ if satellite == value :
719+ return f'Landsat-{ value } '
0 commit comments