Skip to content

Commit 221beec

Browse files
committed
Add support for COG and UPath in SAR reader
1 parent c82e0cd commit 221beec

3 files changed

Lines changed: 94 additions & 26 deletions

File tree

satpy/etc/readers/sar-c_safe.yaml

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,27 @@ reader:
3838
transitive: false
3939

4040
file_types:
41-
safe_measurement:
42-
file_patterns: ['{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}.SAFE/measurement/{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}.tiff']
43-
requires: [safe_calibration, safe_noise, safe_annotation]
44-
safe_calibration:
45-
file_patterns: ['{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}.SAFE/annotation/calibration/calibration-{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}.xml']
46-
requires: [safe_annotation]
47-
safe_noise:
48-
file_patterns: ['{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}.SAFE/annotation/calibration/noise-{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}.xml']
49-
requires: [safe_annotation]
50-
safe_annotation:
51-
file_patterns: ['{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}.SAFE/annotation/{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}.xml']
52-
41+
safe_measurement:
42+
file_patterns:
43+
- "{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}.SAFE/measurement/{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}.tiff"
44+
- "{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}_COG.SAFE/measurement/{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}-cog.tiff"
45+
requires: [safe_calibration, safe_noise, safe_annotation]
46+
safe_calibration:
47+
file_patterns:
48+
- "{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}.SAFE/annotation/calibration/calibration-{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}.xml"
49+
- "{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}_COG.SAFE/annotation/calibration/calibration-{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}-cog.xml"
50+
requires: [safe_annotation]
51+
safe_noise:
52+
file_patterns:
53+
- "{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}.SAFE/annotation/calibration/noise-{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}.xml"
54+
- "{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}_COG.SAFE/annotation/calibration/noise-{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}-cog.xml"
55+
requires: [safe_annotation]
56+
safe_annotation:
57+
file_patterns:
58+
- "{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}.SAFE/annotation/{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}.xml"
59+
- "{fmission_id:3s}_{fsar_mode:2s}_{fproduct_type:3s}{fresolution:1s}_{fprocessing_level:1s}{fproduct_class:1s}{fpolarization:2s}_{fstart_time:%Y%m%dT%H%M%S}_{fend_time:%Y%m%dT%H%M%S}_{forbit_number:6d}_{fmission_data_take_id:6s}_{fproduct_unique_id:4s}_COG.SAFE/annotation/{mission_id:3s}-{swath_id:2s}-{product_type:3s}-{polarization:2s}-{start_time:%Y%m%dt%H%M%S}-{end_time:%Y%m%dt%H%M%S}-{orbit_number:6d}-{mission_data_take_id:6s}-{image_number:3s}-cog.xml"
5360

5461
datasets:
55-
5662
latitude:
5763
name: latitude
5864
resolution: 80
@@ -77,7 +83,6 @@ datasets:
7783
polarization: [hh, hv, vv, vh]
7884
units: meter
7985

80-
8186
measurement:
8287
name: measurement
8388
sensor: sar-c
@@ -105,11 +110,11 @@ datasets:
105110
polarization: [hh, hv, vv, vh]
106111
file_type: safe_noise
107112
xml_item:
108-
- noiseVector
109-
- noiseRangeVector
113+
- noiseVector
114+
- noiseRangeVector
110115
xml_tag:
111-
- noiseLut
112-
- noiseRangeLut
116+
- noiseLut
117+
- noiseRangeLut
113118

114119
sigma:
115120
name: sigma_squared

satpy/readers/sar_c_safe.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
from collections import defaultdict
4242
from datetime import timezone as tz
4343
from functools import cached_property
44-
from pathlib import Path
4544
from threading import Lock
4645

4746
import defusedxml.ElementTree as ET
@@ -52,6 +51,7 @@
5251
from dask import array as da
5352
from geotiepoints.geointerpolator import lonlat2xyz, xyz2lonlat
5453
from geotiepoints.interpolator import MultipleSplineInterpolator
54+
from upath import UPath
5555
from xarray import DataArray
5656

5757
from satpy.dataset.data_dict import DatasetDict
@@ -112,7 +112,7 @@ def __init__(self, filename, filename_info, filetype_info,
112112
self._end_time = filename_info["end_time"].replace(tzinfo=tz.utc)
113113
self._polarization = filename_info["polarization"]
114114
if isinstance(self.filename, str):
115-
self.filename = Path(self.filename)
115+
self.filename = UPath(self.filename)
116116
with self.filename.open() as fd:
117117
self.root = ET.parse(fd)
118118
self._image_shape = image_shape

satpy/tests/reader_tests/test_sar_c_safe.py

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@
2121
from datetime import datetime
2222
from enum import Enum
2323
from pathlib import Path
24+
from zipfile import ZipFile
2425

2526
import numpy as np
2627
import pytest
2728
import yaml
29+
from pytest_lazy_fixtures import lf
30+
from upath import UPath
31+
32+
from satpy.readers.core.remote import FSFile
2833

2934
geotiepoints = pytest.importorskip("geotiepoints", "1.7.5")
3035

@@ -43,10 +48,15 @@
4348
END_TIME = datetime(2019, 2, 1, 2, 47, 20)
4449

4550
@pytest.fixture(scope="module")
46-
def granule_directory(tmp_path_factory):
51+
def data_directory(tmp_path_factory):
52+
"""Create a granule directory."""
53+
return tmp_path_factory.mktemp("data")
54+
55+
56+
@pytest.fixture(scope="module")
57+
def granule_directory(data_directory):
4758
"""Create a granule directory."""
48-
data_dir = tmp_path_factory.mktemp("data")
49-
gdir = data_dir / f"S1A_IW_GRDH_1SDV_{dirname_suffix}.SAFE"
59+
gdir = data_directory / f"S1A_IW_GRDH_1SDV_{dirname_suffix}.SAFE"
5060
os.mkdir(gdir)
5161
return gdir
5262

@@ -62,6 +72,29 @@ def annotation_file(granule_directory):
6272
return annotation_file
6373

6474

75+
@pytest.fixture(scope="module")
76+
def zipped_annotation_file(annotation_file, data_directory):
77+
"""Create a zipped annotation file."""
78+
zip_file = data_directory / "annotation.zip"
79+
with ZipFile(zip_file, mode="w") as archive:
80+
basename = os.path.join(*os.path.normpath(annotation_file).split(os.path.sep)[-3:])
81+
archive.write(annotation_file, basename)
82+
fname = f"zip://{basename}::file://{zip_file.as_posix()}"
83+
return fname
84+
85+
86+
@pytest.fixture(scope="module")
87+
def upath_annotation_file(zipped_annotation_file):
88+
"""Create a upath of a zipped annotation file."""
89+
return UPath(zipped_annotation_file)
90+
91+
92+
@pytest.fixture(scope="module")
93+
def fs_annotation_file(upath_annotation_file):
94+
"""Create an FSFile of a zipped annotation file."""
95+
return FSFile(upath_annotation_file)
96+
97+
6598
@pytest.fixture(scope="module")
6699
def annotation_filehandler(annotation_file):
67100
"""Create an annotation filehandler."""
@@ -161,13 +194,35 @@ def measurement_file(granule_directory):
161194

162195

163196
@pytest.fixture(scope="module")
164-
def measurement_filehandler(measurement_file, noise_filehandler, calibration_filehandler):
197+
def zipped_measurement_file(measurement_file, data_directory):
198+
"""Create a zipped measurement file."""
199+
zip_file = data_directory / "measurement.zip"
200+
with ZipFile(zip_file, mode="w") as archive:
201+
basename = os.path.join(*os.path.normpath(measurement_file).split(os.path.sep)[-3:])
202+
archive.write(measurement_file, basename)
203+
return f"zip://{basename}::file://{zip_file.as_posix()}"
204+
205+
206+
@pytest.fixture(scope="module")
207+
def upath_measurement_file(zipped_measurement_file):
208+
"""Create a upath of a zipped measurement file."""
209+
return UPath(zipped_measurement_file)
210+
211+
212+
@pytest.fixture(scope="module")
213+
def fsfile_measurement_file(upath_measurement_file):
214+
"""Create an FSFile of a zipped measurement file."""
215+
return FSFile(upath_measurement_file)
216+
217+
218+
@pytest.fixture(scope="module", params=("measurement_file", "upath_measurement_file", "fsfile_measurement_file"))
219+
def measurement_filehandler(request, noise_filehandler, calibration_filehandler):
165220
"""Create a measurement filehandler."""
166221
filename_info = {"mission_id": "S1A", "dataset_name": "foo", "start_time": START_TIME, "end_time": END_TIME,
167222
"polarization": "vv"}
168223
filetype_info = None
169224
from satpy.readers.sar_c_safe import SAFEGRD
170-
filehandler = SAFEGRD(measurement_file,
225+
filehandler = SAFEGRD(request.getfixturevalue(request.param),
171226
filename_info,
172227
filetype_info,
173228
calibration_filehandler,
@@ -782,8 +837,16 @@ def test_get_calibration_constant(self, calibration_filehandler):
782837
assert type(res) is np.float32
783838

784839

785-
def test_incidence_angle(annotation_filehandler):
840+
@pytest.mark.parametrize(("annotation_file_thing"), [
841+
lf("annotation_file"),
842+
lf("zipped_annotation_file"),
843+
lf("upath_annotation_file"),
844+
lf("fs_annotation_file")
845+
])
846+
def test_incidence_angle(annotation_file_thing):
786847
"""Test reading the incidence angle in an annotation file."""
848+
filename_info = dict(start_time=START_TIME, end_time=END_TIME, polarization="vv")
849+
annotation_filehandler = SAFEXMLAnnotation(annotation_file_thing, filename_info, None)
787850
query = DataQuery(name="incidence_angle", polarization="vv")
788851
res = annotation_filehandler.get_dataset(query, {})
789852
np.testing.assert_allclose(res, 19.18318046)

0 commit comments

Comments
 (0)