Skip to content

Commit 955a2d3

Browse files
committed
New function "add_spatial_ref"
1 parent 9b61e11 commit 955a2d3

File tree

2 files changed

+118
-0
lines changed

2 files changed

+118
-0
lines changed

test/core/gridmapping/test_cfconv.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22
import unittest
33
from typing import Set
44

5+
import fsspec
56
import numpy as np
67
import pyproj
78
import xarray as xr
89

10+
from test.s3test import MOTO_SERVER_ENDPOINT_URL
11+
from test.s3test import S3Test
12+
from xcube.core.gridmapping import GridMapping
913
from xcube.core.gridmapping.cfconv import GridCoords
1014
from xcube.core.gridmapping.cfconv import GridMappingProxy
15+
from xcube.core.gridmapping.cfconv import add_spatial_ref
1116
from xcube.core.gridmapping.cfconv import get_dataset_grid_mapping_proxies
17+
from xcube.core.new import new_cube
18+
from xcube.core.store.fs.registry import new_fs_data_store
1219

1320
CRS_WGS84 = pyproj.crs.CRS(4326)
1421
CRS_CRS84 = pyproj.crs.CRS.from_string("urn:ogc:def:crs:OGC:1.3:CRS84")
@@ -192,3 +199,76 @@ def _gen_2d(self):
192199
self.assertEqual(('y', 'x'), lon.dims)
193200
self.assertEqual(('y', 'x'), lat.dims)
194201
return noise, crs, lon, lat
202+
203+
204+
class AddSpatialRefTest(S3Test):
205+
206+
def test_add_spatial_ref(self):
207+
original_dataset = new_cube(x_name='x',
208+
y_name='y',
209+
x_start=0,
210+
y_start=0,
211+
x_res=10,
212+
y_res=10,
213+
x_units='metres',
214+
y_units='metres',
215+
drop_bounds=True,
216+
width=100,
217+
height=100,
218+
variables=dict(A=1.3, B=8.3))
219+
220+
root = 'eurodatacube-test/xcube-eea'
221+
data_id = 'test.zarr'
222+
crs_var_name = 'spatial_ref'
223+
crs = pyproj.CRS.from_string("EPSG:3035")
224+
225+
storage_options = dict(
226+
anon=False,
227+
client_kwargs=dict(
228+
endpoint_url=MOTO_SERVER_ENDPOINT_URL,
229+
)
230+
)
231+
232+
fs: fsspec.AbstractFileSystem = fsspec.filesystem('s3',
233+
**storage_options)
234+
if fs.isdir(root):
235+
fs.rm(root, recursive=True)
236+
fs.mkdirs(root, exist_ok=True)
237+
238+
data_store = new_fs_data_store('s3',
239+
root=root,
240+
storage_options=storage_options)
241+
242+
data_store.write_data(original_dataset, data_id=data_id)
243+
opened_dataset = data_store.open_data(data_id)
244+
self.assertEqual({'A', 'B', 'x', 'y', 'time'},
245+
set(opened_dataset.variables))
246+
247+
with self.assertRaises(ValueError) as cm:
248+
GridMapping.from_dataset(opened_dataset)
249+
self.assertEqual(
250+
('cannot find any grid mapping in dataset',),
251+
cm.exception.args
252+
)
253+
254+
path = f"{root}/{data_id}"
255+
group_store = fs.get_mapper(path, create=True)
256+
257+
add_spatial_ref(group_store,
258+
crs,
259+
crs_var_name=crs_var_name)
260+
261+
self.assertTrue(fs.exists(f"{path}/{crs_var_name}"))
262+
self.assertTrue(fs.exists(f"{path}/{crs_var_name}/.zarray"))
263+
self.assertTrue(fs.exists(f"{path}/{crs_var_name}/.zattrs"))
264+
265+
opened_dataset = data_store.open_data(data_id)
266+
self.assertEqual({'A', 'B', 'x', 'y', 'time', 'spatial_ref'},
267+
set(opened_dataset.variables))
268+
self.assertEqual(crs_var_name,
269+
opened_dataset.A.attrs.get('grid_mapping'))
270+
self.assertEqual(crs_var_name,
271+
opened_dataset.B.attrs.get('grid_mapping'))
272+
273+
gm = GridMapping.from_dataset(opened_dataset)
274+
self.assertIn("LAEA Europe", gm.crs.srs)

xcube/core/gridmapping/cfconv.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222
import warnings
2323
from typing import Optional, Dict, Any, Hashable, Union, Set, List, Tuple
2424

25+
import numpy as np
2526
import pyproj
2627
import xarray as xr
28+
import zarr
29+
import zarr.convenience
2730

2831
from xcube.core.schema import get_dataset_chunks
2932

@@ -285,3 +288,38 @@ def _find_dataset_tile_size(dataset: xr.Dataset,
285288
if tile_width is not None and tile_height is not None:
286289
return tile_width, tile_height
287290
return None
291+
292+
293+
def add_spatial_ref(group_store: zarr.convenience.StoreLike,
294+
crs: pyproj.CRS,
295+
crs_var_name: Optional[str] = 'spatial_ref',
296+
xy_dim_names: Optional[Tuple[str, str]] = None):
297+
"""
298+
Helper function that allows adding a spatial reference to an
299+
existing Zarr dataset.
300+
301+
:param group_store: The dataset's existing Zarr store or path.
302+
:param crs: The coordinate reference system.
303+
:param crs_var_name: The name of the variable that will hold the
304+
spatial reference. Defaults to "spatial_ref".
305+
:param xy_dim_names: The names of the x and y dimensions.
306+
Defaults to ("x", "y").
307+
"""
308+
spatial_attrs = crs.to_cf()
309+
spatial_attrs['_ARRAY_DIMENSIONS'] = [] # Required by xarray
310+
group = zarr.open(group_store, mode='r+')
311+
spatial_ref = group.array(crs_var_name,
312+
0,
313+
shape=(),
314+
dtype=np.uint8,
315+
fill_value=0)
316+
spatial_ref.attrs.update(**spatial_attrs)
317+
318+
yx_dim_names = list(reversed(xy_dim_names or ('x', 'y')))
319+
for item_name, item in group.items():
320+
if item_name != crs_var_name:
321+
dims = item.attrs.get('_ARRAY_DIMENSIONS')
322+
if dims and len(dims) >= 2 and dims[-2:] == yx_dim_names:
323+
item.attrs['grid_mapping'] = crs_var_name
324+
325+
zarr.convenience.consolidate_metadata(group_store)

0 commit comments

Comments
 (0)