[Git][debian-gis-team/asf-search][upstream] New upstream version 13.1.0

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Sat Sep 12 16:42:11 BST 2026



Antonio Valentino pushed to branch upstream at Debian GIS Project / asf-search


Commits:
8667e7a5 by Antonio Valentino at 2026-09-12T15:35:01+00:00
New upstream version 13.1.0
- - - - -


6 changed files:

- CHANGELOG.md
- asf_search/ASFProduct.py
- asf_search/CMR/datasets.py
- asf_search/Products/ALOSProduct.py
- asf_search/Products/SMAPProduct.py
- asf_search/constants/PRODUCT_TYPE.py


Changes:

=====================================
CHANGELOG.md
=====================================
@@ -27,6 +27,12 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 -->
 
 ---
+## [v13.1.0](https://github.com/asfadmin/Discovery-asf_search/compare/v13.0.1...v13.1.0)
+
+### Added
+
+- Added support for upcoming ALOS-1 and SMAP metadata changes, added `L1A_RADAR_RO`, `L1C_S0_HIRES`, `L1B_S0_LORES`, `L1A_RADAR` product type constants.
+
 ## [v13.0.1](https://github.com/asfadmin/Discovery-asf_search/compare/v13.0.0...v13.0.1)
 
 ### Fixed


=====================================
asf_search/ASFProduct.py
=====================================
@@ -1,5 +1,6 @@
+from collections import namedtuple
 import os
-from typing import Any, Dict, Tuple, Type, List, final
+from typing import Any, Dict, Tuple, Type, List, final, Literal
 import warnings
 from shapely.geometry import shape, Point, Polygon, mapping
 import json
@@ -14,6 +15,8 @@ from asf_search.download.file_download_type import FileDownloadType
 from asf_search.CMR.translate import try_parse_date
 from asf_search.CMR.translate import try_parse_float, try_parse_int, try_round_float
 
+FileSizeInfo = namedtuple("FileSizeInfo", ["file_sizes", "md5_sums"])
+
 
 class ASFProduct:
     """
@@ -47,71 +50,71 @@ class ASFProduct:
 
     _base_properties = {
         # min viable product
-        'centerLat': {
-            'path': ['AdditionalAttributes', ('Name', 'CENTER_LAT'), 'Values', 0],
-            'cast': try_parse_float,
+        "centerLat": {
+            "path": ["AdditionalAttributes", ("Name", "CENTER_LAT"), "Values", 0],
+            "cast": try_parse_float,
         },
-        'centerLon': {
-            'path': ['AdditionalAttributes', ('Name', 'CENTER_LON'), 'Values', 0],
-            'cast': try_parse_float,
+        "centerLon": {
+            "path": ["AdditionalAttributes", ("Name", "CENTER_LON"), "Values", 0],
+            "cast": try_parse_float,
         },
-        'stopTime': {
-            'path': ['TemporalExtent', 'RangeDateTime', 'EndingDateTime'],
-            'cast': try_parse_date,
+        "stopTime": {
+            "path": ["TemporalExtent", "RangeDateTime", "EndingDateTime"],
+            "cast": try_parse_date,
         },  # primary search results sort key
-        'fileID': {'path': ['GranuleUR']},  # secondary search results sort key
-        'flightDirection': {
-            'path': [
-                'AdditionalAttributes',
-                ('Name', 'ASCENDING_DESCENDING'),
-                'Values',
+        "fileID": {"path": ["GranuleUR"]},  # secondary search results sort key
+        "flightDirection": {
+            "path": [
+                "AdditionalAttributes",
+                ("Name", "ASCENDING_DESCENDING"),
+                "Values",
                 0,
             ]
         },
-        'pathNumber': {
-            'path': ['AdditionalAttributes', ('Name', 'PATH_NUMBER'), 'Values', 0],
-            'cast': try_parse_int,
+        "pathNumber": {
+            "path": ["AdditionalAttributes", ("Name", "PATH_NUMBER"), "Values", 0],
+            "cast": try_parse_int,
         },
-        'processingLevel': {
-            'path': ['AdditionalAttributes', ('Name', 'PROCESSING_TYPE'), 'Values', 0]
+        "processingLevel": {
+            "path": ["AdditionalAttributes", ("Name", "PROCESSING_TYPE"), "Values", 0]
         },
         # commonly used
-        'url': {'path': ['RelatedUrls', ('Type', 'GET DATA'), 'URL']},
-        'startTime': {
-            'path': ['TemporalExtent', 'RangeDateTime', 'BeginningDateTime'],
-            'cast': try_parse_date,
+        "url": {"path": ["RelatedUrls", ("Type", "GET DATA"), "URL"]},
+        "startTime": {
+            "path": ["TemporalExtent", "RangeDateTime", "BeginningDateTime"],
+            "cast": try_parse_date,
         },
-        'sceneName': {
-            'path': [
-                'DataGranule',
-                'Identifiers',
-                ('IdentifierType', 'ProducerGranuleId'),
-                'Identifier',
+        "sceneName": {
+            "path": [
+                "DataGranule",
+                "Identifiers",
+                ("IdentifierType", "ProducerGranuleId"),
+                "Identifier",
             ]
         },
-        'browse': {'path': ['RelatedUrls', ('Type', [('GET RELATED VISUALIZATION', 'URL')])]},
-        'platform': {'path': ['AdditionalAttributes', ('Name', 'ASF_PLATFORM'), 'Values', 0]},
-        'bytes': {
-            'path': ['AdditionalAttributes', ('Name', 'BYTES'), 'Values', 0],
-            'cast': try_round_float,
+        "browse": {"path": ["RelatedUrls", ("Type", [("GET RELATED VISUALIZATION", "URL")])]},
+        "platform": {"path": ["AdditionalAttributes", ("Name", "ASF_PLATFORM"), "Values", 0]},
+        "bytes": {
+            "path": ["AdditionalAttributes", ("Name", "BYTES"), "Values", 0],
+            "cast": try_round_float,
         },
-        'md5sum': {'path': ['AdditionalAttributes', ('Name', 'MD5SUM'), 'Values', 0]},
-        'frameNumber': {
-            'path': ['AdditionalAttributes', ('Name', 'CENTER_ESA_FRAME'), 'Values', 0],
-            'cast': try_parse_int,
+        "md5sum": {"path": ["AdditionalAttributes", ("Name", "MD5SUM"), "Values", 0]},
+        "frameNumber": {
+            "path": ["AdditionalAttributes", ("Name", "CENTER_ESA_FRAME"), "Values", 0],
+            "cast": try_parse_int,
         },  # overloaded by S1, ALOS, and ERS
-        'granuleType': {'path': ['AdditionalAttributes', ('Name', 'GRANULE_TYPE'), 'Values', 0]},
-        'orbit': {
-            'path': ['OrbitCalculatedSpatialDomains', 0, 'OrbitNumber'],
-            'cast': try_parse_int,
+        "granuleType": {"path": ["AdditionalAttributes", ("Name", "GRANULE_TYPE"), "Values", 0]},
+        "orbit": {
+            "path": ["OrbitCalculatedSpatialDomains", 0, "OrbitNumber"],
+            "cast": try_parse_int,
         },
-        'polarization': {'path': ['AdditionalAttributes', ('Name', 'POLARIZATION'), 'Values', 0]},
-        'processingDate': {
-            'path': ['DataGranule', 'ProductionDateTime'],
-            'cast': try_parse_date,
+        "polarization": {"path": ["AdditionalAttributes", ("Name", "POLARIZATION"), "Values", 0]},
+        "processingDate": {
+            "path": ["DataGranule", "ProductionDateTime"],
+            "cast": try_parse_date,
         },
-        'sensor': {
-            'path': ['Platforms', 0, 'Instruments', 0, 'ShortName'],
+        "sensor": {
+            "path": ["Platforms", 0, "Instruments", 0, "ShortName"],
         },
     }
     """
@@ -128,16 +131,25 @@ class ASFProduct:
     combine `ASFProduct._base_properties` with their own separately defined `_base_properties`
     """
 
-    _url_types = ['GET DATA', 'EXTENDED METADATA', 'GET DATA VIA DIRECT ACCESS', 'GET RELATED VISUALIZATION', 'VIEW RELATED INFORMATION', 'USE SERVICE API']
-    
+    _url_types = [
+        "GET DATA",
+        "EXTENDED METADATA",
+        "GET DATA VIA DIRECT ACCESS",
+        "GET RELATED VISUALIZATION",
+        "VIEW RELATED INFORMATION",
+        "USE SERVICE API",
+    ]
+
+    _default_browse_extensions = (".png", ".jpg", ".jpeg")
+
     def __init__(self, args: Dict = {}, session: ASFSession = ASFSession()):
-        self.meta = args.get('meta')
-        self.umm = args.get('umm')
+        self.meta = args.get("meta")
+        self.umm = args.get("umm")
 
         translated = self.translate_product(args)
 
-        self.properties = translated['properties']
-        self.geometry = translated['geometry']
+        self.properties = translated["properties"]
+        self.geometry = translated["geometry"]
         self.baseline = None
         self.session = session
 
@@ -150,9 +162,9 @@ class ASFProduct:
         with `type`, `geometry`, and `properties` keys
         """
         return {
-            'type': 'Feature',
-            'geometry': self.geometry,
-            'properties': self.properties,
+            "type": "Feature",
+            "geometry": self.geometry,
+            "properties": self.properties,
         }
 
     def download(
@@ -172,16 +184,16 @@ class ASFProduct:
         :return: None
         """
 
-        default_filename = self.properties['fileName']
+        default_filename = self.properties["fileName"]
 
         if filename is not None:
             multiple_files = (
                 fileType == FileDownloadType.ADDITIONAL_FILES
-                and len(self.properties['additionalUrls']) > 1
+                and len(self.properties["additionalUrls"]) > 1
             ) or fileType == FileDownloadType.ALL_FILES
             if multiple_files:
                 warnings.warn(
-                    'Attempting to download multiple files for product, '
+                    "Attempting to download multiple files for product, "
                     f'ignoring user provided filename argument "{filename}", using default.'
                 )
             else:
@@ -193,12 +205,12 @@ class ASFProduct:
         urls = self.get_urls(fileType=fileType)
 
         for url in urls:
-            base_filename = '.'.join(default_filename.split('.')[:-1])
-            extension = url.split('.')[-1]
+            base_filename = ".".join(default_filename.split(".")[:-1])
+            extension = url.split(".")[-1]
             download_url(
                 url=url,
                 path=path,
-                filename=f'{base_filename}.{extension}',
+                filename=f"{base_filename}.{extension}",
                 session=session,
             )
 
@@ -206,12 +218,12 @@ class ASFProduct:
         urls = []
 
         if fileType == FileDownloadType.DEFAULT_FILE:
-            urls.append(self.properties['url'])
+            urls.append(self.properties["url"])
         elif fileType == FileDownloadType.ADDITIONAL_FILES:
-            urls.extend(self.properties.get('additionalUrls', []))
+            urls.extend(self.properties.get("additionalUrls", []))
         elif fileType == FileDownloadType.ALL_FILES:
-            urls.append(self.properties['url'])
-            urls.extend(self.properties.get('additionalUrls', []))
+            urls.append(self.properties["url"])
+            urls.extend(self.properties.get("additionalUrls", []))
         else:
             raise ValueError(
                 "Invalid FileDownloadType provided, the valid types are 'DEFAULT_FILE', 'ADDITIONAL_FILES', and 'ALL_FILES'"
@@ -224,7 +236,7 @@ class ASFProduct:
     ) -> List[Tuple[str, str]]:
         return [
             (self._parse_filename_from_url(url), url)
-            for url in self.properties.get('additionalUrls', [])
+            for url in self.properties.get("additionalUrls", [])
         ]
 
     def _parse_filename_from_url(self, url: str) -> str:
@@ -233,7 +245,7 @@ class ASFProduct:
         return filename
 
     def stack(
-        self, opts: ASFSearchOptions = None, useSubclass: Type['ASFProduct'] = None
+        self, opts: ASFSearchOptions = None, useSubclass: Type["ASFProduct"] = None
     ) -> ASFSearchResults:
         """
         Builds a baseline stack from this product.
@@ -267,13 +279,12 @@ class ASFProduct:
         return None
 
     def _get_access_urls(
-        self, 
-        url_types: List[str] = ['GET DATA', 'EXTENDED METADATA']
+        self, url_types: List[str] = ["GET DATA", "EXTENDED METADATA"]
     ) -> List[str]:
         accessUrls = []
 
         for url_type in url_types:
-            if urls := self.umm_get(self.umm, 'RelatedUrls', ('Type', [(url_type, 'URL')]), 0):
+            if urls := self.umm_get(self.umm, "RelatedUrls", ("Type", [(url_type, "URL")]), 0):
                 accessUrls.extend(urls)
 
         return sorted(list(set(accessUrls)))
@@ -281,27 +292,28 @@ class ASFProduct:
     def _get_urls(self) -> List[str]:
         """Finds and returns all umm urls"""
         urls = self._get_access_urls(self._url_types)
-        return [
-            url for url in urls if not url.startswith('s3://')
-        ]
+        return [url for url in urls if not url.startswith("s3://")]
 
     def _get_s3_uris(self) -> List[str]:
         """Finds and returns all umm S3 direct access uris"""
         s3_urls = self._get_access_urls(self._url_types)
-        return [url for url in s3_urls if url.startswith('s3://')]
+        return [url for url in s3_urls if url.startswith("s3://")]
 
     def _get_additional_urls(self) -> List[str]:
         """Finds and returns all non-md5/image urls and filters out the existing `url` property"""
         access_urls = self._get_urls()
         return [
-            url for url in access_urls
-            if not url.endswith('.md5')
-            and not url.endswith('.png')
-            and url != self.properties['url']
-            and 's3credentials' not in url
+            url
+            for url in access_urls
+            if not url.endswith(".md5")
+            and not url.endswith(".png")
+            and url != self.properties["url"]
+            and "s3credentials" not in url
         ]
 
-    def find_urls(self, extension: str = None, pattern: str = r'.*', directAccess: bool = False) -> List[str]:
+    def find_urls(
+        self, extension: str = None, pattern: str = r".*", directAccess: bool = False
+    ) -> List[str]:
         """
         Searches for all urls matching a given extension and/or pattern
         param extension: the file extension to search for. (Defaults to `None`)
@@ -311,13 +323,13 @@ class ASFProduct:
         param directAccess: should search in s3 bucket urls (Defaults to `False`)
         """
         search_list = self._get_s3_uris() if directAccess else self._get_urls()
-        
+
         def _get_extension(file_url: str):
             path = parse.urlparse(file_url).path
             return os.path.splitext(path)[-1]
-        
+
         if extension is not None:
-            search_list = [url for url in search_list if _get_extension(url) == extension] 
+            search_list = [url for url in search_list if _get_extension(url) == extension]
 
         regexp = re.compile(pattern=pattern)
 
@@ -327,7 +339,7 @@ class ASFProduct:
         """
         Finds the centroid of a product
         """
-        coords = mapping(shape(self.geometry))['coordinates'][0]
+        coords = mapping(shape(self.geometry))["coordinates"][0]
         lons = [p[0] for p in coords]
         if max(lons) - min(lons) > 180:
             unwrapped_coords = [a if a[0] > 0 else [a[0] + 360, a[1]] for a in coords]
@@ -336,7 +348,7 @@ class ASFProduct:
 
         return Polygon(unwrapped_coords).centroid
 
-    def remotezip(self, session: ASFSession) -> 'RemoteZip':  # type: ignore # noqa: F821
+    def remotezip(self, session: ASFSession) -> "RemoteZip":  # type: ignore # noqa: F821
         """Returns a RemoteZip object which can be used to download
         a part of an ASFProduct's zip archive. (See example in examples/5-Download.ipynb)
 
@@ -348,36 +360,36 @@ class ASFProduct:
         """
         from .download.download import remotezip
 
-        return remotezip(self.properties['url'], session=session)
+        return remotezip(self.properties["url"], session=session)
 
     def _read_umm_property(self, umm: Dict, mapping: Dict) -> Any:
-        value = self.umm_get(umm, *mapping['path'])
-        if mapping.get('cast') is None:
+        value = self.umm_get(umm, *mapping["path"])
+        if mapping.get("cast") is None:
             return value
 
-        return self.umm_cast(mapping['cast'], value)
+        return self.umm_cast(mapping["cast"], value)
 
     def _get_geometry(self, item: Dict):
-        """Helper method that creates a geometry context dictionary. 
+        """Helper method that creates a geometry context dictionary.
         Meant primarily for NISARProduct to override for dateline multipolygon parsing.
         """
         try:
-            coordinates = item['umm']['SpatialExtent']['HorizontalSpatialDomain']['Geometry'][
-                'GPolygons'
-            ][0]['Boundary']['Points']
-            coordinates = [[c['Longitude'], c['Latitude']] for c in coordinates]
-            geometry = {'coordinates': [coordinates], 'type': 'Polygon'}
+            coordinates = item["umm"]["SpatialExtent"]["HorizontalSpatialDomain"]["Geometry"][
+                "GPolygons"
+            ][0]["Boundary"]["Points"]
+            coordinates = [[c["Longitude"], c["Latitude"]] for c in coordinates]
+            geometry = {"coordinates": [coordinates], "type": "Polygon"}
         except KeyError:
-            geometry = {'coordinates': None, 'type': 'Polygon'}
+            geometry = {"coordinates": None, "type": "Polygon"}
 
         return geometry
-    
+
     def translate_product(self, item: Dict) -> Dict:
         """
         Generates `properties` and `geometry` from the CMR UMM response
         """
         geometry = self._get_geometry(item)
-        umm = item.get('umm')
+        umm = item.get("umm")
 
         # additionalAttributes = {attr['Name']: attr['Values'] for attr in umm['AdditionalAttributes']}
 
@@ -386,21 +398,21 @@ class ASFProduct:
             for prop, umm_mapping in self._base_properties.items()
         }
 
-        if properties.get('url') is not None:
-            properties['fileName'] = properties['url'].split('/')[-1]
+        if properties.get("url") is not None:
+            properties["fileName"] = properties["url"].split("/")[-1]
         else:
-            properties['fileName'] = None
+            properties["fileName"] = None
 
         # Fallbacks
-        if properties.get('beamModeType') is None:
-            properties['beamModeType'] = self.umm_get(
-                umm, 'AdditionalAttributes', ('Name', 'BEAM_MODE'), 'Values', 0
+        if properties.get("beamModeType") is None:
+            properties["beamModeType"] = self.umm_get(
+                umm, "AdditionalAttributes", ("Name", "BEAM_MODE"), "Values", 0
             )
 
-        if properties.get('platform') is None:
-            properties['platform'] = self.umm_get(umm, 'Platforms', 0, 'ShortName')
+        if properties.get("platform") is None:
+            properties["platform"] = self.umm_get(umm, "Platforms", 0, "ShortName")
 
-        return {'geometry': geometry, 'properties': properties, 'type': 'Feature'}
+        return {"geometry": geometry, "properties": properties, "type": "Feature"}
 
     def get_sort_keys(self) -> Tuple[str, str]:
         """
@@ -409,9 +421,9 @@ class ASFProduct:
         """
         # `sort()` will raise an error when comparing `NoneType`,
         # using self._read_property() to wrap standard `dict.get()` for possible `None` values
-        primary_key = self._read_property(key='stopTime', default='')
+        primary_key = self._read_property(key="stopTime", default="")
         secondary_key = self._read_property(
-            key='fileID', default=self._read_property('sceneName', '')
+            key="fileID", default=self._read_property("sceneName", "")
         )
 
         return (primary_key, secondary_key)
@@ -428,6 +440,52 @@ class ASFProduct:
 
         return output
 
+    def _get_file_sizes_and_sums(self) -> FileSizeInfo | None:
+        """Helper method for returning file sizes and md5sums from `ArchiveAndDistributionInformation` if available.
+        Returns None if `ArchiveAndDistributionInformation` isn't defined"""
+        bytes_temp = self.umm_get(self.umm, "DataGranule", "ArchiveAndDistributionInformation")
+        if bytes_temp is None or len(bytes_temp) == 0:
+            return None
+
+        if bytes_temp[0].get("SizeInBytes"):
+            size_key = "SizeInBytes"
+            size_format = "Format"
+        else:
+            size_key = "Size"
+            size_format = "SizeUnit"
+
+        bytes_mapping = {
+            entry["Name"]: {
+                "bytes": entry.get(size_key),
+                "format": entry.get(size_format),
+            }
+            for entry in bytes_temp
+        }
+
+        md5sum_mapping = {
+            entry["Name"]: entry.get("Checksum", {"Value": None})["Value"] for entry in bytes_temp
+        }
+
+        return FileSizeInfo(bytes_mapping, md5sum_mapping)
+
+    def _set_additional_metadata(self):
+        """Helper method for data migrated off-prem"""
+        file_info = self._get_file_sizes_and_sums()
+
+        if file_info is not None:
+            self.properties["bytes"], self.properties["md5sum"] = file_info
+        self.properties["additionalUrls"] = self._get_additional_urls()
+        self.properties["browse"] = [
+            url for url in self._get_urls() if url.endswith(self._default_browse_extensions)
+        ]
+        self.properties["s3Urls"] = self._get_s3_uris()
+
+        self.properties["conceptID"] = self.umm_get(self.meta, "collection-concept-id")
+
+        center = self.centroid()
+        self.properties["centerLat"] = center.y
+        self.properties["centerLon"] = center.x
+
     @final
     @staticmethod
     def umm_get(item: Dict, *args):
@@ -535,7 +593,7 @@ class ASFProduct:
                     return None
             if item is None:
                 return None
-        if item in [None, 'NA', 'N/A', '']:
+        if item in [None, "NA", "N/A", ""]:
             item = None
         return item
 


=====================================
asf_search/CMR/datasets.py
=====================================
@@ -205,6 +205,10 @@ dataset_collections = {
         "ALOS_PSR_L2.2",
         "ALOS_PSR_RTC_HIGH",
         "ALOS_PSR_RTC_LOW",
+        "ALOS_L10_PSR",
+        "ALOS_L11_PSR",
+        "ALOS_L15_PSR",
+        "ALOS_RTC_PSR",
     },
     "ALOS AVNIR-2": {"ALOS_AVNIR_OBS_ORI", "ALOS_AVNIR_OBS_ORI_BROWSE"},
     "SIR-C": {
@@ -266,6 +270,10 @@ dataset_collections = {
         "SPL1C_S0_HiRes_QA_001",
         "SPL1C_S0_HiRes_QA_002",
         "SPL1C_S0_HiRes_QA_003",
+        "SMAP_L1A_RO_V3",
+        "SMAP_L1A_V2",
+        "SMAP_L1B_S0_V3",
+        "SMAP_L1C_S0_V3",
     },
     "UAVSAR": {
         "UAVSAR_INSAR_AMP",
@@ -394,6 +402,10 @@ collections_per_platform = {
         "ALOS_PSR_L2.2",
         "ALOS_PSR_RTC_HIGH",
         "ALOS_PSR_RTC_LOW",
+        "ALOS_L10_PSR",
+        "ALOS_L11_PSR",
+        "ALOS_L15_PSR",
+        "ALOS_RTC_PSR",
     },
     "ALOS-2": {"ALOS2_L1_PSR2"},
     "ERS-1": {"ERS-1_L0", "ERS-1_L1"},
@@ -429,6 +441,10 @@ collections_per_platform = {
         "SPL1A_RO_QA_001",
         "SPL1A_RO_QA_002",
         "SPL1A_RO_QA_003",
+        "SMAP_L1A_RO_V3",
+        "SMAP_L1A_V2",
+        "SMAP_L1B_S0_V3",
+        "SMAP_L1C_S0_V3",
     },
     "G-III": {
         "UAVSAR_POL_META",
@@ -652,6 +668,10 @@ collections_by_processing_level = {
     "GSLC": {"NISAR_L2_GSLC_BETA_V1", "NISAR_L2_GSLC_PROVISIONAL_V1", "NISAR_L2_GSLC_V1"},
     "GUNW": {"NISAR_L2_GUNW_BETA_V1", "NISAR_L2_GUNW_PROVISIONAL_V1", "NISAR_L2_GUNW_V1"},
     "SME2": {"NISAR_L3_SME2_BETA_V1", "NISAR_L3_SME2_PROVISIONAL_V1", "NISAR_L3_SME2_V1"},
+    "L1A_RADAR_RO": {"L1A_RADAR_RO"},
+    "L1C_S0_HIRES": {"L1C_S0_HIRES"},
+    "L1B_S0_LORES": {"L1B_S0_LORES"},
+    "L1A_RADAR": {"L1A_RADAR"},
 }
 
 


=====================================
asf_search/Products/ALOSProduct.py
=====================================
@@ -13,31 +13,40 @@ class ALOSProduct(ASFStackableProduct):
 
     _base_properties = {
         **ASFStackableProduct._base_properties,
-        'frameNumber': {
-            'path': ['AdditionalAttributes', ('Name', 'FRAME_NUMBER'), 'Values', 0],
-            'cast': try_parse_int,
+        "frameNumber": {
+            "path": ["AdditionalAttributes", ("Name", "FRAME_NUMBER"), "Values", 0],
+            "cast": try_parse_int,
         },
-        'faradayRotation': {
-            'path': ['AdditionalAttributes', ('Name', 'FARADAY_ROTATION'), 'Values', 0],
-            'cast': try_parse_float,
+        "faradayRotation": {
+            "path": ["AdditionalAttributes", ("Name", "FARADAY_ROTATION"), "Values", 0],
+            "cast": try_parse_float,
         },
-        'offNadirAngle': {
-            'path': ['AdditionalAttributes', ('Name', 'OFF_NADIR_ANGLE'), 'Values', 0],
-            'cast': try_parse_float,
+        "offNadirAngle": {
+            "path": ["AdditionalAttributes", ("Name", "OFF_NADIR_ANGLE"), "Values", 0],
+            "cast": try_parse_float,
         },
-        'bytes': {
-            'path': ['AdditionalAttributes', ('Name', 'BYTES'), 'Values', 0],
-            'cast': try_round_float,
+        "bytes": {
+            "path": ["AdditionalAttributes", ("Name", "BYTES"), "Values", 0],
+            "cast": try_round_float,
         },
-        'insarStackId': {'path': ['AdditionalAttributes', ('Name', 'INSAR_STACK_ID'), 'Values', 0]},
-        'beamModeType': {'path': ['AdditionalAttributes', ('Name', 'BEAM_MODE_TYPE'), 'Values', 0]},
+        "insarStackId": {"path": ["AdditionalAttributes", ("Name", "INSAR_STACK_ID"), "Values", 0]},
+        "beamModeType": {"path": ["AdditionalAttributes", ("Name", "BEAM_MODE"), "Values", 0]},
+        "polarization": {"path": ["AdditionalAttributes", ("Name", "POLARIZATION"), "Values"]},
     }
 
     def __init__(self, args: Dict = {}, session: ASFSession = ASFSession()):
         super().__init__(args, session)
 
-        if self.properties.get('groupID') is None:
-            self.properties['groupID'] = self.properties['sceneName']
+        if self.properties["bytes"] is None:
+            self._set_additional_metadata()
+
+        if (
+            self.properties["polarization"] is not None
+            and len(self.properties["polarization"]) == 1
+        ):
+            self.properties["polarization"] = self.properties["polarization"].pop()
+        if self.properties.get("groupID") is None:
+            self.properties["groupID"] = self.properties["sceneName"]
 
     @staticmethod
     def get_default_baseline_product_type() -> Union[str, None]:


=====================================
asf_search/Products/SMAPProduct.py
=====================================
@@ -10,10 +10,18 @@ class SMAPProduct(ASFProduct):
 
     _base_properties = {
         **ASFProduct._base_properties,
-        'groupID': {'path': ['AdditionalAttributes', ('Name', 'GROUP_ID'), 'Values', 0]},
-        'insarStackId': {'path': ['AdditionalAttributes', ('Name', 'INSAR_STACK_ID'), 'Values', 0]},
-        'md5sum': {'path': ['AdditionalAttributes', ('Name', 'MD5SUM'), 'Values', 0]},
+        "groupID": {"path": ["AdditionalAttributes", ("Name", "GROUP_ID"), "Values", 0]},
+        "insarStackId": {"path": ["AdditionalAttributes", ("Name", "INSAR_STACK_ID"), "Values", 0]},
+        "md5sum": {"path": ["AdditionalAttributes", ("Name", "MD5SUM"), "Values", 0]},
+        "processingLevel": {
+            "path": ["AdditionalAttributes", ("Name", "PRODUCT_TYPE"), "Values", 0]
+        },
     }
 
     def __init__(self, args: Dict = {}, session: ASFSession = ASFSession()):
         super().__init__(args, session)
+        if self.properties["md5sum"] is None:
+            self._set_additional_metadata()
+
+        if self.properties.get("groupID") is None:
+            self.properties["groupID"] = self.properties["sceneName"]


=====================================
asf_search/constants/PRODUCT_TYPE.py
=====================================
@@ -1,29 +1,33 @@
 # Sentinel-1
-GRD_HD = 'GRD_HD'
-GRD_MD = 'GRD_MD'
-GRD_MS = 'GRD_MS'
-GRD_HS = 'GRD_HS'
-GRD_FD = 'GRD_FD'
-SLC = 'SLC'
-OCN = 'OCN'
-RAW = 'RAW'
-METADATA_GRD_HD = 'METADATA_GRD_HD'
-METADATA_GRD_MD = 'METADATA_GRD_MD'
-METADATA_GRD_MS = 'METADATA_GRD_MS'
-METADATA_GRD_HS = 'METADATA_GRD_HS'
-METADATA_SLC = 'METADATA_SLC'
-METADATA_OCN = 'METADATA_OCN'
-METADATA_RAW = 'METADATA_RAW'
-BURST = 'BURST'
+GRD_HD = "GRD_HD"
+GRD_MD = "GRD_MD"
+GRD_MS = "GRD_MS"
+GRD_HS = "GRD_HS"
+GRD_FD = "GRD_FD"
+SLC = "SLC"
+OCN = "OCN"
+RAW = "RAW"
+METADATA_GRD_HD = "METADATA_GRD_HD"
+METADATA_GRD_MD = "METADATA_GRD_MD"
+METADATA_GRD_MS = "METADATA_GRD_MS"
+METADATA_GRD_HS = "METADATA_GRD_HS"
+METADATA_SLC = "METADATA_SLC"
+METADATA_OCN = "METADATA_OCN"
+METADATA_RAW = "METADATA_RAW"
+BURST = "BURST"
 
 # ALOS PALSAR
-L1_0 = 'L1.0'
-L1_1 = 'L1.1'
-L1_5 = 'L1.5'
-L2_2 = 'L2.2'
-RTC_LOW_RES = 'RTC_LOW_RES'
-RTC_HIGH_RES = 'RTC_HI_RES'
-KMZ = 'KMZ'
+L1_0 = "L1.0"
+L1_1 = "L1.1"
+L1_5 = "L1.5"
+L2_2 = "L2.2"
+RTC_LOW_RES = "RTC_LOW_RES"
+RTC_HIGH_RES = "RTC_HI_RES"
+KMZ = "KMZ"
+L1A_RADAR_RO = "L1A_RADAR_RO"
+L1C_S0_HIRES = "L1C_S0_HIRES"
+L1B_S0_LORES = "L1B_S0_LORES"
+L1A_RADAR = "L1A_RADAR"
 
 # ALOS AVNIR
 # No PROCESSING_TYPE attribute in CMR
@@ -32,45 +36,45 @@ KMZ = 'KMZ'
 # SLC and SLC metadata are both 'SLC', provided by Sentinel-1 constants
 
 # Sentinel-1 InSAR
-GUNW_STD = 'GUNW_STD'
-GUNW_AMP = 'GUNW_AMP'
-GUNW_CON = 'GUNW_CON'
-GUN_COH = 'GUNW_COH'
-GUNW_UNW = 'GUNW_UNW'
+GUNW_STD = "GUNW_STD"
+GUNW_AMP = "GUNW_AMP"
+GUNW_CON = "GUNW_CON"
+GUN_COH = "GUNW_COH"
+GUNW_UNW = "GUNW_UNW"
 
 # SMAP
-L1A_RADAR_RO_HDF5 = 'L1A_Radar_RO_HDF5'
-L1A_RADAR_HDF5 = 'L1A_Radar_HDF5'
-L1B_S0_LOW_RES_HDF5 = 'L1B_S0_LoRes_HDF5'
-L1C_S0_HIGH_RES_HDF5 = 'L1C_S0_HiRes_HDF5'
-L1A_RADAR_RO_QA = 'L1A_Radar_RO_QA'
-L1A_RADAR_QA = 'L1A_Radar_QA'
-L1B_S0_LOW_RES_QA = 'L1B_S0_LoRes_QA'
-L1C_S0_HIGH_RES_QA = 'L1C_S0_HiRes_QA'
-L1A_RADAR_RO_ISO_XML = 'L1A_Radar_RO_ISO_XML'
-L1B_S0_LOW_RES_ISO_XML = 'L1B_S0_LoRes_ISO_XML'
-L1C_S0_HIGH_RES_ISO_XML = 'L1C_S0_HiRes_ISO_XML'
+L1A_RADAR_RO_HDF5 = "L1A_Radar_RO_HDF5"
+L1A_RADAR_HDF5 = "L1A_Radar_HDF5"
+L1B_S0_LOW_RES_HDF5 = "L1B_S0_LoRes_HDF5"
+L1C_S0_HIGH_RES_HDF5 = "L1C_S0_HiRes_HDF5"
+L1A_RADAR_RO_QA = "L1A_Radar_RO_QA"
+L1A_RADAR_QA = "L1A_Radar_QA"
+L1B_S0_LOW_RES_QA = "L1B_S0_LoRes_QA"
+L1C_S0_HIGH_RES_QA = "L1C_S0_HiRes_QA"
+L1A_RADAR_RO_ISO_XML = "L1A_Radar_RO_ISO_XML"
+L1B_S0_LOW_RES_ISO_XML = "L1B_S0_LoRes_ISO_XML"
+L1C_S0_HIGH_RES_ISO_XML = "L1C_S0_HiRes_ISO_XML"
 
 # UAVSAR
-AMPLITUDE = 'AMPLITUDE'
-STOKES = 'STOKES'
-AMPLITUDE_GRD = 'AMPLITUDE_GRD'
-PROJECTED = 'PROJECTED'
-PROJECTED_ML5X5 = 'PROJECTED_ML5X5'
-PROJECTED_ML3X3 = 'PROJECTED_ML3X3'
-INTERFEROMETRY_GRD = 'INTERFEROMETRY_GRD'
-INTERFEROMETRY = 'INTERFEROMETRY'
-COMPLEX = 'COMPLEX'
+AMPLITUDE = "AMPLITUDE"
+STOKES = "STOKES"
+AMPLITUDE_GRD = "AMPLITUDE_GRD"
+PROJECTED = "PROJECTED"
+PROJECTED_ML5X5 = "PROJECTED_ML5X5"
+PROJECTED_ML3X3 = "PROJECTED_ML3X3"
+INTERFEROMETRY_GRD = "INTERFEROMETRY_GRD"
+INTERFEROMETRY = "INTERFEROMETRY"
+COMPLEX = "COMPLEX"
 # KMZ provided by ALOS PALSAR
-INC = 'INC'
-SLOPE = 'SLOPE'
-DEM_TIFF = 'DEM_TIFF'
-PAULI = 'PAULI'
-METADATA = 'METADATA'
+INC = "INC"
+SLOPE = "SLOPE"
+DEM_TIFF = "DEM_TIFF"
+PAULI = "PAULI"
+METADATA = "METADATA"
 
 # RADARSAT
-L0 = 'L0'
-L1 = 'L1'
+L0 = "L0"
+L1 = "L1"
 
 # ERS
 # L0 provided by RADARSAT
@@ -81,67 +85,67 @@ L1 = 'L1'
 # L1 provided by RADARSAT
 
 # AIRSAR
-CTIF = 'CTIF'
-PTIF = 'PTIF'
-LTIF = 'LTIF'
-JPG = 'JPG'
-LSTOKES = 'LSTOKES'
-PSTOKES = 'PSTOKES'
-CSTOKES = 'CSTOKES'
-DEM = 'DEM'
-THREEFP = '3FP'
+CTIF = "CTIF"
+PTIF = "PTIF"
+LTIF = "LTIF"
+JPG = "JPG"
+LSTOKES = "LSTOKES"
+PSTOKES = "PSTOKES"
+CSTOKES = "CSTOKES"
+DEM = "DEM"
+THREEFP = "3FP"
 
 # OPERA-S1
-RTC = 'RTC'
-CSLC = 'CSLC'
-RTC_STATIC = 'RTC-STATIC'
-CSLC_STATIC = 'CSLC-STATIC'
-DISP_S1 = 'DISP-S1'
-DISP_S1_STATIC = 'DISP-S1-STATIC'
-DIST_ALERT_S1 = 'DIST-ALERT-S1'
+RTC = "RTC"
+CSLC = "CSLC"
+RTC_STATIC = "RTC-STATIC"
+CSLC_STATIC = "CSLC-STATIC"
+DISP_S1 = "DISP-S1"
+DISP_S1_STATIC = "DISP-S1-STATIC"
+DIST_ALERT_S1 = "DIST-ALERT-S1"
 
 # TROPO
-TROPO_ZENITH = 'TROPO-ZENITH'
-ECMWF_TROPO = 'ECMWF_TROPO'
+TROPO_ZENITH = "TROPO-ZENITH"
+ECMWF_TROPO = "ECMWF_TROPO"
 
 ### NISAR
 
 ### NISAR Science Products ###
-L0B = 'L0B'
+L0B = "L0B"
 """alias for RRSD Level 0B product types"""
 
-RRSD = 'RRSD'
+RRSD = "RRSD"
 """Level 0B Radar Raw Signal Data"""
 
-RSLC = 'RSLC'
+RSLC = "RSLC"
 """Level 1 Range-Doppler Single Look Complex"""
-RIFG = 'RIFG'
+RIFG = "RIFG"
 """Level 1 Range-Doppler Wrapped Interferrogram"""
-RUNW = 'RUNW'
+RUNW = "RUNW"
 """Level 1 Range-Doppler Unwrapped Interferrogram"""
-ROFF = 'ROFF'
+ROFF = "ROFF"
 """Level 1 Range-Doppler Pixel Offsets"""
-GSLC = 'GSLC'
+GSLC = "GSLC"
 """Level 2 Geocoded Single Look Complex"""
-GCOV = 'GCOV'
+GCOV = "GCOV"
 """Level 2 Geocoded Polarimetric Covariance"""
-GUNW = 'GUNW'
+GUNW = "GUNW"
 """"Level 2 Geocoded Unwrapped Inteferrogram"""
-GOFF = 'GOFF'
+GOFF = "GOFF"
 """Level 2 Geocoded Pixel Offsets"""
-SME2 = 'SME2'
+SME2 = "SME2"
 """Level 3 Soil Moisture EASE-Grid 2.0"""
 
 ### NISAR ANCILLARY PRODUCTS ###
 
-CRSD = 'CRSD'
+CRSD = "CRSD"
 """NISAR Radar Raw Signal Calibration Data"""
 
 # NISAR Coordinated Observation Plan (NISAR_COP)
-DCOP = 'DCOP'
+DCOP = "DCOP"
 """NISAR Coordinates Observation Plan"""
 # NISAR_OROST
-OROST = 'OROST'
+OROST = "OROST"
 """NISAR Radar Observation Sequence Table"""
 # NISAR_STUF
 STUF = "STUF"
@@ -157,7 +161,7 @@ LRCLK_UTC = "LRCLK_UTC"
 
 
 # NISAR Orbit Ephemeris (NISAR_OE)
-# "The NASA-ISRO Synthetic Aperture Radar (NISAR) Orbit Ephemeris collection contains 
+# "The NASA-ISRO Synthetic Aperture Radar (NISAR) Orbit Ephemeris collection contains
 # the state vector files for the NISAR mission"
 FOE = "FOE"
 """Forecast Orbit Ephemeris"""
@@ -169,7 +173,7 @@ POE = "POE"
 """	Precise Orbit Ephemeris"""
 
 # NISAR Ancillary and Auxiliary Data (NISAR_ANC_AUX)
-# "The NASA-ISRO Synthetic Aperture Radar (NISAR) Ancilliary and Auxiallry collection contains products 
+# "The NASA-ISRO Synthetic Aperture Radar (NISAR) Ancilliary and Auxiallry collection contains products
 # that are supplementary information for the NISAR mission and are created a limited number of times.""
 TSR_STATIC = "TSR_STATIC"
 """Time Series Ratio (TSR_STATIC)"""
@@ -218,7 +222,7 @@ ECMWF_SMST = "ECMWF_SMST"
 # DEM = "DEM"
 # """	Digital Elevation Model"""
 
-# LOCAL_INC_ANG = "LOCAL_INC_ANG" 
+# LOCAL_INC_ANG = "LOCAL_INC_ANG"
 # """	Local Incidence Angles"""
 # VWC = "VWC"
 # """	Vegetation water content"""



View it on GitLab: https://salsa.debian.org/debian-gis-team/asf-search/-/commit/8667e7a562519aa476929ee8d053c2ed9ae605ac

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/asf-search/-/commit/8667e7a562519aa476929ee8d053c2ed9ae605ac
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20260912/e0a905b3/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list