From e152a093524c86fb5c71a5e8c3f965999a7af569 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 26 Feb 2024 11:25:20 +0000 Subject: [PATCH 1/4] New example field: 11 --- Changelog.rst | 2 + cfdm/examplefield.py | 177 +++++++++++++++++++++++++++++++++++- cfdm/test/test_functions.py | 2 +- 3 files changed, 179 insertions(+), 2 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index f028a514f..4a49b20d6 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -5,6 +5,8 @@ Version 1.11.1.0 * New keyword parameter to `cfdm.Field.insert_dimension`: ``constructs`` (https://github.com/NCAS-CMS/cfdm/issues/287) +* New example field `11`: discrete sampling geometry trajectory + features (https://github.com/NCAS-CMS/cfdm/issues/2??) ---- diff --git a/cfdm/examplefield.py b/cfdm/examplefield.py index da6faf07a..2f88983b7 100644 --- a/cfdm/examplefield.py +++ b/cfdm/examplefield.py @@ -51,6 +51,9 @@ def example_field(n, _implementation=_implementation): ``9`` A UGRID mesh topology of edge cells. ``10`` A UGRID mesh topology of point cells. + + ``11`` Discrete sampling geometry (DSG) "trajectory" + features. ====== ================================================== See the examples for details. @@ -201,6 +204,27 @@ def example_field(n, _implementation=_implementation): Domain Topology : cell:face(ncdim%nMesh2_face(3), 4) = [[2, ..., --]] Cell connects : connectivity:edge(ncdim%nMesh2_face(3), 5) = [[0, ..., --]] + >>> print(cfdm.example_field(10)) + Field: air_pressure (ncvar%pa) + ------------------------------ + Data : air_pressure(time(2), ncdim%nMesh2_node(7)) hPa + Cell methods : time(2): point (interval: 3600 s) + Dimension coords: time(2) = [2016-01-02 01:00:00, 2016-01-02 11:00:00] gregorian + Auxiliary coords: longitude(ncdim%nMesh2_node(7)) = [-45.0, ..., -40.0] degrees_east + : latitude(ncdim%nMesh2_node(7)) = [35.0, ..., 34.0] degrees_north + Topologies : cell:point(ncdim%nMesh2_node(7), 5) = [[0, ..., --]] + + >>> print(cfdm.example_field(11)) + Field: mole_fraction_of_ozone_in_air (ncvar%O3) + ----------------------------------------------- + Data : mole_fraction_of_ozone_in_air(cf_role=trajectory_id(1), ncdim%trajectory(4)) ppb + Auxiliary coords: time(cf_role=trajectory_id(1), ncdim%trajectory(4)) = [[2024-02-26 09:01:00, ..., 2024-02-26 09:04:00]] standard + : altitude(cf_role=trajectory_id(1), ncdim%trajectory(4)) = [[2577.0, ..., 2563.0]] m + : air_pressure(cf_role=trajectory_id(1), ncdim%trajectory(4)) = [[751.0, ..., 780.0]] hPa + : latitude(cf_role=trajectory_id(1), ncdim%trajectory(4)) = [[52.0, ..., 52.2]] degree_north + : longitude(cf_role=trajectory_id(1), ncdim%trajectory(4)) = [[0.0, ..., 0.31]] degree_east + : cf_role=trajectory_id(cf_role=trajectory_id(1)) = [flight1] + """ # For safety given the private second argument which we might not # document, otherwise a user gets an obscure error if they tried, say: @@ -5136,6 +5160,122 @@ def example_field(n, _implementation=_implementation): # # field data axes f.set_data_axes(("domainaxis0", "domainaxis2")) + elif n == 11: + # field: mole_fraction_of_ozone_in_air + f = Field() + f.set_properties( + { + "Conventions": "CF-1.11", + "featureType": "trajectory", + "standard_name": "mole_fraction_of_ozone_in_air", + } + ) + f.nc_set_variable("O3") + data = Data( + [[50.0, 51.0, 49.0, 53.0]], + units="ppb", + dtype="f4", + fill_value=-9999.0, + ) + f.set_data(data) + # + # netCDF global attributes + f.nc_set_global_attributes({"Conventions": None, "featureType": None}) + # + # domain_axis: ncdim%dim + c = DomainAxis() + c.set_size(1) + c.nc_set_dimension("dim") + f.set_construct(c, key="domainaxis0", copy=False) + # + # domain_axis: ncdim%trajectory + c = DomainAxis() + c.set_size(4) + c.nc_set_dimension("trajectory") + f.set_construct(c, key="domainaxis1", copy=False) + # + # auxiliary_coordinate: time + c = AuxiliaryCoordinate() + c.set_properties({"standard_name": "time", "calendar": "standard"}) + c.nc_set_variable("time") + data = Data( + [[60, 129, 180, 240]], + units="seconds since 2024-02-26 09:00:00", + calendar="standard", + dtype="f4", + ) + c.set_data(data) + f.set_construct( + c, + axes=("domainaxis0", "domainaxis1"), + key="auxiliarycoordinate0", + copy=False, + ) + # + # auxiliary_coordinate: altitude + c = AuxiliaryCoordinate() + c.set_properties({"standard_name": "altitude"}) + c.nc_set_variable("altitude") + data = Data([[2577, 2576, 2575, 2563]], units="m", dtype="f4") + c.set_data(data) + f.set_construct( + c, + axes=("domainaxis0", "domainaxis1"), + key="auxiliarycoordinate1", + copy=False, + ) + # + # auxiliary_coordinate: air_pressure + c = AuxiliaryCoordinate() + c.set_properties({"standard_name": "air_pressure"}) + c.nc_set_variable("air_pressure") + data = Data([[751, 755, 758, 780]], units="hPa", dtype="f4") + c.set_data(data) + f.set_construct( + c, + axes=("domainaxis0", "domainaxis1"), + key="auxiliarycoordinate2", + copy=False, + ) + # + # auxiliary_coordinate: latitude + c = AuxiliaryCoordinate() + c.set_properties({"standard_name": "latitude"}) + c.nc_set_variable("latitude") + data = Data([[52, 52.5, 52.6, 52.2]], units="degree_north", dtype="f8") + c.set_data(data) + f.set_construct( + c, + axes=("domainaxis0", "domainaxis1"), + key="auxiliarycoordinate3", + copy=False, + ) + # + # auxiliary_coordinate: longitude + c = AuxiliaryCoordinate() + c.set_properties({"standard_name": "longitude"}) + c.nc_set_variable("longitude") + data = Data([[0.0, 0.3, 0.2, 0.31]], units="degree_east", dtype="f8") + c.set_data(data) + f.set_construct( + c, + axes=("domainaxis0", "domainaxis1"), + key="auxiliarycoordinate4", + copy=False, + ) + # + # auxiliary_coordinate: cf_role=trajectory_id + c = AuxiliaryCoordinate() + c.set_properties({"cf_role": "trajectory_id"}) + c.nc_set_variable("campaign") + data = Data(["flight1"], dtype="U7") + c.set_data(data) + f.set_construct( + c, axes=("domainaxis0",), key="auxiliarycoordinate5", copy=False + ) + # + # field data axes + f.set_data_axes(("domainaxis0", "domainaxis1")) else: raise ValueError( "Must select an example construct with an integer " @@ -5193,6 +5333,9 @@ def example_fields(*n, _func=example_field): ``9`` A UGRID mesh topology of edge cells. ``10`` A UGRID mesh topology of point cells. + + ``11`` Discrete sampling geometry (DSG) "trajectory" + features. ====== ================================================== If no individual field constructs are selected then all @@ -5224,7 +5367,8 @@ def example_fields(*n, _func=example_field): , , , - ] + , + ] >>> cfdm.example_fields(7, 1) [, @@ -5302,6 +5446,9 @@ def example_domain(n, _func=example_field): ``9`` A UGRID mesh topology of edge cells. ``10`` A UGRID mesh topology of point cells. + + ``11`` Discrete sampling geometry (DSG) "trajectory" + features. ====== ================================================== See the examples for details. @@ -5392,5 +5539,33 @@ def example_domain(n, _func=example_field): : longitude(grid_latitude(4), grid_longitude(5)) = [[8.0648, ..., 10.9238]] degrees_east Coord references: grid_mapping_name:rotated_latitude_longitude + >>> print(example_domain(8)) + Dimension coords: time(2) = [2016-01-02 01:00:00, 2016-01-02 11:00:00] gregorian + Auxiliary coords: longitude(ncdim%nMesh2_face(3)) = [-44.0, -44.0, -42.0] degrees_east + : latitude(ncdim%nMesh2_face(3)) = [34.0, 32.0, 34.0] degrees_north + Topologies : cell:face(ncdim%nMesh2_face(3), 4) = [[2, ..., --]] + Connectivities : connectivity:edge(ncdim%nMesh2_face(3), 5) = [[0, ..., --]] + + >>> print(example_domain(9)) + Dimension coords: time(2) = [2016-01-02 01:00:00, 2016-01-02 11:00:00] gregorian + Auxiliary coords: longitude(ncdim%nMesh2_edge(9)) = [-41.5, ..., -43.0] degrees_east + : latitude(ncdim%nMesh2_edge(9)) = [34.5, ..., 32.0] degrees_north + Topologies : cell:edge(ncdim%nMesh2_edge(9), 2) = [[1, ..., 5]] + Connectivities : connectivity:node(ncdim%nMesh2_edge(9), 6) = [[0, ..., --]] + + >>> print(example_domain(10)) + Dimension coords: time(2) = [2016-01-02 01:00:00, 2016-01-02 11:00:00] gregorian + Auxiliary coords: longitude(ncdim%nMesh2_node(7)) = [-45.0, ..., -40.0] degrees_east + : latitude(ncdim%nMesh2_node(7)) = [35.0, ..., 34.0] degrees_north + Topologies : cell:point(ncdim%nMesh2_node(7), 5) = [[0, ..., --]] + + >>> print(cfdm.example_domain(11) + Auxiliary coords: time(cf_role=trajectory_id(1), ncdim%trajectory(4)) = [[2024-02-26 09:01:00, ..., 2024-02-26 09:04:00]] standard + : altitude(cf_role=trajectory_id(1), ncdim%trajectory(4)) = [[2577.0, ..., 2563.0]] m + : air_pressure(cf_role=trajectory_id(1), ncdim%trajectory(4)) = [[751.0, ..., 780.0]] hPa + : latitude(cf_role=trajectory_id(1), ncdim%trajectory(4)) = [[52.0, ..., 52.2]] degree_north + : longitude(cf_role=trajectory_id(1), ncdim%trajectory(4)) = [[0.0, ..., 0.31]] degree_east + : cf_role=trajectory_id(cf_role=trajectory_id(1)) = [flight1] + """ return _func(n).get_domain() diff --git a/cfdm/test/test_functions.py b/cfdm/test/test_functions.py index c0de26701..5758e907c 100644 --- a/cfdm/test/test_functions.py +++ b/cfdm/test/test_functions.py @@ -227,7 +227,7 @@ def test_environment(self): def test_example_field(self): """Test the `example_field` function.""" - top = 11 + top = 12 example_fields = cfdm.example_fields() self.assertEqual(len(example_fields), top) From edf84d25efa322d9c4046428eb5fb1e201c47f79 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 26 Feb 2024 11:27:29 +0000 Subject: [PATCH 2/4] New example field: 11 --- Changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index 4a49b20d6..1e32acfea 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -6,7 +6,7 @@ Version 1.11.1.0 * New keyword parameter to `cfdm.Field.insert_dimension`: ``constructs`` (https://github.com/NCAS-CMS/cfdm/issues/287) * New example field `11`: discrete sampling geometry trajectory - features (https://github.com/NCAS-CMS/cfdm/issues/2??) + features (https://github.com/NCAS-CMS/cfdm/issues/289) ---- From a79c709ed541b92fcfc8bafa01cf86548b0edae3 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Tue, 27 Feb 2024 15:09:42 +0000 Subject: [PATCH 3/4] re-instate units --- cfdm/examplefield.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/cfdm/examplefield.py b/cfdm/examplefield.py index 2f88983b7..0a9332e8d 100644 --- a/cfdm/examplefield.py +++ b/cfdm/examplefield.py @@ -5168,6 +5168,7 @@ def example_field(n, _implementation=_implementation): "Conventions": "CF-1.11", "featureType": "trajectory", "standard_name": "mole_fraction_of_ozone_in_air", + "units": "ppb", } ) f.nc_set_variable("O3") @@ -5196,7 +5197,13 @@ def example_field(n, _implementation=_implementation): # # auxiliary_coordinate: time c = AuxiliaryCoordinate() - c.set_properties({"standard_name": "time", "calendar": "standard"}) + c.set_properties( + { + "standard_name": "time", + "calendar": "standard", + "units": "seconds since 2024-02-26 09:00:00", + } + ) c.nc_set_variable("time") data = Data( [[60, 129, 180, 240]], @@ -5214,7 +5221,7 @@ def example_field(n, _implementation=_implementation): # # auxiliary_coordinate: altitude c = AuxiliaryCoordinate() - c.set_properties({"standard_name": "altitude"}) + c.set_properties({"standard_name": "altitude", "units": "m"}) c.nc_set_variable("altitude") data = Data([[2577, 2576, 2575, 2563]], units="m", dtype="f4") c.set_data(data) @@ -5227,7 +5234,7 @@ def example_field(n, _implementation=_implementation): # # auxiliary_coordinate: air_pressure c = AuxiliaryCoordinate() - c.set_properties({"standard_name": "air_pressure"}) + c.set_properties({"standard_name": "air_pressure", "units": "hPa"}) c.nc_set_variable("air_pressure") data = Data([[751, 755, 758, 780]], units="hPa", dtype="f4") c.set_data(data) @@ -5240,7 +5247,9 @@ def example_field(n, _implementation=_implementation): # # auxiliary_coordinate: latitude c = AuxiliaryCoordinate() - c.set_properties({"standard_name": "latitude"}) + c.set_properties( + {"standard_name": "latitude", "units": "degree_north"} + ) c.nc_set_variable("latitude") data = Data([[52, 52.5, 52.6, 52.2]], units="degree_north", dtype="f8") c.set_data(data) @@ -5253,7 +5262,9 @@ def example_field(n, _implementation=_implementation): # # auxiliary_coordinate: longitude c = AuxiliaryCoordinate() - c.set_properties({"standard_name": "longitude"}) + c.set_properties( + {"standard_name": "longitude", "units": "degree_east"} + ) c.nc_set_variable("longitude") data = Data([[0.0, 0.3, 0.2, 0.31]], units="degree_east", dtype="f8") c.set_data(data) From bd6433624dd6e591002f4cf34c160e5240b3d8fc Mon Sep 17 00:00:00 2001 From: David Hassell Date: Tue, 27 Feb 2024 15:17:50 +0000 Subject: [PATCH 4/4] cfdm.examplefield._n_example_fields --- cfdm/examplefield.py | 7 +++++-- cfdm/test/test_functions.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cfdm/examplefield.py b/cfdm/examplefield.py index 0a9332e8d..c7095e1d2 100644 --- a/cfdm/examplefield.py +++ b/cfdm/examplefield.py @@ -3,6 +3,9 @@ _implementation = implementation() +# The number of example fields +_n_example_fields = 12 + def example_field(n, _implementation=_implementation): """Return an example field construct. @@ -5289,8 +5292,8 @@ def example_field(n, _implementation=_implementation): f.set_data_axes(("domainaxis0", "domainaxis1")) else: raise ValueError( - "Must select an example construct with an integer " - f"argument between 0 and 8 inclusive. Got {n!r}" + "Must select an example construct with an integer argument " + f"between 0 and {_n_example_fields - 1} inclusive. Got {n!r}" ) return f diff --git a/cfdm/test/test_functions.py b/cfdm/test/test_functions.py index 5758e907c..20707901b 100644 --- a/cfdm/test/test_functions.py +++ b/cfdm/test/test_functions.py @@ -227,7 +227,7 @@ def test_environment(self): def test_example_field(self): """Test the `example_field` function.""" - top = 12 + top = cfdm.examplefield._n_example_fields example_fields = cfdm.example_fields() self.assertEqual(len(example_fields), top)