Repository: bopen/xarray-sentinel (language: python)

Issue:
Add tests for *fsspec* access
Add at least:
- [x] local file zip
- [x] remore files on github
- [x] check all metadata and data

Data access DEPENDS on rasterio >= 1.3a3.


Discussion / hints:

Fix patch:
diff --git a/environment-dev.yml b/environment-dev.yml
index 6fe0353..31e6434 100644
--- a/environment-dev.yml
+++ b/environment-dev.yml
@@ -1,6 +1,7 @@
 channels:
   - conda-forge
 dependencies:
+  - adlfs
   - build
   - gcsfs
   - ipytree
@@ -8,3 +9,5 @@ dependencies:
   - mamba
   - notebook
   - sentinelsat
+  - pip:
+    - --no-binary rasterio rasterio>=1.3a3  # for fsspec support
diff --git a/notebooks/Sentinel-1_GRD_IW.ipynb b/notebooks/Sentinel-1_GRD_IW.ipynb
index 0d7ecc1..5b41335 100644
--- a/notebooks/Sentinel-1_GRD_IW.ipynb
+++ b/notebooks/Sentinel-1_GRD_IW.ipynb
@@ -14,8 +14,9 @@
     "%config InlineBackend.figure_format = 'retina'\n",
     "\n",
     "import matplotlib.pyplot as plt\n",
-    "plt.rcParams['figure.figsize'] = (12, 8)\n",
-    "plt.rcParams['font.size'] = 12"
+    "\n",
+    "plt.rcParams[\"figure.figsize\"] = (12, 8)\n",
+    "plt.rcParams[\"font.size\"] = 12"
    ]
   },
   {
@@ -48,7 +49,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "product_path = \"data/S1B_IW_GRDH_1SDV_20211223T051122_20211223T051147_030148_039993_5371.SAFE\"\n",
+    "product_path = (\n",
+    "    \"data/S1B_IW_GRDH_1SDV_20211223T051122_20211223T051147_030148_039993_5371.SAFE\"\n",
+    ")\n",
     "swath_group = \"IW\"\n",
     "swath_polarisation_group = \"IW/VV\"\n",
     "measurement_group = \"IW/VV\"\n",
@@ -95,7 +98,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "measurement = xr.open_dataset(product_path, engine=\"sentinel-1\", group=measurement_group, chunks=2048)\n",
+    "measurement = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=measurement_group, chunks=2048\n",
+    ")\n",
     "measurement"
    ]
   },
@@ -129,7 +134,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "gcp = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/gcp\")\n",
+    "gcp = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/gcp\"\n",
+    ")\n",
     "gcp"
    ]
   },
@@ -160,7 +167,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "orbit = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/orbit\")\n",
+    "orbit = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/orbit\"\n",
+    ")\n",
     "orbit"
    ]
   },
@@ -181,7 +190,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "calibration = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/calibration\")\n",
+    "calibration = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/calibration\"\n",
+    ")\n",
     "calibration"
    ]
   },
@@ -224,7 +235,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "betaNought_block = xarray_sentinel.calibrate_amplitude(measurement_block, calibration.betaNought)\n",
+    "betaNought_block = xarray_sentinel.calibrate_amplitude(\n",
+    "    measurement_block, calibration.betaNought\n",
+    ")\n",
     "betaNought_block"
    ]
   },
@@ -245,7 +258,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "betaNought_block_db = xarray_sentinel.calibrate_intensity(measurement_block, calibration.betaNought)\n",
+    "betaNought_block_db = xarray_sentinel.calibrate_intensity(\n",
+    "    measurement_block, calibration.betaNought\n",
+    ")\n",
     "betaNought_block_db"
    ]
   },
@@ -266,7 +281,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "noise_range = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_range\")\n",
+    "noise_range = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_range\"\n",
+    ")\n",
     "noise_range"
    ]
   },
@@ -287,7 +304,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "noise_azimuth = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_azimuth\")\n",
+    "noise_azimuth = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_azimuth\"\n",
+    ")\n",
     "noise_azimuth"
    ]
   },
@@ -308,7 +327,11 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "coordinate_conversion = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/coordinate_conversion\")\n",
+    "coordinate_conversion = xr.open_dataset(\n",
+    "    product_path,\n",
+    "    engine=\"sentinel-1\",\n",
+    "    group=f\"{swath_polarisation_group}/coordinate_conversion\",\n",
+    ")\n",
     "coordinate_conversion"
    ]
   },
@@ -319,7 +342,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "measurement_block_sr = xarray_sentinel.convert_ground_to_slant_range(measurement_block, coordinate_conversion)"
+    "measurement_block_sr = xarray_sentinel.convert_ground_to_slant_range(\n",
+    "    measurement_block, coordinate_conversion\n",
+    ")"
    ]
   },
   {
@@ -339,7 +364,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/attitude\")"
+    "xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/attitude\"\n",
+    ")"
    ]
   },
   {
@@ -349,7 +376,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/dc_estimate\")"
+    "xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/dc_estimate\"\n",
+    ")"
    ]
   },
   {
@@ -359,7 +388,11 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/azimuth_fm_rate\")"
+    "xr.open_dataset(\n",
+    "    product_path,\n",
+    "    engine=\"sentinel-1\",\n",
+    "    group=f\"{swath_polarisation_group}/azimuth_fm_rate\",\n",
+    ")"
    ]
   },
   {
diff --git a/notebooks/Sentinel-1_GRD_SM.ipynb b/notebooks/Sentinel-1_GRD_SM.ipynb
index f26c45f..5d70bcf 100644
--- a/notebooks/Sentinel-1_GRD_SM.ipynb
+++ b/notebooks/Sentinel-1_GRD_SM.ipynb
@@ -14,8 +14,9 @@
     "%config InlineBackend.figure_format = 'retina'\n",
     "\n",
     "import matplotlib.pyplot as plt\n",
-    "plt.rcParams['figure.figsize'] = (12, 8)\n",
-    "plt.rcParams['font.size'] = 12"
+    "\n",
+    "plt.rcParams[\"figure.figsize\"] = (12, 8)\n",
+    "plt.rcParams[\"font.size\"] = 12"
    ]
   },
   {
@@ -48,7 +49,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "product_path = \"data/S1B_S6_GRDH_1SDV_20211216T115438_20211216T115501_030050_03968A_0F8A.SAFE\"\n",
+    "product_path = (\n",
+    "    \"data/S1B_S6_GRDH_1SDV_20211216T115438_20211216T115501_030050_03968A_0F8A.SAFE\"\n",
+    ")\n",
     "swath_group = \"S6\"\n",
     "swath_polarisation_group = \"S6/VV\"\n",
     "measurement_group = \"S6/VV\"\n",
@@ -95,7 +98,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "measurement = xr.open_dataset(product_path, engine=\"sentinel-1\", group=measurement_group, chunks=2048)\n",
+    "measurement = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=measurement_group, chunks=2048\n",
+    ")\n",
     "measurement"
    ]
   },
@@ -129,7 +134,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "gcp = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/gcp\")\n",
+    "gcp = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/gcp\"\n",
+    ")\n",
     "gcp"
    ]
   },
@@ -160,7 +167,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "orbit = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/orbit\")\n",
+    "orbit = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/orbit\"\n",
+    ")\n",
     "orbit"
    ]
   },
@@ -181,7 +190,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "calibration = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/calibration\")\n",
+    "calibration = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/calibration\"\n",
+    ")\n",
     "calibration"
    ]
   },
@@ -224,7 +235,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "betaNought_block = xarray_sentinel.calibrate_amplitude(measurement_block, calibration.betaNought)\n",
+    "betaNought_block = xarray_sentinel.calibrate_amplitude(\n",
+    "    measurement_block, calibration.betaNought\n",
+    ")\n",
     "betaNought_block"
    ]
   },
@@ -245,7 +258,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "betaNought_block_db = xarray_sentinel.calibrate_intensity(measurement_block, calibration.betaNought)\n",
+    "betaNought_block_db = xarray_sentinel.calibrate_intensity(\n",
+    "    measurement_block, calibration.betaNought\n",
+    ")\n",
     "betaNought_block_db"
    ]
   },
@@ -266,7 +281,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "noise_range = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_range\")\n",
+    "noise_range = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_range\"\n",
+    ")\n",
     "noise_range"
    ]
   },
@@ -287,7 +304,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "noise_azimuth = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_azimuth\")\n",
+    "noise_azimuth = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_azimuth\"\n",
+    ")\n",
     "noise_azimuth"
    ]
   },
@@ -308,7 +327,11 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "coordinate_conversion = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/coordinate_conversion\")\n",
+    "coordinate_conversion = xr.open_dataset(\n",
+    "    product_path,\n",
+    "    engine=\"sentinel-1\",\n",
+    "    group=f\"{swath_polarisation_group}/coordinate_conversion\",\n",
+    ")\n",
     "coordinate_conversion"
    ]
   },
@@ -319,7 +342,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "measurement_block_sr = xarray_sentinel.convert_ground_to_slant_range(measurement_block, coordinate_conversion)"
+    "measurement_block_sr = xarray_sentinel.convert_ground_to_slant_range(\n",
+    "    measurement_block, coordinate_conversion\n",
+    ")"
    ]
   },
   {
@@ -339,7 +364,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/attitude\")"
+    "xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/attitude\"\n",
+    ")"
    ]
   },
   {
@@ -349,7 +376,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/dc_estimate\")"
+    "xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/dc_estimate\"\n",
+    ")"
    ]
   },
   {
@@ -359,7 +388,11 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/azimuth_fm_rate\")"
+    "xr.open_dataset(\n",
+    "    product_path,\n",
+    "    engine=\"sentinel-1\",\n",
+    "    group=f\"{swath_polarisation_group}/azimuth_fm_rate\",\n",
+    ")"
    ]
   },
   {
diff --git a/notebooks/Sentinel-1_SLC_IW.ipynb b/notebooks/Sentinel-1_SLC_IW.ipynb
index 41299d7..f51333b 100644
--- a/notebooks/Sentinel-1_SLC_IW.ipynb
+++ b/notebooks/Sentinel-1_SLC_IW.ipynb
@@ -14,8 +14,9 @@
     "%config InlineBackend.figure_format = 'retina'\n",
     "\n",
     "import matplotlib.pyplot as plt\n",
-    "plt.rcParams['figure.figsize'] = (12, 8)\n",
-    "plt.rcParams['font.size'] = 12"
+    "\n",
+    "plt.rcParams[\"figure.figsize\"] = (12, 8)\n",
+    "plt.rcParams[\"font.size\"] = 12"
    ]
   },
   {
@@ -48,7 +49,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "product_path = \"data/S1B_IW_SLC__1SDV_20211223T051121_20211223T051148_030148_039993_BA4B.SAFE\"\n",
+    "product_path = (\n",
+    "    \"data/S1B_IW_SLC__1SDV_20211223T051121_20211223T051148_030148_039993_BA4B.SAFE\"\n",
+    ")\n",
     "swath_group = \"IW3\"\n",
     "swath_polarisation_group = \"IW3/VV\"\n",
     "measurement_group = \"IW3/VV/4\"\n",
@@ -95,7 +98,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "measurement = xr.open_dataset(product_path, engine=\"sentinel-1\", group=measurement_group)\n",
+    "measurement = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=measurement_group\n",
+    ")\n",
     "measurement"
    ]
   },
@@ -129,7 +134,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "gcp = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/gcp\")\n",
+    "gcp = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/gcp\"\n",
+    ")\n",
     "gcp"
    ]
   },
@@ -160,7 +167,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "orbit = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/orbit\")\n",
+    "orbit = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/orbit\"\n",
+    ")\n",
     "orbit"
    ]
   },
@@ -181,7 +190,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "calibration = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/calibration\")\n",
+    "calibration = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/calibration\"\n",
+    ")\n",
     "calibration"
    ]
   },
@@ -224,7 +235,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "betaNought_block = xarray_sentinel.calibrate_amplitude(measurement_block, calibration.betaNought)\n",
+    "betaNought_block = xarray_sentinel.calibrate_amplitude(\n",
+    "    measurement_block, calibration.betaNought\n",
+    ")\n",
     "betaNought_block"
    ]
   },
@@ -245,7 +258,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "betaNought_block_db = xarray_sentinel.calibrate_intensity(measurement_block, calibration.betaNought)\n",
+    "betaNought_block_db = xarray_sentinel.calibrate_intensity(\n",
+    "    measurement_block, calibration.betaNought\n",
+    ")\n",
     "betaNought_block_db"
    ]
   },
@@ -266,7 +281,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "noise_range = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_range\")\n",
+    "noise_range = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_range\"\n",
+    ")\n",
     "noise_range"
    ]
   },
@@ -287,7 +304,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "noise_azimuth = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_azimuth\")\n",
+    "noise_azimuth = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_azimuth\"\n",
+    ")\n",
     "noise_azimuth"
    ]
   },
@@ -308,7 +327,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/attitude\")"
+    "xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/attitude\"\n",
+    ")"
    ]
   },
   {
@@ -318,7 +339,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/dc_estimate\")"
+    "xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/dc_estimate\"\n",
+    ")"
    ]
   },
   {
@@ -328,7 +351,11 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/azimuth_fm_rate\")"
+    "xr.open_dataset(\n",
+    "    product_path,\n",
+    "    engine=\"sentinel-1\",\n",
+    "    group=f\"{swath_polarisation_group}/azimuth_fm_rate\",\n",
+    ")"
    ]
   },
   {
diff --git a/notebooks/Sentinel-1_SLC_SM.ipynb b/notebooks/Sentinel-1_SLC_SM.ipynb
index 33a9fb8..d6f6af0 100644
--- a/notebooks/Sentinel-1_SLC_SM.ipynb
+++ b/notebooks/Sentinel-1_SLC_SM.ipynb
@@ -14,8 +14,9 @@
     "%config InlineBackend.figure_format = 'retina'\n",
     "\n",
     "import matplotlib.pyplot as plt\n",
-    "plt.rcParams['figure.figsize'] = (12, 8)\n",
-    "plt.rcParams['font.size'] = 12"
+    "\n",
+    "plt.rcParams[\"figure.figsize\"] = (12, 8)\n",
+    "plt.rcParams[\"font.size\"] = 12"
    ]
   },
   {
@@ -48,7 +49,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "product_path = \"data/S1B_S6_SLC__1SDV_20211216T115438_20211216T115501_030050_03968A_4DCB.SAFE\"\n",
+    "product_path = (\n",
+    "    \"data/S1B_S6_SLC__1SDV_20211216T115438_20211216T115501_030050_03968A_4DCB.SAFE\"\n",
+    ")\n",
     "swath_group = \"S6\"\n",
     "swath_polarisation_group = \"S6/VV\"\n",
     "measurement_group = \"S6/VV\"\n",
@@ -95,7 +98,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "measurement = xr.open_dataset(product_path, engine=\"sentinel-1\", group=measurement_group, chunks=2048)\n",
+    "measurement = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=measurement_group, chunks=2048\n",
+    ")\n",
     "measurement"
    ]
   },
@@ -129,7 +134,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "gcp = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/gcp\")\n",
+    "gcp = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/gcp\"\n",
+    ")\n",
     "gcp"
    ]
   },
@@ -160,7 +167,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "orbit = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/orbit\")\n",
+    "orbit = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/orbit\"\n",
+    ")\n",
     "orbit"
    ]
   },
@@ -181,7 +190,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "calibration = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/calibration\")\n",
+    "calibration = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/calibration\"\n",
+    ")\n",
     "calibration"
    ]
   },
@@ -224,7 +235,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "betaNought_block = xarray_sentinel.calibrate_amplitude(measurement_block, calibration.betaNought)\n",
+    "betaNought_block = xarray_sentinel.calibrate_amplitude(\n",
+    "    measurement_block, calibration.betaNought\n",
+    ")\n",
     "betaNought_block"
    ]
   },
@@ -245,7 +258,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "betaNought_block_db = xarray_sentinel.calibrate_intensity(measurement_block, calibration.betaNought)\n",
+    "betaNought_block_db = xarray_sentinel.calibrate_intensity(\n",
+    "    measurement_block, calibration.betaNought\n",
+    ")\n",
     "betaNought_block_db"
    ]
   },
@@ -266,7 +281,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "noise_range = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_range\")\n",
+    "noise_range = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_range\"\n",
+    ")\n",
     "noise_range"
    ]
   },
@@ -287,7 +304,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "noise_azimuth = xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_azimuth\")\n",
+    "noise_azimuth = xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/noise_azimuth\"\n",
+    ")\n",
     "noise_azimuth"
    ]
   },
@@ -308,7 +327,11 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/coordinate_conversion\")"
+    "xr.open_dataset(\n",
+    "    product_path,\n",
+    "    engine=\"sentinel-1\",\n",
+    "    group=f\"{swath_polarisation_group}/coordinate_conversion\",\n",
+    ")"
    ]
   },
   {
@@ -318,7 +341,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/attitude\")"
+    "xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/attitude\"\n",
+    ")"
    ]
   },
   {
@@ -328,7 +353,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/dc_estimate\")"
+    "xr.open_dataset(\n",
+    "    product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/dc_estimate\"\n",
+    ")"
    ]
   },
   {
@@ -338,7 +365,11 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "xr.open_dataset(product_path, engine=\"sentinel-1\", group=f\"{swath_polarisation_group}/azimuth_fm_rate\")"
+    "xr.open_dataset(\n",
+    "    product_path,\n",
+    "    engine=\"sentinel-1\",\n",
+    "    group=f\"{swath_polarisation_group}/azimuth_fm_rate\",\n",
+    ")"
    ]
   },
   {
diff --git a/notebooks/download_data.sh b/notebooks/download_data.sh
index 1823265..929feea 100755
--- a/notebooks/download_data.sh
+++ b/notebooks/download_data.sh
@@ -11,6 +11,11 @@ sentinelsat --path data -d --include-pattern "*vv*" \
 sentinelsat --path data -d --include-pattern "*vv*" \
   --name S1B_S6_GRDH_1SDV_20211216T115438_20211216T115501_030050_03968A_0F8A
 
+## GRD zipped to test fsspec
+sentinelsat --path data -d --include-pattern \
+  --name S1B_S6_GRDH_1SDV_20211216T115438_20211216T115501_030050_03968A_0F8A
+
+
 # IW
 
 ## SLC
diff --git a/notebooks/fsspec-with-credentials.ipynb b/notebooks/fsspec-with-credentials.ipynb
new file mode 100644
index 0000000..cab4747
--- /dev/null
+++ b/notebooks/fsspec-with-credentials.ipynb
@@ -0,0 +1,130 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f648a743",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%load_ext autoreload\n",
+    "%autoreload 2\n",
+    "\n",
+    "%matplotlib inline\n",
+    "%config InlineBackend.figure_format = 'retina'\n",
+    "\n",
+    "import matplotlib.pyplot as plt\n",
+    "\n",
+    "plt.rcParams[\"figure.figsize\"] = (12, 8)\n",
+    "plt.rcParams[\"font.size\"] = 12"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "76a45b2f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "\n",
+    "import adlfs\n",
+    "import xarray as xr\n",
+    "\n",
+    "import xarray_sentinel"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a3691348",
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "token = \"st=2022-02-04T16%3A18%3A37Z&se=2022-02-12T16%3A18%3A37Z&sp=rl&sv=2020-06-12&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2022-02-05T15%3A18%3A37Z&ske=2022-02-06T16%3A18%3A37Z&sks=b&skv=2020-06-12&sig=fNSLqXMPQy4fufnVMCdFO3LYcj0cM8RHE0vjImiQiqE%3D\"\n",
+    "product_folder = \"s1-grd/GRD/2022/1/10/IW/DV/S1A_IW_GRDH_1SDV_20220110T050922_20220110T050947_041394_04EBF7_A360\"\n",
+    "fs = adlfs.AzureBlobFileSystem(\"sentinel1euwest\", credential=token)\n",
+    "fs.ls(product_folder)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6c2a8a84",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "storage_options = {\"account_name\": \"sentinel1euwest\", \"sas_token\": token}\n",
+    "product_path = f\"abfs://{product_folder}/manifest.safe\"\n",
+    "\n",
+    "ds = xr.open_dataset(\n",
+    "    product_path,\n",
+    "    engine=\"sentinel-1\",\n",
+    "    storage_options=storage_options,\n",
+    ")\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2eaab998",
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "ds = xr.open_dataset(\n",
+    "    product_path,\n",
+    "    engine=\"sentinel-1\",\n",
+    "    storage_options=storage_options,\n",
+    "    group=\"IW/VV\",\n",
+    "    chunks=2048,\n",
+    "    override_product_files=\"{dirname}/{swath}-{polarization}{ext}\",\n",
+    ")\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9ca7ace6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ds.measurement[:2048, :2048].plot(vmax=300)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2d5ca01a",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.9.9"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/fsspec.ipynb b/notebooks/fsspec.ipynb
new file mode 100644
index 0000000..1802545
--- /dev/null
+++ b/notebooks/fsspec.ipynb
@@ -0,0 +1,216 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f648a743",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%load_ext autoreload\n",
+    "%autoreload 2\n",
+    "\n",
+    "%matplotlib inline\n",
+    "%config InlineBackend.figure_format = 'retina'\n",
+    "\n",
+    "import matplotlib.pyplot as plt\n",
+    "\n",
+    "plt.rcParams[\"figure.figsize\"] = (12, 8)\n",
+    "plt.rcParams[\"font.size\"] = 12"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "76a45b2f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import xarray as xr\n",
+    "\n",
+    "import xarray_sentinel"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ec537b7f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "product_path = \"zip://*/manifest.safe::data/S1B_S6_GRDH_1SDV_20211216T115438_20211216T115501_030050_03968A_0F8A.zip\"\n",
+    "ds = xarray_sentinel.open_sentinel1_dataset(product_path)\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2858e010",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ds = xarray_sentinel.open_sentinel1_dataset(product_path, group=\"S6/VH/orbit\")\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c1d5ec7a",
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "ds = xarray_sentinel.open_sentinel1_dataset(product_path, group=\"S6/VH\")\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c3bc593b",
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "_ = ds.measurement[2000:4000, 2000:4000].plot(vmax=100)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b72b077d",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d260c490",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "product_path = \"github://bopen:xarray-sentinel@/tests/data/S1B_IW_SLC__1SDV_20210401T052622_20210401T052650_026269_032297_EFA4.SAFE\"\n",
+    "ds = xarray_sentinel.open_sentinel1_dataset(product_path)\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5a316651",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ds = xarray_sentinel.open_sentinel1_dataset(product_path, group=\"IW1/VH/orbit\")\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "07302996",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ds = xarray_sentinel.open_sentinel1_dataset(product_path, group=\"IW1/VH/2\")\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6bea7088",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "abs(ds.measurement).mean().compute()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "cbc3efbc",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "cf2ea217",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "product_path = \"zip://*/manifest.safe::github://bopen:xarray-sentinel@/tests/data/S1B_IW_SLC__1SDV_20210401T052622_20210401T052650_026269_032297_EFA4.zip\"\n",
+    "ds = xarray_sentinel.open_sentinel1_dataset(product_path)\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "634089c1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ds = xarray_sentinel.open_sentinel1_dataset(product_path, group=\"IW1/VH/orbit\")\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3e656f38",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ds = xarray_sentinel.open_sentinel1_dataset(product_path, group=\"IW1/VH/2\")\n",
+    "ds"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "58bc770e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "abs(ds.measurement).mean().compute()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d8ab1d18",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.9.9"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/xarray_sentinel/esa_safe.py b/xarray_sentinel/esa_safe.py
index f0ff900..dd5e3a8 100644
--- a/xarray_sentinel/esa_safe.py
+++ b/xarray_sentinel/esa_safe.py
@@ -1,5 +1,6 @@
 import functools
 import os
+import re
 import typing as T
 from xml.etree import ElementTree
 
@@ -41,6 +42,8 @@ def parse_tag(
     validation: str = "skip",
 ) -> T.Dict[str, T.Any]:
     schema = cached_sentinel1_schemas(schema_type)
+    if hasattr(xml_path, "seek"):
+        xml_path.seek(0)  # type: ignore
     xml_tree = ElementTree.parse(xml_path)
     tag_dict: T.Dict[str, T.Any] = schema.decode(xml_tree, query, validation=validation)  # type: ignore
     assert isinstance(tag_dict, dict), f"{type(tag_dict)} is not dict"
@@ -90,10 +93,19 @@ def findall(
     return values
 
 
+def parse_annotation_filename(name: str) -> T.Tuple[str, str, str]:
+    match = re.match(
+        r"[a-z-]*s1[ab]-([^-]*)-[^-]*-([^-]*)-([\dt]*)-", os.path.basename(name)
+    )
+    if match is None:
+        raise ValueError(f"cannot parse name {name!r}")
+    return tuple(match.groups())  # type: ignore
+
+
 @functools.lru_cache
 def parse_manifest_sentinel1(
     manifest_path: PathOrFileType,
-) -> T.Tuple[T.Dict[str, T.Any], T.Dict[str, str]]:
+) -> T.Tuple[T.Dict[str, T.Any], T.Dict[str, T.Tuple[str, str, str, str]]]:
     # We use ElementTree because we didn't find a XSD definition for the manifest
     manifest = ElementTree.parse(manifest_path)
 
@@ -141,7 +153,13 @@ def parse_manifest_sentinel1(
         location_tag = file_tag.find(".//fileLocation")
         if location_tag is not None:
             file_href = location_tag.attrib["href"]
+            try:
+                swath, polarization, start = parse_annotation_filename(
+                    os.path.basename(file_href)
+                )
+            except ValueError:
+                continue
             file_type = file_tag.attrib["repID"]
-            files[file_href] = file_type
+            files[file_href] = (file_type, swath, polarization, start)
 
     return attributes, files
diff --git a/xarray_sentinel/sentinel1.py b/xarray_sentinel/sentinel1.py
index 6177612..d318a7f 100644
--- a/xarray_sentinel/sentinel1.py
+++ b/xarray_sentinel/sentinel1.py
@@ -23,10 +23,16 @@
 
 
 def get_fs_path(
-    urlpath_or_path: esa_safe.PathType, fs: T.Optional[fsspec.AbstractFileSystem] = None
+    urlpath_or_path: esa_safe.PathType,
+    fs: T.Optional[fsspec.AbstractFileSystem] = None,
+    storage_options: T.Optional[T.Dict[str, T.Any]] = None,
 ) -> T.Tuple[fsspec.AbstractFileSystem, str]:
+    if fs is not None and storage_options is not None:
+        raise TypeError("only one of 'fs' and 'storage_options' can be not None")
     if fs is None:
-        fs, _, paths = fsspec.get_fs_token_paths(urlpath_or_path)
+        fs, _, paths = fsspec.get_fs_token_paths(
+            urlpath_or_path, storage_options=storage_options
+        )
         if len(paths) == 0:
             raise ValueError(f"file or object not found {urlpath_or_path!r}")
         elif len(paths) > 1:
@@ -37,25 +43,6 @@ def get_fs_path(
     return fs, path
 
 
-def get_ancillary_data_paths(
-    base_path: esa_safe.PathType,
-    product_files: T.Dict[str, str],
-) -> T.Dict[str, T.Dict[str, T.Dict[str, str]]]:
-    ancillary_data_paths: T.Dict[str, T.Dict[str, T.Dict[str, str]]] = {}
-    for filename, filetype in product_files.items():
-        # HACK: no easy way to normalise the path component of a urlpath
-        file_path = os.path.join(base_path, os.path.normpath(filename))
-        name = os.path.basename(filename)
-        try:
-            subswath, _, pol = os.path.basename(name).rsplit("-", 8)[1:4]
-        except ValueError:
-            continue
-        swath_dict = ancillary_data_paths.setdefault(subswath.upper(), {})
-        pol_dict = swath_dict.setdefault(pol.upper(), {})
-        pol_dict[filetype] = file_path
-    return ancillary_data_paths
-
-
 def normalise_group(group: T.Optional[str]) -> T.Tuple[str, T.Optional[int]]:
     if group is None:
         group = ""
@@ -363,21 +350,22 @@ def open_azimuth_fm_rate_dataset(annotation: esa_safe.PathOrFileType) -> xr.Data
     return ds
 
 
-def find_avalable_groups(
-    ancillary_data_paths: T.Dict[str, T.Dict[str, T.Dict[str, str]]],
-    product_attrs: T.Dict[str, T.Any],
+def find_available_groups(
+    product_files: T.Dict[str, T.Tuple[str, str, str, str]],
+    product_path: str,
+    check_files_exist: bool = False,
     fs: fsspec.AbstractFileSystem = fsspec.filesystem("file"),
-) -> T.Dict[str, str]:
-    groups: T.Dict[str, str] = {}
-    for subswath_id, subswath_data_path in ancillary_data_paths.items():
-        for pol_id, pol_data_paths in subswath_data_path.items():
-            try:
-                with fs.open(pol_data_paths["s1Level1ProductSchema"]):
-                    pass
-            except FileNotFoundError:
+) -> T.Dict[str, T.List[str]]:
+    groups: T.Dict[str, T.List[str]] = {}
+    for path, (type, swath, polarization, _) in product_files.items():
+        swath_pol_group = f"{swath}/{polarization}".upper()
+        abspath = os.path.join(product_path, os.path.normpath(path))
+        if check_files_exist:
+            if not fs.exists(abspath):
                 continue
-            groups[subswath_id] = ""
-            groups[f"{subswath_id}/{pol_id}"] = pol_data_paths["s1Level1ProductSchema"]
+        if type == "s1Level1ProductSchema":
+            groups[swath.upper()] = [""]
+            groups[swath_pol_group] = [abspath] + groups.get(swath_pol_group, [])
             for metadata_group in [
                 "orbit",
                 "attitude",
@@ -386,41 +374,26 @@ def find_avalable_groups(
                 "gcp",
                 "coordinate_conversion",
             ]:
-                groups[f"{subswath_id}/{pol_id}/{metadata_group}"] = pol_data_paths[
-                    "s1Level1ProductSchema"
-                ]
-
-            try:
-                with fs.open(pol_data_paths["s1Level1CalibrationSchema"]):
-                    pass
-            except FileNotFoundError:
-                continue
-            groups[f"{subswath_id}/{pol_id}/calibration"] = pol_data_paths[
-                "s1Level1CalibrationSchema"
-            ]
-
-            try:
-                with fs.open(pol_data_paths["s1Level1NoiseSchema"]):
-                    pass
-            except FileNotFoundError:
-                continue
-            groups[f"{subswath_id}/{pol_id}/noise_range"] = pol_data_paths[
-                "s1Level1NoiseSchema"
-            ]
-            groups[f"{subswath_id}/{pol_id}/noise_azimuth"] = pol_data_paths[
-                "s1Level1NoiseSchema"
-            ]
+                groups[f"{swath_pol_group}/{metadata_group}"] = [abspath]
+        elif type == "s1Level1CalibrationSchema":
+            groups[f"{swath_pol_group}/calibration"] = [abspath]
+        elif type == "s1Level1NoiseSchema":
+            groups[f"{swath_pol_group}/noise_range"] = [abspath]
+            groups[f"{swath_pol_group}/noise_azimuth"] = [abspath]
+        elif type == "s1Level1MeasurementSchema":
+            groups[swath_pol_group] = [abspath] + groups.get(swath_pol_group, [])
 
     return groups
 
 
 def open_pol_dataset(
-    measurement: esa_safe.PathType,
+    measurement: esa_safe.PathOrFileType,
     annotation: esa_safe.PathOrFileType,
-    chunks: T.Optional[T.Union[int, T.Dict[str, int]]] = None,
+    fs: T.Optional[fsspec.AbstractFileSystem] = None,
 ) -> xr.Dataset:
-    image_information = esa_safe.parse_tag(annotation, ".//imageInformation")
+
     product_information = esa_safe.parse_tag(annotation, ".//productInformation")
+    image_information = esa_safe.parse_tag(annotation, ".//imageInformation")
     swath_timing = esa_safe.parse_tag(annotation, ".//swathTiming")
 
     number_of_samples = image_information["numberOfSamples"]
@@ -440,13 +413,17 @@ def open_pol_dataset(
         "azimuth_time_interval": azimuth_time_interval,
         "slant_range_time_interval": slant_range_time_interval,
     }
+    chunks = {}
+    swap_dims = {}
 
     azimuth_time = pd.date_range(
         start=first_azimuth_time,
         periods=number_of_lines,
         freq=pd.Timedelta(azimuth_time_interval, "s"),
     ).values
-    if number_of_bursts:
+    if number_of_bursts == 0:
+        swap_dims = {"line": "azimuth_time", "pixel": "slant_range_time"}
+    else:
         lines_per_burst = swath_timing["linesPerBurst"]
         attrs.update(
             {
@@ -465,14 +442,14 @@ def open_pol_dataset(
             azimuth_time[
                 lines_per_burst * burst_index : lines_per_burst * (burst_index + 1)
             ] = azimuth_time_burst
-        if chunks is None:
-            # chunk at burst boundaries if dask is present
-            try:
-                import dask  # noqa
 
-                chunks = {"y": lines_per_burst}
-            except ModuleNotFoundError:
-                pass
+        # chunk at burst boundaries if dask is present
+        try:
+            import dask  # noqa
+
+            chunks["y"] = lines_per_burst
+        except ModuleNotFoundError:
+            pass
 
     coords = {
         "pixel": np.arange(0, number_of_samples, dtype=int),
@@ -495,18 +472,20 @@ def open_pol_dataset(
             number_of_samples,
         )
         coords["ground_range"] = ("pixel", ground_range)
+        swap_dims = {"line": "azimuth_time", "pixel": "ground_range"}
     else:
         raise ValueError(f"unknown projection {product_information['projection']}")
 
-    arr = xr.open_dataarray(measurement, engine="rasterio", chunks=chunks)  # type: ignore
+    # temporary ugly work-around to get fsspec support with rasterio >= 1.3a3
+    try:
+        arr = xr.open_dataarray(fs.open(measurement), engine="rasterio", chunks=chunks)  # type: ignore
+    except AttributeError:
+        arr = xr.open_dataarray(measurement, engine="rasterio", chunks=chunks)  # type: ignore
+
     arr = arr.squeeze("band").drop_vars(["band", "spatial_ref"])
     arr = arr.rename({"y": "line", "x": "pixel"})
     arr = arr.assign_coords(coords)
-
-    if product_information["projection"] == "Ground Range":
-        arr = arr.swap_dims({"line": "azimuth_time", "pixel": "ground_range"})
-    elif number_of_bursts == 0:
-        arr = arr.swap_dims({"line": "azimuth_time", "pixel": "slant_range_time"})
+    arr = arr.swap_dims(swap_dims)
 
     return xr.Dataset(attrs=attrs, data_vars={"measurement": arr})
 
@@ -665,28 +644,45 @@ def compute_burst_centres(
 }
 
 
+def do_override_product_files(
+    template: str, product_files: T.Dict[str, T.Tuple[str, str, str, str]]
+) -> T.Dict[str, T.Tuple[str, str, str, str]]:
+    overridden_product_files = {}
+    for path, (type, swath, polarization, date) in product_files.items():
+        ext = os.path.splitext(path)[1]
+        dirname = os.path.dirname(path)
+        overridden_path = template.format(**locals())
+        overridden_product_files[overridden_path] = (type, swath, polarization, date)
+    return overridden_product_files
+
+
 def open_sentinel1_dataset(
     product_urlpath: esa_safe.PathType,
     *,
     drop_variables: T.Optional[T.Tuple[str]] = None,
     group: T.Optional[str] = None,
-    chunks: T.Optional[T.Union[int, T.Dict[str, int]]] = None,
     fs: T.Optional[fsspec.AbstractFileSystem] = None,
+    storage_options: T.Optional[T.Dict[str, T.Any]] = None,
+    check_files_exist: bool = False,
+    override_product_files: T.Optional[str] = None,
 ) -> xr.Dataset:
-    fs, manifest_path = get_fs_path(product_urlpath, fs)
+    if drop_variables is not None:
+        warnings.warn("'drop_variables' is currently ignored")
 
+    fs, manifest_path = get_fs_path(product_urlpath, fs, storage_options)
     if fs.isdir(manifest_path):
         manifest_path = os.path.join(manifest_path, "manifest.safe")
+    product_path = os.path.dirname(manifest_path)
 
     with fs.open(manifest_path) as file:
         product_attrs, product_files = esa_safe.parse_manifest_sentinel1(file)
 
-    base_path = os.path.dirname(manifest_path)
-    ancillary_data_paths = get_ancillary_data_paths(base_path, product_files)
-    if drop_variables is not None:
-        warnings.warn("'drop_variables' is currently ignored")
+    if override_product_files:
+        product_files = do_override_product_files(override_product_files, product_files)
 
-    groups = find_avalable_groups(ancillary_data_paths, product_attrs, fs=fs)
+    groups = find_available_groups(
+        product_files, product_path, check_files_exist=check_files_exist, fs=fs
+    )
 
     group, burst_index = normalise_group(group)
     absgroup = f"/{group}"
@@ -698,26 +694,20 @@ def open_sentinel1_dataset(
 
     metadata = ""
 
+    ds = xr.Dataset()
     if group == "":
-        ds = xr.Dataset()
         subgroups = list(groups)
     else:
         subgroups = [
             g[len(group) + 1 :] for g in groups if g.startswith(group) and g != group
         ]
 
-        if "/" not in group:
-            ds = xr.Dataset()
-        elif group.count("/") == 1:
-            subswath, pol = group.split("/", 1)
-            ds = open_pol_dataset(
-                ancillary_data_paths[subswath][pol]["s1Level1MeasurementSchema"],
-                ancillary_data_paths[subswath][pol]["s1Level1ProductSchema"],
-                chunks=chunks,
-            )
-        else:
-            subswath, pol, metadata = group.split("/", 2)
-            with fs.open(groups[group]) as file:
+        if group.count("/") == 1:
+            with fs.open(groups[group][1]) as annotation:
+                ds = open_pol_dataset(groups[group][0], annotation, fs=fs)
+        elif group.count("/") == 2:
+            _, _, metadata = group.split("/", 2)
+            with fs.open(groups[group][0]) as file:
                 ds = METADATA_OPENERS[metadata](file)
 
     product_attrs["group"] = absgroup
diff --git a/xarray_sentinel/xarray_backends.py b/xarray_sentinel/xarray_backends.py
index be635d1..8ac3457 100644
--- a/xarray_sentinel/xarray_backends.py
+++ b/xarray_sentinel/xarray_backends.py
@@ -12,9 +12,15 @@ def open_dataset(  # type: ignore
         filename_or_obj: str,
         drop_variables: T.Optional[T.Tuple[str]] = None,
         group: T.Optional[str] = None,
+        storage_options: T.Optional[T.Dict[str, T.Any]] = None,
+        override_product_files: T.Optional[str] = None,
     ) -> xr.Dataset:
         ds = sentinel1.open_sentinel1_dataset(
-            filename_or_obj, drop_variables=drop_variables, group=group
+            filename_or_obj,
+            drop_variables=drop_variables,
+            group=group,
+            storage_options=storage_options,
+            override_product_files=override_product_files,
         )
         return ds
 


Test patch:
diff --git a/tests/test_10_esa_safe.py b/tests/test_10_esa_safe.py
index 219fd67..0aa51b0 100644
--- a/tests/test_10_esa_safe.py
+++ b/tests/test_10_esa_safe.py
@@ -154,6 +154,17 @@ def test_parse_tag_as_list() -> None:
     assert res == []
 
 
+def test_parse_annotation_filename() -> None:
+    res = esa_safe.parse_annotation_filename(
+        "s1b-iw1-slc-vv-20210401t052624-20210401t052649-026269-032297-004.xml"
+    )
+
+    assert res == ("iw1", "vv", "20210401t052624")
+
+    with pytest.raises(ValueError):
+        esa_safe.parse_annotation_filename("")
+
+
 @pytest.mark.parametrize("product_id,expected", SENTINEL1_ATTRIBUTES.items())
 def test_parse_manifest_sentinel1(
     product_id: str, expected: T.Dict[str, T.Any]
diff --git a/tests/test_20_sentinel1.py b/tests/test_20_sentinel1.py
index 3e928ed..1b6323e 100644
--- a/tests/test_20_sentinel1.py
+++ b/tests/test_20_sentinel1.py
@@ -4,7 +4,7 @@
 import pytest
 import xarray as xr
 
-from xarray_sentinel import sentinel1
+from xarray_sentinel import esa_safe, sentinel1
 
 DATA_FOLDER = pathlib.Path(__file__).parent / "data"
 
@@ -13,40 +13,38 @@
     / "S1B_IW_SLC__1SDV_20210401T052622_20210401T052650_026269_032297_EFA4.SAFE"
 )
 SLC_IW1_VV_annotation = (
-    DATA_FOLDER
-    / "S1B_IW_SLC__1SDV_20210401T052622_20210401T052650_026269_032297_EFA4.SAFE"
+    SLC_IW
     / "annotation"
     / "s1b-iw1-slc-vv-20210401t052624-20210401t052649-026269-032297-004.xml"
 )
 SLC_IW1_VV_calibration = (
-    DATA_FOLDER
-    / "S1B_IW_SLC__1SDV_20210401T052622_20210401T052650_026269_032297_EFA4.SAFE"
+    SLC_IW
     / "annotation"
     / "calibration"
     / "calibration-s1b-iw1-slc-vv-20210401t052624-20210401t052649-026269-032297-004.xml"
 )
 SLC_IW1_VV_noise = (
-    DATA_FOLDER
-    / "S1B_IW_SLC__1SDV_20210401T052622_20210401T052650_026269_032297_EFA4.SAFE"
+    SLC_IW
     / "annotation"
     / "calibration"
     / "noise-s1b-iw1-slc-vv-20210401t052624-20210401t052649-026269-032297-004.xml"
 )
 SLC_IW1_VV_measurement = (
-    DATA_FOLDER
-    / "S1B_IW_SLC__1SDV_20210401T052622_20210401T052650_026269_032297_EFA4.SAFE"
+    SLC_IW
     / "measurement"
     / "s1b-iw1-slc-vv-20210401t052624-20210401t052649-026269-032297-004.tiff"
 )
-SLC_S3_VV_annotation = (
+SLC_S3 = (
     DATA_FOLDER
     / "S1A_S3_SLC__1SDV_20210401T152855_20210401T152914_037258_04638E_6001.SAFE"
+)
+SLC_S3_VV_annotation = (
+    SLC_S3
     / "annotation"
     / "s1a-s3-slc-vv-20210401t152855-20210401t152914-037258-04638e-002.xml"
 )
 SLC_S3_VV_measurement = (
-    DATA_FOLDER
-    / "S1A_S3_SLC__1SDV_20210401T152855_20210401T152914_037258_04638E_6001.SAFE"
+    SLC_S3
     / "measurement"
     / "s1a-s3-slc-vv-20210401t152855-20210401t152914-037258-04638e-002.tiff"
 )
@@ -74,42 +72,6 @@ def test_get_fs_path() -> None:
         sentinel1.get_fs_path(DATA_FOLDER / "*")
 
 
-def test_get_ancillary_data() -> None:
-    base_path = (
-        DATA_FOLDER
-        / "S1B_IW_SLC__1SDV_20210401T052622_20210401T052650_026269_032297_EFA4.SAFE"
-    )
-
-    product_files = {
-        "./annotation/s1b-iw1-slc-vh-xx-xx-xx-xx-xx.xml": "s1Level1ProductSchema",
-        "./annotation/calibration/noise-s1b-iw1-slc-vh-x-x-x-x-x.xml": "s1Level1NoiseSchema",
-        "./annotation/calibration/calibration-s1b-iw1-slc-vh-x-x-x-x-x.xml": "s1Level1CalibrationSchema",
-        "./annotation/s1b-iw1-slc-vv-x-x-x-x-x.xml": "s1Level1ProductSchema",
-        "./annotation/calibration/noise-s1b-iw1-slc-vv-x-x-x-x-x.xml": "s1Level1NoiseSchema",
-        "./annotation/calibration/calibration-s1b-iw1-slc-vv-x-x-x-x-x.xml": "s1Level1CalibrationSchema",
-        "./measurement/s1b-iw1-slc-vh-x-x-x-x-x.tiff": "s1Level1MeasurementSchema",
-        "./measurement/s1b-iw1-slc-vv-x-x-x-x-x.tiff": "s1Level1MeasurementSchema",
-    }
-
-    ancillary_data_paths = sentinel1.get_ancillary_data_paths(base_path, product_files)
-
-    expected = {"IW1"}
-    assert set(ancillary_data_paths) == expected
-
-    expected = {"VV", "VH"}
-    assert set(ancillary_data_paths["IW1"]) == expected
-
-    expected = {
-        "s1Level1ProductSchema",
-        "s1Level1CalibrationSchema",
-        "s1Level1NoiseSchema",
-        "s1Level1MeasurementSchema",
-    }
-    assert set(ancillary_data_paths["IW1"]["VV"]) == expected
-
-    assert isinstance(ancillary_data_paths["IW1"]["VV"]["s1Level1ProductSchema"], str)
-
-
 def test_normalise_group() -> None:
     assert sentinel1.normalise_group(None) == ("", None)
     assert sentinel1.normalise_group("/") == ("", None)
@@ -210,35 +172,40 @@ def test_build_burst_id() -> None:
 
 
 def test_find_avalable_groups() -> None:
-    ancillary_data_paths = {
-        "IW1": {
-            "VV": {
-                "s1Level1ProductSchema": f"{SLC_IW}/annotation/"
-                + "s1b-iw1-slc-vv-20210401t052624-20210401t052649-026269-032297-004.xml",
-                "s1Level1CalibrationSchema": f"{SLC_IW}/annotation/calibration/"
-                + "calibration-s1b-iw1-slc-vv-20210401t052624-20210401t052649-026269-032297-004.xml",
-                "s1Level1NoiseSchema": f"{SLC_IW}/annotation/calibration/"
-                + "noise-s1b-iw1-slc-vv-20210401t052624-20210401t052649-026269-032297-004.xml",
-            },
-        },
-    }
-    product_attrs = {"sat:relative_orbit": 168}
+    _, product_files = esa_safe.parse_manifest_sentinel1(SLC_S3 / "manifest.safe")
     expected_groups = {
-        "IW1",
-        "IW1/VV",
-        "IW1/VV/attitude",
-        "IW1/VV/gcp",
-        "IW1/VV/orbit",
-        "IW1/VV/dc_estimate",
-        "IW1/VV/azimuth_fm_rate",
-        "IW1/VV/coordinate_conversion",
-        "IW1/VV/calibration",
-        "IW1/VV/noise_range",
-        "IW1/VV/noise_azimuth",
+        "S3",
+        "S3/VV",
+        "S3/VV/attitude",
+        "S3/VV/gcp",
+        "S3/VV/orbit",
+        "S3/VV/dc_estimate",
+        "S3/VV/azimuth_fm_rate",
+        "S3/VV/coordinate_conversion",
+        "S3/VV/calibration",
+        "S3/VV/noise_range",
+        "S3/VV/noise_azimuth",
+        "S3",
+        "S3/VH",
+        "S3/VH/attitude",
+        "S3/VH/gcp",
+        "S3/VH/orbit",
+        "S3/VH/dc_estimate",
+        "S3/VH/azimuth_fm_rate",
+        "S3/VH/coordinate_conversion",
+        "S3/VH/calibration",
+        "S3/VH/noise_range",
+        "S3/VH/noise_azimuth",
     }
 
-    groups = sentinel1.find_avalable_groups(ancillary_data_paths, product_attrs)
-    assert set(groups) == expected_groups
+    res = sentinel1.find_available_groups(product_files, str(SLC_IW))
+
+    assert set(res) == expected_groups
+
+    res = sentinel1.find_available_groups(
+        product_files, str(SLC_IW), check_files_exist=True
+    )
+    assert res == {}
 
 
 def test_compute_burst_centres() -> None:
@@ -300,12 +267,11 @@ def test_open_dataset_virtual_groups() -> None:
 
 
 def test_open_dataset_chunks() -> None:
-    res = sentinel1.open_sentinel1_dataset(SLC_IW, group="IW1/VV", chunks=1000)
+    res = sentinel1.open_sentinel1_dataset(SLC_IW, group="IW1/VV")
 
     assert isinstance(res, xr.Dataset)
     assert len(res.dims) == 2
-    assert np.allclose(res.measurement.chunks[0][:-1], 1000)
-    assert np.allclose(res.measurement.chunks[1][:-1], 1000)
+    assert res.measurement.chunks[0][0] == res.attrs["lines_per_burst"]
     assert not np.all(np.isnan(res.measurement))
 
 
diff --git a/tests/test_30_sentinel1_fsspec.py b/tests/test_30_sentinel1_fsspec.py
index 0d90078..62cb5fc 100644
--- a/tests/test_30_sentinel1_fsspec.py
+++ b/tests/test_30_sentinel1_fsspec.py
@@ -93,3 +93,9 @@ def test_open_dataset_zip() -> None:
 
     assert isinstance(res, xr.Dataset)
     assert res.dims == {"axis": 3, "azimuth_time": 17}
+
+    res = sentinel1.open_sentinel1_dataset(zip_urlpath, group="IW1/VV/0")
+
+    assert isinstance(res, xr.Dataset)
+    assert res.dims == {"slant_range_time": 21632, "azimuth_time": 1501}
+    assert abs(res.measurement[:40, :40]).mean() >= 0