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

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Sat Feb 7 11:18:48 GMT 2026



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


Commits:
ac264e6b by Antonio Valentino at 2026-02-07T11:11:59+00:00
New upstream version 11.0.3
- - - - -


4 changed files:

- CHANGELOG.md
- asf_search/ASFProduct.py
- asf_search/Products/NISARProduct.py
- tests/yml_tests/test_authenticated/test_ASFSubproduct_Auth.yml


Changes:

=====================================
CHANGELOG.md
=====================================
@@ -25,6 +25,11 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 -
 
 -->
+------
+## [v11.0.3](https://github.com/asfadmin/Discovery-asf_search/compare/v11.0.2...v11.0.3)
+### Fixed
+- Properly parse multipolygons from NISAR UMM-G that cross dateline, unwrap geometry (add 360 to negative longitude polygon)
+
 ------
 ## [v11.0.2](https://github.com/asfadmin/Discovery-asf_search/compare/v11.0.1...v11.0.2)
 ### Fixed


=====================================
asf_search/ASFProduct.py
=====================================
@@ -357,9 +357,9 @@ class ASFProduct:
 
         return self.umm_cast(mapping['cast'], value)
 
-    def translate_product(self, item: Dict) -> Dict:
-        """
-        Generates `properties` and `geometry` from the CMR UMM response
+    def _get_geometry(self, item: Dict):
+        """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'][
@@ -370,6 +370,13 @@ class ASFProduct:
         except KeyError:
             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')
 
         # additionalAttributes = {attr['Name']: attr['Values'] for attr in umm['AdditionalAttributes']}


=====================================
asf_search/Products/NISARProduct.py
=====================================
@@ -1,7 +1,10 @@
+from shapely import unary_union, multipolygons
 from typing import Dict, Tuple, Union
 from asf_search import ASFSearchOptions, ASFSession, ASFStackableProduct
 from asf_search.CMR.translate import try_parse_frame_coverage, try_parse_bool, try_parse_int
-
+from shapely.geometry import shape, MultiPolygon
+from shapely.geometry.base import BaseGeometry
+from shapely.ops import transform
 class NISARProduct(ASFStackableProduct):
     """
     Used for NISAR dataset products
@@ -73,3 +76,50 @@ class NISARProduct(ASFStackableProduct):
             return (self._read_property('processingDate', ''), keys[1])
 
         return keys
+
+
+    def _get_geometry(self, item: Dict) -> dict:
+        """Overload for dateline multipolygon parsing.
+        # TODO consider implications of moving this to base ASFProduct class in future
+        """
+        try:
+            polygons = item['umm']['SpatialExtent']['HorizontalSpatialDomain']['Geometry'][
+                'GPolygons'
+            ]
+            # dateline spanning scenes are stored as multiple polygons in CMR, 
+            # we need to unwrap and merge them
+            if len(polygons) > 1:
+                polygon_shapes = []
+                for polygon in polygons:
+                    coordinates = [[c['Longitude'], c['Latitude']] for c in polygon['Boundary']['Points']]
+                    geometry = self._get_unwrapped({'coordinates': [coordinates], 'type': 'Polygon'})
+
+                    polygon_shapes.append(geometry)
+                
+                geom = unary_union(multipolygons(polygon_shapes))
+
+                # sometimes the dateline spanning polygons don't overlap properly
+                if isinstance(geom, MultiPolygon):
+                    geom = geom.convex_hull
+
+                return {'coordinates': [geom.exterior.coords], 'type': 'Polygon'}
+            else:
+                coordinates = polygons[0]['Boundary']['Points']
+                coordinates = [[c['Longitude'], c['Latitude']] for c in coordinates]
+                geometry = {'coordinates': [coordinates], 'type': 'Polygon'}
+        except KeyError:
+            geometry = {'coordinates': None, 'type': 'Polygon'}
+
+        return geometry
+
+    def _get_unwrapped(self, geometry: dict) -> BaseGeometry:
+        def unwrap_shape(x, y, z=None):
+            x = x if x > 0 else x + 360
+            return tuple([x, y])
+        wrapped = shape(geometry)
+        if wrapped.bounds[0] < 0 or wrapped.bounds[2] < 0:
+            unwrapped = transform(unwrap_shape, wrapped)
+        else:
+            unwrapped = wrapped
+
+        return unwrapped


=====================================
tests/yml_tests/test_authenticated/test_ASFSubproduct_Auth.yml
=====================================
@@ -2,4 +2,11 @@ tests:
 - test-collection-attributes RSLC:
     params:
       processingLevel: RSLC
-    expected_attributes: ['ASCENDING_DESCENDING', 'TRACK_NUMBER', 'FRAME_NUMBER', 'FREQUENCY_A_POLARIZATION', 'FREQUENCY_B_POLARIZATION', 'PRODUCT_VERSION', 'PROCESSING_CENTER', 'FREQUENCIES', 'FULL_FRAME', 'FREQUENCY_A_RANGE_BANDWIDTH', 'FREQUENCY_B_RANGE_BANDWIDTH', 'RANGE_BANDWIDTH_CONCAT', 'JOINT_OBSERVATION', 'PROCESSING_LEVEL', 'PRODUCT_TYPE', 'PRODUCT_TYPE_DESC', 'PRODUCTION_PIPELINE', 'FREQUENCY_A_POLARIZATION_CONCAT', 'FREQUENCY_B_POLARIZATION_CONCAT', 'ORBIT_TYPE', 'STACK_ID', 'INPUT_RCID']
\ No newline at end of file
+    expected_attributes: ['ASCENDING_DESCENDING', 'TRACK_NUMBER', 'FRAME_NUMBER', 'FREQUENCY_A_POLARIZATION', 'FREQUENCY_B_POLARIZATION', 'PRODUCT_VERSION', 'PROCESSING_CENTER', 'FREQUENCIES', 'FULL_FRAME', 'FREQUENCY_A_RANGE_BANDWIDTH', 'FREQUENCY_B_RANGE_BANDWIDTH', 'RANGE_BANDWIDTH_CONCAT', 'JOINT_OBSERVATION', 'PROCESSING_LEVEL', 'PRODUCT_TYPE', 'PRODUCT_TYPE_DESC', 'PRODUCTION_PIPELINE', 'FREQUENCY_A_POLARIZATION_CONCAT', 'FREQUENCY_B_POLARIZATION_CONCAT', 'ORBIT_TYPE', 'STACK_ID', 'INPUT_RCID']
+
+- Test NISAR ASFSubproduct cross-dateline:
+    scenes: [
+      'NISAR_L0_PR_RRSD_008_005_A_153S_20251022T161112_20251022T161132_P05000_F_J_001', # polygons don't overlap correctly, need convex_hull
+      'NISAR_L0_PR_RRSD_008_086_D_148S_20251028T071837_20251028T071901_P05000_F_J_001' # simple unary union case
+    ]
+    expected_subclass: NISARProduct



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

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/asf-search/-/commit/ac264e6bade6a89809112f31c0e8446fa37eb978
You're receiving this email because of your account on salsa.debian.org.


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


More information about the Pkg-grass-devel mailing list