Skip to content

Commit

Permalink
OGC Coverages: minor refactoring and reformatting
Browse files Browse the repository at this point in the history
  • Loading branch information
pont-us committed Dec 12, 2023
1 parent 597f8ae commit f2edb8b
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 69 deletions.
13 changes: 7 additions & 6 deletions test/webapi/ows/coverages/test_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
dtype_to_opengis_datatype,
get_dataarray_description,
get_units,
is_xy_order, transform_bbox,
is_xy_order,
transform_bbox,
)


Expand All @@ -58,7 +59,7 @@ def test_get_coverage_data_tiff(self):
'bbox': ['51,1,52,2'],
'bbox-crs': ['[EPSG:4326]'],
'datetime': ['2017-01-25T00:00:00Z'],
'properties': ['conc_chl']
'properties': ['conc_chl'],
}
content, content_bbox, content_crs = get_coverage_data(
get_coverages_ctx().datasets_ctx, 'demo', query, 'image/tiff'
Expand Down Expand Up @@ -116,7 +117,7 @@ def test_get_coverage_data_netcdf(self):
'time_bnds',
'conc_chl',
'kd489',
'crs'
'crs',
},
set(ds.variables),
)
Expand Down Expand Up @@ -160,7 +161,7 @@ def test_get_coverage_data_time_slice_subset(self):
'time',
'time_bnds',
'conc_chl',
'spatial_ref'
'spatial_ref',
},
set(ds.variables),
)
Expand Down Expand Up @@ -302,5 +303,5 @@ def test_is_xy(self):
def test_transform_bbox_same_crs(self):
self.assertEqual(
bbox := [1, 2, 3, 4],
transform_bbox(bbox, crs := pyproj.CRS('EPSG:4326'), crs)
)
transform_bbox(bbox, crs := pyproj.CRS('EPSG:4326'), crs),
)
20 changes: 10 additions & 10 deletions test/webapi/ows/coverages/test_scaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@


class ScalingTest(unittest.TestCase):

def setUp(self):
self.epsg4326 = pyproj.CRS('EPSG:4326')
self.ds = xcube.core.new.new_cube()
Expand All @@ -22,33 +21,34 @@ def test_default_scaling(self):
def test_no_data(self):
with self.assertRaises(ApiError.NotFound):
CoverageScaling(
CoverageRequest({}), self.epsg4326,
self.ds.isel(lat=slice(0, 0))
CoverageRequest({}),
self.epsg4326,
self.ds.isel(lat=slice(0, 0)),
)

def test_crs_no_valid_axis(self):
@dataclass
class CrsMock:
axis_info = [object()]

# noinspection PyTypeChecker
self.assertIsNone(
CoverageScaling(CoverageRequest({}), CrsMock(), self.ds)
.get_axis_from_crs(set())
CoverageScaling(
CoverageRequest({}), CrsMock(), self.ds
).get_axis_from_crs(set())
)

def test_scale_factor(self):
scaling = CoverageScaling(
CoverageRequest({'scale-factor': ['2']}),
self.epsg4326,
self.ds
CoverageRequest({'scale-factor': ['2']}), self.epsg4326, self.ds
)
self.assertEqual((2, 2), scaling.scale)

def test_scale_axes(self):
scaling = CoverageScaling(
CoverageRequest({'scale-axes': ['Lat(3),Lon(1.2)']}),
self.epsg4326,
self.ds
self.ds,
)
self.assertEqual((1.2, 3), scaling.scale)
self.assertEqual((300, 60), scaling.size)
Expand All @@ -57,7 +57,7 @@ def test_scale_size(self):
scaling = CoverageScaling(
CoverageRequest({'scale-size': ['Lat(90),Lon(240)']}),
self.epsg4326,
self.ds
self.ds,
)
self.assertEqual((240, 90), scaling.size)
self.assertEqual((1.5, 2), scaling.scale)
69 changes: 24 additions & 45 deletions xcube/webapi/ows/coverages/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,7 @@ def get_coverage_data(
subset_crs = request.subset_crs

if request.properties is not None:
requested_vars = set(request.properties)
data_vars = set(map(str, ds.data_vars))
unrecognized_vars = requested_vars - data_vars
if unrecognized_vars == set():
ds = ds.drop_vars(
list(data_vars - requested_vars - {'crs', 'spatial_ref'})
)
else:
raise ApiError.BadRequest(
f'The following properties are not present in the coverage '
f'{collection_id}: {", ".join(unrecognized_vars)}'
)
ds = _apply_properties(collection_id, ds, request.properties)

# https://docs.ogc.org/DRAFTS/19-087.html#datetime-parameter-subset-requirements
# requirement 7D: "If a datetime parameter is specified requesting a
Expand All @@ -136,20 +125,12 @@ def get_coverage_data(
if request.bbox is not None:
ds = _apply_bbox(ds, request.bbox, bbox_crs, always_xy=False)

# NB: a default scale factor of 1 is not compulsory. We may in future
# choose to downscale by default if a large coverage is requested.
scale_factor = 1 if request.scale_factor is None else request.scale_factor
_assert_coverage_size_ok(ds, scale_factor)

source_gm = GridMapping.from_dataset(ds, crs=native_crs)
target_gm = source_gm

scaling = CoverageScaling(request, final_crs, ds)
_assert_coverage_size_ok(scaling)
target_gm = source_gm = GridMapping.from_dataset(ds, crs=native_crs)
if native_crs != final_crs:
target_gm = target_gm.transform(final_crs).to_regular()

scaling = CoverageScaling(request, final_crs, ds)
target_gm = scaling.apply(target_gm)

if target_gm is not source_gm:
ds = resample_in_space(ds, source_gm=source_gm, target_gm=target_gm)

Expand Down Expand Up @@ -194,25 +175,28 @@ def get_coverage_data(
return content, final_bbox, final_crs


def _assert_coverage_size_ok(ds: xr.Dataset, scale_factor: float):
def _apply_properties(collection_id, ds, properties):
requested_vars = set(properties)
data_vars = set(map(str, ds.data_vars))
unrecognized_vars = requested_vars - data_vars
if unrecognized_vars == set():
ds = ds.drop_vars(
list(data_vars - requested_vars - {'crs', 'spatial_ref'})
)
else:
raise ApiError.BadRequest(
f'The following properties are not present in the coverage '
f'{collection_id}: {", ".join(unrecognized_vars)}'
)
return ds


def _assert_coverage_size_ok(scaling: CoverageScaling):
size_limit = 4000 * 4000 # TODO make this configurable
h_dim = get_h_dim(ds)
v_dim = get_v_dim(ds)
for d in h_dim, v_dim:
size = ds.dims[d]
if size == 0:
# Requirement 8C currently specifies a 204 rather than 404 here,
# but spec will soon be updated to allow 404 as an alternative.
# (J. Jacovella-St-Louis, pers. comm., 2023-11-27).
raise ApiError.NotFound(
f'Requested coverage contains no data: {d} has zero size.'
)
if (h_size := ds.dims[h_dim] / scale_factor) * (
y_size := ds.dims[v_dim] / scale_factor
) > size_limit:
x, y = scaling.size
if (x * y) > size_limit:
raise ApiError.ContentTooLarge(
f'Requested coverage is too large:'
f'{h_size} × {y_size} > {size_limit}.'
f'Requested coverage is too large:' f'{x} × {y} > {size_limit}.'
)


Expand Down Expand Up @@ -350,11 +334,6 @@ def get_bbox_from_ds(ds: xr.Dataset):
return bbox


def apply_scaling(gm: GridMapping, ds: xr.Dataset, request: CoverageRequest):
# TODO: implement me
pass


def _find_geographic_parameters(
names: list[str],
) -> tuple[Optional[str], Optional[str]]:
Expand Down
18 changes: 10 additions & 8 deletions xcube/webapi/ows/coverages/scaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@


class CoverageScaling:

_scale: Optional[tuple[float, float]] = None
_final_size: Optional[tuple[int, int]] = None
_initial_size: tuple[int, int]
_crs: pyproj.CRS
_x_name: str
_y_name: str

def __init__(self, request: CoverageRequest, crs: pyproj.CRS,
ds: xr.Dataset):
def __init__(
self, request: CoverageRequest, crs: pyproj.CRS, ds: xr.Dataset
):
h_dim = get_h_dim(ds)
v_dim = get_v_dim(ds)
for d in h_dim, v_dim:
Expand Down Expand Up @@ -100,7 +100,7 @@ def size(self) -> tuple[float, float]:
return x_initial / x_scale, y_initial / y_scale

def _get_xy_values(
self, axis_to_value: dict[str, float]
self, axis_to_value: dict[str, float]
) -> tuple[float, float]:
x, y = None, None
for axis in axis_to_value:
Expand All @@ -114,10 +114,12 @@ def get_axis_from_crs(self, valid_identifiers: set[str]):
for axis in self._crs.axis_info:
if not hasattr(axis, 'abbrev'):
continue
identifiers = set(map(
lambda attr: getattr(axis, attr, '').lower(),
['name', 'abbrev']
))
identifiers = set(
map(
lambda attr: getattr(axis, attr, '').lower(),
['name', 'abbrev'],
)
)
if identifiers & valid_identifiers:
return axis.abbrev
return None
Expand Down

0 comments on commit f2edb8b

Please sign in to comment.