[Git][debian-gis-team/xsar][upstream] New upstream version 2026.08.31

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Sat Sep 5 11:01:36 BST 2026



Antonio Valentino pushed to branch upstream at Debian GIS Project / xsar


Commits:
11c2532c by Antonio Valentino at 2026-09-05T09:40:51+00:00
New upstream version 2026.08.31
- - - - -


17 changed files:

- .git_archival.txt
- .github/workflows/README.md
- .github/workflows/ci.yaml
- .github/workflows/conda-feedstock-check.yml
- .github/workflows/publish.yml
- .github/workflows/upstream-dev.yaml
- ci/requirements/environment.yaml
- docs/conf.py
- docs/examples/mask.ipynb
- docs/examples/xsar_batch_datarmor.ipynb
- environment.yml
- pyproject.toml
- src/xsar/base_dataset.py
- src/xsar/base_meta.py
- src/xsar/config.yml
- src/xsar/ipython_backends.py
- test/test_invalid_mask_geometries.py


Changes:

=====================================
.git_archival.txt
=====================================
@@ -1 +1 @@
-ref-names: HEAD -> main, tag: 2026.02.10
\ No newline at end of file
+ref-names: HEAD -> main, tag: 2026.08.31
\ No newline at end of file


=====================================
.github/workflows/README.md
=====================================
@@ -20,7 +20,7 @@ The jobs contains 5 steps:
 
 - Setup conda and create environment : using a community github action package [conda-incubator/setup-miniconda at v2](https://github.com/marketplace/actions/setup-miniconda)
 - Install xsar dependencies
-- Check xsar environment: you can see in a debug job the version of conda, python, rasterio, gdal, cartopy and dask
+- Check xsar environment: you can see in a debug job the version of conda, python, rasterio, gdal and dask
 - Install xsar
 - Testing xsar : run the script test `test/test_xsar.py`
 


=====================================
.github/workflows/ci.yaml
=====================================
@@ -23,7 +23,7 @@ jobs:
     outputs:
       triggered: ${{ steps.detect-trigger.outputs.trigger-found }}
     steps:
-      - uses: actions/checkout at v6
+      - uses: actions/checkout at v7
         with:
           fetch-depth: 2
       - uses: xarray-contrib/ci-trigger at v1
@@ -50,7 +50,7 @@ jobs:
 
     steps:
       - name: Checkout the repository
-        uses: actions/checkout at v6
+        uses: actions/checkout at v7
         with:
           # need to fetch all tags to get a correct version
           fetch-depth: 0 # fetch all branches and tags
@@ -62,7 +62,7 @@ jobs:
           echo "CONDA_ENV_FILE=ci/requirements/environment.yaml" >> $GITHUB_ENV
 
       - name: Setup micromamba
-        uses: mamba-org/setup-micromamba at v2
+        uses: mamba-org/setup-micromamba at v3
         with:
           environment-file: ${{ env.CONDA_ENV_FILE }}
           environment-name: xsar-tests


=====================================
.github/workflows/conda-feedstock-check.yml
=====================================
@@ -14,7 +14,7 @@ jobs:
 # from https://michaelheap.com/dynamic-matrix-generation-github-actions/
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout at v6
+      - uses: actions/checkout at v7
       - id: get-matrix
         run: |
           echo "get matrix for event ${{ github.event_name }}"
@@ -37,10 +37,10 @@ jobs:
         shell: bash -l {0}
     name: python ${{ matrix.python-version }} on ${{ matrix.os }}
     steps:
-      - uses: actions/checkout at v6
+      - uses: actions/checkout at v7
       - name: Strip python version
         run: cat environment.yml | egrep -vw python > environment-nopython.yml
-      - uses: conda-incubator/setup-miniconda at v3
+      - uses: conda-incubator/setup-miniconda at v4
         with:
           auto-update-conda: true
           activate-environment: xsar


=====================================
.github/workflows/publish.yml
=====================================
@@ -11,9 +11,9 @@ jobs:
     if: github.repository == 'umr-lops/xsar'
     steps:
       - name: Checkout
-        uses: actions/checkout at v6
+        uses: actions/checkout at v7
       - name: Set up Python
-        uses: actions/setup-python at v6
+        uses: actions/setup-python at v7
         with:
           python-version: "3.x"
       - name: Install dependencies
@@ -27,7 +27,7 @@ jobs:
         run: |
           twine check dist/*
       - name: Upload build artifacts
-        uses: actions/upload-artifact at v6
+        uses: actions/upload-artifact at v7
         with:
           name: packages
           path: dist/*
@@ -45,10 +45,10 @@ jobs:
 
     steps:
       - name: Download build artifacts
-        uses: actions/download-artifact at v7
+        uses: actions/download-artifact at v8
         with:
           name: packages
           path: dist/
 
       - name: Publish to PyPI
-        uses: pypa/gh-action-pypi-publish at ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
+        uses: pypa/gh-action-pypi-publish at dc37677b2e1c63e2034f94d8a5b11f265b73ba33


=====================================
.github/workflows/upstream-dev.yaml
=====================================
@@ -21,7 +21,7 @@ jobs:
     outputs:
       triggered: ${{ steps.detect-trigger.outputs.trigger-found }}
     steps:
-      - uses: actions/checkout at v6
+      - uses: actions/checkout at v7
         with:
           fetch-depth: 2
       - uses: xarray-contrib/ci-trigger at v1.2
@@ -55,13 +55,13 @@ jobs:
 
     steps:
       - name: checkout the repository
-        uses: actions/checkout at v6
+        uses: actions/checkout at v7
         with:
           # need to fetch all tags to get a correct version
           fetch-depth: 0 # fetch all branches and tags
 
       - name: set up conda environment
-        uses: mamba-org/setup-micromamba at v2
+        uses: mamba-org/setup-micromamba at v3
         with:
           environment-file: ci/requirements/environment.yaml
           environment-name: tests


=====================================
ci/requirements/environment.yaml
=====================================
@@ -19,7 +19,6 @@ dependencies:
   - distributed
   - aiohttp
   - rasterio
-  - cartopy
   - pyproj
   - scipy
   - shapely


=====================================
docs/conf.py
=====================================
@@ -99,7 +99,7 @@ nbsphinx_timeout = 300
 nbsphinx_prolog = """
 Download this notebook from github_.
 
-.. _github: https://github.com/oarcher/xsar/tree/main/docs/{{ env.doc2path(env.docname, base=False) }}
+.. _github: https://github.com/umr-lops/xsar/tree/main/docs/{{ env.doc2path(env.docname, base=False) }}
 
 ----
 """


=====================================
docs/examples/mask.ipynb
=====================================
@@ -6,12 +6,7 @@
    "id": "525f4c15-e090-4c0d-aba0-212138ea6a1a",
    "metadata": {},
    "outputs": [],
-   "source": [
-    "import xarray as xr\n",
-    "import xsar\n",
-    "import numpy as np\n",
-    "import os"
-   ]
+   "source": "import xarray as xr\nimport xsar\nimport numpy as np\nimport os\n\n# The default 'land' mask is read from the 'land_mask' key of ~/.xsar/config.yml.\n# For the docs (and RTD, which has no such config), we set it here to a small OSM clip\n# that only covers the example scenes below (~6 MB instead of the full ~748 MB).\nxsar.BaseMeta.set_mask_feature(\n    'land',\n    os.path.join(xsar.get_test_file('masks/osm/land-osm-doc'), 'land_polygons.shp'),\n)"
   },
   {
    "cell_type": "markdown",
@@ -40,8 +35,7 @@
     "tags": []
    },
    "source": [
-    "[xsar.Sentinel1Dataset.dataset](../basic_api.rst#xsar.Sentinel1Dataset.dataset) has a `land_mask` variable by default, rasterized from [cartopy.feature.NaturalEarthFeature('physical', 'land', '10m')](https://scitools.org.uk/cartopy/docs/latest/matplotlib/feature_interface.html#cartopy.feature.NaturalEarthFeature)\n",
-    "\n"
+    "[xsar.Sentinel1Dataset.dataset](../basic_api.rst#xsar.Sentinel1Dataset.dataset) has a `land_mask` variable by default, rasterized from the shapefile given by the `land_mask` key of `~/.xsar/config.yml` (e.g. OSM land-polygons)\n"
    ]
   },
   {
@@ -76,7 +70,7 @@
    "source": [
     "### Adding masks\n",
     "\n",
-    "Masks can be added with [xsar.BaseMeta.set_mask_feature](../basic_api.rst#xsar.BaseMeta.set_mask_feature), providing a shapefile or a [cartopy.feature.Feature](https://scitools.org.uk/cartopy/docs/latest/matplotlib/feature_interface.html) object.\n",
+    "Masks can be added with [xsar.BaseMeta.set_mask_feature](../basic_api.rst#xsar.BaseMeta.set_mask_feature), providing a shapefile path (or any object exposing a `.geometries()` method).\n",
     "\n",
     "For a default mask for all SAFE, use classmethod `xsar.BaseMeta.set_mask_feature` \n",
     "\n",
@@ -171,8 +165,7 @@
     "tags": []
    },
    "source": [
-    "[xsar.RadarSat2Dataset.dataset](../basic_api.rst#xsar.RadarSat2Dataset.dataset) has a `land_mask` variable by default, rasterized from [cartopy.feature.NaturalEarthFeature('physical', 'land', '10m')](https://scitools.org.uk/cartopy/docs/latest/matplotlib/feature_interface.html#cartopy.feature.NaturalEarthFeature)\n",
-    "\n"
+    "[xsar.RadarSat2Dataset.dataset](../basic_api.rst#xsar.RadarSat2Dataset.dataset) has a `land_mask` variable by default, rasterized from the shapefile given by the `land_mask` key of `~/.xsar/config.yml` (e.g. OSM land-polygons)\n"
    ]
   },
   {
@@ -227,8 +220,7 @@
     "tags": []
    },
    "source": [
-    "[xsar.RcmDataset.dataset](../basic_api.rst#xsar.RcmDataset.dataset) has a `land_mask` variable by default, rasterized from [cartopy.feature.NaturalEarthFeature('physical', 'land', '10m')](https://scitools.org.uk/cartopy/docs/latest/matplotlib/feature_interface.html#cartopy.feature.NaturalEarthFeature)\n",
-    "\n"
+    "[xsar.RcmDataset.dataset](../basic_api.rst#xsar.RcmDataset.dataset) has a `land_mask` variable by default, rasterized from the shapefile given by the `land_mask` key of `~/.xsar/config.yml` (e.g. OSM land-polygons)\n"
    ]
   },
   {
@@ -276,13 +268,29 @@
    "source": [
     "ds['sigma0_land'].sel(pol='HV').plot(vmin=0)"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "478442d3",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3da62185",
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "env_xsar",
+   "display_name": "env_xsar (Slurm)",
    "language": "python",
-   "name": "python3"
+   "name": "env_xsar"
   },
   "language_info": {
    "codemirror_mode": {
@@ -294,9 +302,9 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.10.15"
+   "version": "3.12.12"
   }
  },
  "nbformat": 4,
  "nbformat_minor": 5
-}
+}
\ No newline at end of file


=====================================
docs/examples/xsar_batch_datarmor.ipynb
=====================================
@@ -51,12 +51,6 @@
     "pip install git+https://github.com/umr-lops/xsarsea.git\n",
     "```\n",
     "\n",
-    "This is needed for coastlines, because datarmor nodes don't have internet access\n",
-    "\n",
-    "```\n",
-    "cartopy_feature_download.py --output `python -c 'import cartopy ; print(cartopy.config[\"data_dir\"])'` physical\n",
-    "```\n",
-    "\n",
     "This is needed for https://datarmor-jupyterhub.ifremer.fr/\n",
     "\n",
     "```\n",


=====================================
environment.yml
=====================================
@@ -3,7 +3,6 @@ channels:
   - conda-forge
 dependencies:
   - aiohttp
-  - cartopy >=0.19.0
   - dask >=2021.6.2
   - gdal >=3.3
   - geopandas


=====================================
pyproject.toml
=====================================
@@ -37,7 +37,6 @@ dependencies = [
   "xarray>=2024.10.0",
   'affine',
   'rasterio',
-  'cartopy',
   'fiona',
   'pyproj',
   'numpy',


=====================================
src/xsar/base_dataset.py
=====================================
@@ -553,17 +553,22 @@ class BaseDataset(ABC):
             # transform * (x, y) -> (line, sample)
             # (where (x, y) are index in out_shape)
             # Affine.permutation() is used because (line, sample) is transposed from geographic
+            # coords label the pixel centers, while rasterio expects the transform
+            # to map pixel indices to the grid corner -> shift by half a step
+            dline = (line[-1] - line[0]) / (len(line) - 1)
+            dsample = (sample[-1] - sample[0]) / (len(sample) - 1)
 
             transform = (
-                Affine.translation(*chunk_coords[0])
-                * Affine.scale(*[np.unique(np.diff(c))[0] for c in [line, sample]])
+                Affine.translation(line[0] - dline / 2,
+                                   sample[0] - dsample / 2)
+                * Affine.scale(dline, dsample)
                 * Affine.permutation()
             )
 
             raster_mask = rasterio.features.rasterize(
                 [vector_mask_coords],
                 out_shape=out_shape,
-                all_touched=False,
+                all_touched=True,
                 transform=transform,
             )
             return raster_mask
@@ -732,9 +737,15 @@ class BaseDataset(ABC):
             # (where (x, y) are index in out_shape)
             # Affine.permutation() is used because (line, sample) is transposed from geographic
             # this transform Affine seems to be sufficient approx for SLC -> curvilinear could be even better?
+            # coords label the pixel centers, while rasterio expects the transform
+            # to map pixel indices to the grid corner -> shift by half a step
+            dline = (line[-1] - line[0]) / (len(line) - 1)
+            dsample = (sample[-1] - sample[0]) / (len(sample) - 1)
+
             transform = (
-                Affine.translation(*chunk_coords[0])
-                * Affine.scale(*[np.unique(np.diff(c))[0] for c in [line, sample]])
+                Affine.translation(line[0] - dline / 2,
+                                   sample[0] - dsample / 2)
+                * Affine.scale(dline, dsample)
                 * Affine.permutation()
             )
 


=====================================
src/xsar/base_meta.py
=====================================
@@ -2,7 +2,6 @@ import copy
 import logging
 import warnings
 
-import cartopy
 import rasterio
 import shapely
 from shapely.geometry import Polygon
@@ -16,7 +15,7 @@ from .raster_readers import available_rasters
 from .base_dataset import BaseDataset
 import geopandas as gpd
 
-from .utils import class_or_instancemethod, to_lon180, haversine
+from .utils import class_or_instancemethod, to_lon180, haversine, config
 
 logger = logging.getLogger("xsar.base_meta")
 logger.addHandler(logging.NullHandler())
@@ -30,6 +29,20 @@ warnings.filterwarnings(
 np.errstate(invalid="ignore")
 
 
+# Default 'land' mask: shapefile path from the 'land_mask' key of the config.
+_DEFAULT_LAND = config.get("land_mask")
+
+
+class _ShapefileFeature:
+    """Minimal feature exposing ``.geometries()``, built from shapefile geometries (OSM, GSHHG...)."""
+
+    def __init__(self, geometries):
+        self._geometries = list(geometries)
+
+    def geometries(self):
+        return iter(self._geometries)
+
+
 class BaseMeta(BaseDataset):
     """
     Abstract class that defines necessary common functions for the computation of different SAR metadata
@@ -39,7 +52,7 @@ class BaseMeta(BaseDataset):
 
     # default mask feature (see self.set_mask_feature and cls.set_mask_feature)
     _mask_features_raw = {
-        "land": cartopy.feature.NaturalEarthFeature("physical", "land", "10m")
+        "land": _DEFAULT_LAND
     }
 
     _mask_features = {}
@@ -64,11 +77,16 @@ class BaseMeta(BaseDataset):
         self.rasters = available_rasters.iloc[0:0].copy()
 
     def _get_mask_feature(self, name):
-        # internal method that returns a cartopy feature from a mask name
+        # internal method that returns a feature (with a .geometries() method) from a mask name
         if self._mask_features[name] is None:
             feature = self._mask_features_raw[name]
+            if feature is None:
+                raise ValueError(
+                    f"No feature set for mask '{name}'. Set 'land_mask' in "
+                    f"~/.xsar/config.yml, or call set_mask_feature('{name}', '/path/to.shp')."
+                )
             if isinstance(feature, str):
-                # feature is a shapefile.
+                # feature is a shapefile (e.g. OSM land-polygons, GSHHG).
                 # we get the crs from the shapefile to be able to transform the footprint to this crs_in
                 # (so we can use `mask=` in gpd.read_file)
                 import fiona
@@ -91,14 +109,16 @@ class BaseMeta(BaseDataset):
                 with warnings.catch_warnings():
                     # ignore "RuntimeWarning: Sequential read of iterator was interrupted. Resetting iterator."
                     warnings.simplefilter("ignore", RuntimeWarning)
-                    feature = cartopy.feature.ShapelyFeature(
+                    # wrap the shapefile geometries in a minimal feature exposing .geometries()
+                    feature = _ShapefileFeature(
                         gpd.read_file(feature, mask=footprint_crs)
                         .to_crs(epsg=4326)
-                        .geometry,
-                        cartopy.crs.PlateCarree(),
+                        .geometry
                     )
-            if not isinstance(feature, cartopy.feature.Feature):
-                raise TypeError("Expected a cartopy.feature.Feature type")
+            if not hasattr(feature, "geometries"):
+                raise TypeError(
+                    "Expected a feature with a .geometries() method "
+                    "(a shapefile path, or any feature exposing .geometries())")
             self._mask_features[name] = feature
 
         return self._mask_features[name]
@@ -106,33 +126,34 @@ class BaseMeta(BaseDataset):
     @class_or_instancemethod
     def set_mask_feature(self_or_cls, name, feature):
         """
-        Set a named mask from a shapefile or a cartopy feature.
+        Set a named mask from a shapefile path.
 
         Parameters
         ----------
         name: str
             mask name
-        feature: str or cartopy.feature.Feature
-            if str, feature is a path to a shapefile or whatever file readable with fiona.
-            It is recommended to use str, as the serialization of cartopy feature might be big.
+        feature: str
+            path to a shapefile (or any file readable with fiona).
+            Any object exposing a ``.geometries()`` method is also accepted.
 
         Examples
         --------
-            Add an 'ocean' mask at class level (ie as default mask):
+            Override the default 'land' mask with your own shapefile, at class level
+            (ie as default mask for every new meta):
             ```
-            >>> xsar.RadarSat2Meta.set_mask_feature("ocean", cartopy.feature.OCEAN)
-            >>> xsar.Sentinel1Meta.set_mask_feature("ocean", cartopy.feature.OCEAN)
+            >>> xsar.RadarSat2Meta.set_mask_feature("land", "/path/to/land_polygons.shp")
+            >>> xsar.Sentinel1Meta.set_mask_feature("land", "/path/to/land_polygons.shp")
             ```
 
-            Add an 'ocean' mask at instance level (ie only for this self Sentinel1Meta (or RadarSat2Meta instance):
+            Add an extra named mask at instance level (only for this meta instance):
             ```
-            >>> xsar.RadarSat2Meta.set_mask_feature("ocean", cartopy.feature.OCEAN)
-            >>> xsar.Sentinel1Meta.set_mask_feature("ocean", cartopy.feature.OCEAN)
+            >>> meta.set_mask_feature("gshhg", "/path/to/GSHHS_h_L1.shp")
             ```
 
 
-            High resoltion shapefiles can be found from openstreetmap.
+            High resolution land shapefiles can be found from openstreetmap.
             It is recommended to use WGS84 with large polygons split from https://osmdata.openstreetmap.de/
+            The default 'land' mask is read from the 'land_mask' key of ~/.xsar/config.yml.
 
         See Also
         --------
@@ -166,24 +187,24 @@ class BaseMeta(BaseDataset):
         if describe:
             descr = self._mask_features_raw.get(name)
 
-            # 1) if descr is a str ( shapefile file path in most of the case)
+            # 1) if descr is a str (shapefile path, e.g. OSM land-polygons / GSHHG):
+            #    describe the mask by its source path -> ends up in the mask 'history' attr
+            if isinstance(descr, str):
+                return descr
 
             # 2) if descr is None
             if descr is None:
-                descr = f"Unknown mask feature: {name}"
+                return f"Unknown mask feature: {name}"
 
-            # 3) otherwise
+            # 3) otherwise a feature object: nice repr like 'module.Class name'
             module = getattr(descr, "__module__", "unknown_module")
             cls = descr.__class__.__name__
             feat_name = getattr(descr, "name",  f"{module}.{cls}")
-            # nice repr for a class (like 'cartopy.feature.NaturalEarthFeature land')
-            descr = "%s.%s %s" % (
+            return "%s.%s %s" % (
                     module,
                     cls,
                     feat_name,
             )
-
-            return descr
         if self._mask_geometry[name] is None:
             intersecting_geoms = self._get_mask_intersecting_geometries(name)
 


=====================================
src/xsar/config.yml
=====================================
@@ -2,3 +2,6 @@
 data_dir: /tmp
 auxiliary_dir:
 path_auxiliary_df:
+# default 'land' mask: path to a shapefile (e.g. OSM land-polygons).
+# if empty, no default land mask, ValueError
+land_mask:


=====================================
src/xsar/ipython_backends.py
=====================================
@@ -4,7 +4,6 @@ try:
     # make sure we are running from a notebook
     # if test fail, nothing will be imported, and that will save lot of importtime
     assert get_ipython() is not None
-    import cartopy
     import holoviews as hv
     import geoviews as gv
     import geoviews.feature as gf
@@ -51,10 +50,8 @@ def repr_mimebundle_Sentinel1Meta(self, include=None, exclude=None):
         """
     )
 
-    crs = cartopy.crs.PlateCarree()
-
     world = gv.operation.resample_geometry(gf.land.geoms("10m")).opts(
-        color="khaki", projection=crs, alpha=0.5
+        color="khaki", alpha=0.5
     )
 
     center = self.footprint.centroid
@@ -78,7 +75,7 @@ def repr_mimebundle_Sentinel1Meta(self, include=None, exclude=None):
 
     footprint = (
         gv.Polygons(footprints_df, label="footprint")
-        .opts(projection=crs, xlim=xlim, ylim=ylim, alpha=0.5)
+        .opts(xlim=xlim, ylim=ylim, alpha=0.5)
         .opts(
             **(opts.get(hv.Store.current_backend) or {}),
             backend=hv.Store.current_backend
@@ -87,7 +84,7 @@ def repr_mimebundle_Sentinel1Meta(self, include=None, exclude=None):
 
     orbit = (
         gv.Points(self.orbit["geometry"].to_crs("EPSG:4326"), label="orbit")
-        .opts(projection=crs, xlim=xlim, ylim=ylim, alpha=0.5)
+        .opts(xlim=xlim, ylim=ylim, alpha=0.5)
         .opts(
             **(opts.get(hv.Store.current_backend) or {}),
             backend=hv.Store.current_backend


=====================================
test/test_invalid_mask_geometries.py
=====================================
@@ -10,9 +10,7 @@ import pytest
 import geopandas as gpd
 from shapely.geometry import Polygon, MultiPolygon, box
 from shapely.validation import explain_validity
-import cartopy
-
-from xsar.base_meta import BaseMeta
+from xsar.base_meta import BaseMeta, _ShapefileFeature
 
 
 class DummyMeta(BaseMeta):
@@ -131,12 +129,9 @@ def test_get_mask_handles_self_intersecting_geometry(sentinel1_footprint):
     # Get the problematic polygon
     invalid_poly = get_problematic_gshhs_polygon()
 
-    # Create a mock cartopy feature with the invalid geometry
+    # Create a mock shapefile feature with the invalid geometry
     geoseries = gpd.GeoSeries([invalid_poly])
-    mock_feature = cartopy.feature.ShapelyFeature(
-        geoseries,
-        cartopy.crs.PlateCarree()
-    )
+    mock_feature = _ShapefileFeature(geoseries)
 
     # Set the mask feature
     meta.set_mask_feature("gshhs_test", mock_feature)
@@ -171,10 +166,7 @@ def test_get_mask_with_multiple_invalid_coastal_geometries(sentinel1_footprint):
 
     # Create a mock feature with mixed geometries
     geoseries = gpd.GeoSeries([valid_poly1, invalid_poly, valid_poly2])
-    mock_feature = cartopy.feature.ShapelyFeature(
-        geoseries,
-        cartopy.crs.PlateCarree()
-    )
+    mock_feature = _ShapefileFeature(geoseries)
 
     # Set the mask feature
     meta.set_mask_feature("gshhs_multiple", mock_feature)
@@ -223,10 +215,7 @@ def test_get_mask_caches_fixed_geometry(sentinel1_footprint):
 
     invalid_poly = get_problematic_gshhs_polygon()
     geoseries = gpd.GeoSeries([invalid_poly])
-    mock_feature = cartopy.feature.ShapelyFeature(
-        geoseries,
-        cartopy.crs.PlateCarree()
-    )
+    mock_feature = _ShapefileFeature(geoseries)
 
     meta.set_mask_feature("gshhs_cache", mock_feature)
 



View it on GitLab: https://salsa.debian.org/debian-gis-team/xsar/-/commit/11c2532ccf883d272f8c312992c818f3a033fb86

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/xsar/-/commit/11c2532ccf883d272f8c312992c818f3a033fb86
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/20260905/8f235254/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list