Skip to content

Commit c607b20

Browse files
Adding ocean basin in the Metadata (#732)
#### What was done - Reorganization of what's in `NetCDFToIodaConverter.h`: Moved structs and functions to `util.h` as an attempt to make the code slightly cleaner - Added a simple OceanMask struct that reads in regional ocean masks from [reccap2 ocean mask](https://reccap2-ocean.github.io/regions/). The struct has a method to return the integer mask at a specific location. - Fixed a bug in `trim`, related to the optional float metadata - implementation of the new feature in the RADS converter #### Notes It's currently not part of the `CI`, something to think about in the future. We could add `RECCAP2_region_masks_all_v20221025.nc` to the tarball or stage it on hpc somewhere ...
1 parent edbe844 commit c607b20

8 files changed

+259
-173
lines changed

utils/obsproc/Ghrsst2Ioda.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace gdasapp {
2626
}
2727

2828
// Read netcdf file and populate iodaVars
29-
gdasapp::IodaVars providerToIodaVars(const std::string fileName) final {
29+
gdasapp::obsproc::iodavars::IodaVars providerToIodaVars(const std::string fileName) final {
3030
oops::Log::info() << "Processing files provided by GHRSST" << std::endl;
3131

3232
// Get the sst bounds from the configuration
@@ -182,7 +182,7 @@ namespace gdasapp {
182182
int nobs = sst_s.size() * sst_s[0].size();
183183

184184
// Create instance of iodaVars object
185-
gdasapp::IodaVars iodaVars(nobs, {}, {});
185+
gdasapp::obsproc::iodavars::IodaVars iodaVars(nobs, {}, {});
186186

187187
// Reference time is Jan 01 1981 00:00:00 GMT+0000
188188
iodaVars.referenceDate_ = refDate;

utils/obsproc/IcecAmsr2Ioda.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace gdasapp {
2626
}
2727

2828
// Read netcdf file and populate iodaVars
29-
gdasapp::IodaVars providerToIodaVars(const std::string fileName) final {
29+
gdasapp::obsproc::iodavars::IodaVars providerToIodaVars(const std::string fileName) final {
3030
oops::Log::info() << "Processing files provided by the AMSR2" << std::endl;
3131

3232
// Open the NetCDF file in read-only mode
@@ -40,7 +40,7 @@ namespace gdasapp {
4040
int ntimes = dimxSize * dimySize * dimTimeSize;
4141

4242
// Create instance of iodaVars object
43-
gdasapp::IodaVars iodaVars(nobs, {}, {});
43+
gdasapp::obsproc::iodavars::IodaVars iodaVars(nobs, {}, {});
4444

4545
oops::Log::debug() << "--- iodaVars.location_: " << iodaVars.location_ << std::endl;
4646

utils/obsproc/NetCDFToIodaConverter.h

+6-159
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#pragma once
22

33
#include <iostream>
4-
#include <map>
54
#include <string>
65
#include <vector>
76

@@ -17,162 +16,9 @@
1716
#include "oops/util/Logger.h"
1817
#include "oops/util/missingValues.h"
1918

20-
namespace gdasapp {
21-
namespace testutils {
22-
template <typename Derived>
23-
std::string checksum(const Eigen::ArrayBase<Derived>& arr, const std::string varname) {
24-
std::stringstream result;
25-
if (arr.size() == 0) {
26-
result << varname << " is empty" << "\n";
27-
} else {
28-
auto minElement = arr.minCoeff();
29-
auto maxElement = arr.maxCoeff();
30-
auto sumElements = arr.sum();
31-
32-
result << varname << ":" << "\n";
33-
result << " Min: " << minElement << "\n";
34-
result << " Max: " << maxElement << "\n";
35-
result << " Sum: " << sumElements;
36-
}
37-
return result.str();
38-
}
39-
} // namespace testutils
40-
41-
// A simple data structure to organize the info to provide to the ioda
42-
// writter
43-
struct IodaVars {
44-
int location_; // Number of observation per variable
45-
int nVars_; // number of obs variables, should be set to
46-
// for now in the children classes
47-
int nfMetadata_; // number of float metadata fields
48-
int niMetadata_; // number of int metadata fields
49-
50-
// Non optional metadata
51-
Eigen::ArrayXf longitude_; // geo-location_
52-
Eigen::ArrayXf latitude_; // "
53-
Eigen::Array<int64_t, Eigen::Dynamic, 1> datetime_; // Epoch date in seconds
54-
std::string referenceDate_; // Reference date for epoch time
55-
56-
// Obs info
57-
Eigen::ArrayXf obsVal_; // Observation value
58-
Eigen::ArrayXf obsError_; // " error
59-
Eigen::ArrayXi preQc_; // Quality control flag
60-
61-
// Optional metadata
62-
Eigen::ArrayXXf floatMetadata_; // Optional array of float metadata
63-
std::vector<std::string> floatMetadataName_; // String descriptor of the float metadata
64-
Eigen::ArrayXXf intMetadata_; // Optional array of integer metadata
65-
std::vector<std::string> intMetadataName_; // String descriptor of the integer metadata
66-
67-
// Optional global attributes
68-
std::map<std::string, std::string> strGlobalAttr_;
69-
70-
// Constructor
71-
explicit IodaVars(const int nobs = 0,
72-
const std::vector<std::string> fmnames = {},
73-
const std::vector<std::string> imnames = {}) :
74-
location_(nobs), nVars_(1), nfMetadata_(fmnames.size()), niMetadata_(imnames.size()),
75-
longitude_(location_), latitude_(location_), datetime_(location_),
76-
obsVal_(location_),
77-
obsError_(location_),
78-
preQc_(location_),
79-
floatMetadata_(location_, fmnames.size()),
80-
floatMetadataName_(fmnames),
81-
intMetadata_(location_, imnames.size()),
82-
intMetadataName_(imnames)
83-
{
84-
oops::Log::trace() << "IodaVars::IodaVars created." << std::endl;
85-
}
86-
87-
// Append an other instance of IodaVars
88-
void append(const IodaVars& other) {
89-
// Check if the two instances can be concatenated
90-
ASSERT(nVars_ == other.nVars_);
91-
ASSERT(nfMetadata_ == other.nfMetadata_);
92-
ASSERT(niMetadata_ == other.niMetadata_);
93-
ASSERT(floatMetadataName_ == floatMetadataName_);
94-
ASSERT(intMetadataName_ == intMetadataName_);
95-
96-
// Concatenate Eigen arrays and vectors
97-
longitude_.conservativeResize(location_ + other.location_);
98-
latitude_.conservativeResize(location_ + other.location_);
99-
datetime_.conservativeResize(location_ + other.location_);
100-
obsVal_.conservativeResize(location_ + other.location_);
101-
obsError_.conservativeResize(location_ + other.location_);
102-
preQc_.conservativeResize(location_ + other.location_);
103-
floatMetadata_.conservativeResize(location_ + other.location_, nfMetadata_);
104-
intMetadata_.conservativeResize(location_ + other.location_, niMetadata_);
105-
106-
// Copy data from the 'other' object to this object
107-
longitude_.tail(other.location_) = other.longitude_;
108-
latitude_.tail(other.location_) = other.latitude_;
109-
datetime_.tail(other.location_) = other.datetime_;
110-
obsVal_.tail(other.location_) = other.obsVal_;
111-
obsError_.tail(other.location_) = other.obsError_;
112-
preQc_.tail(other.location_) = other.preQc_;
113-
floatMetadata_.bottomRows(other.location_) = other.floatMetadata_;
114-
intMetadata_.bottomRows(other.location_) = other.intMetadata_;
115-
116-
// Update obs count
117-
location_ += other.location_;
118-
oops::Log::trace() << "IodaVars::IodaVars done appending." << std::endl;
119-
}
120-
void trim(const Eigen::Array<bool, Eigen::Dynamic, 1>& mask ) {
121-
int newlocation = mask.count();
122-
123-
IodaVars iodaVarsMasked(newlocation, floatMetadataName_, intMetadataName_);
124-
125-
// this feels downright crude, but apparently numpy-type masks are on the todo list for Eigen
126-
int j = 0;
127-
for (int i = 0; i < location_; i++) {
128-
if (mask(i)) {
129-
iodaVarsMasked.longitude_(j) = longitude_(i);
130-
iodaVarsMasked.latitude_(j) = latitude_(i);
131-
iodaVarsMasked.obsVal_(j) = obsVal_(i);
132-
iodaVarsMasked.obsError_(j) = obsError_(i);
133-
iodaVarsMasked.preQc_(j) = preQc_(i);
134-
iodaVarsMasked.datetime_(j) = datetime_(i);
135-
for (int k = 0; k < nfMetadata_; k++) {
136-
iodaVarsMasked.intMetadata_(j, k) = intMetadata_(i, k);
137-
}
138-
for (int k = 0; k < niMetadata_; k++) {
139-
iodaVarsMasked.intMetadata_(j, k) = intMetadata_(i, k);
140-
}
141-
j++;
142-
} // end if (mask(i))
143-
}
144-
145-
longitude_ = iodaVarsMasked.longitude_;
146-
latitude_ = iodaVarsMasked.latitude_;
147-
datetime_ = iodaVarsMasked.datetime_;
148-
obsVal_ = iodaVarsMasked.obsVal_;
149-
obsError_ = iodaVarsMasked.obsError_;
150-
preQc_ = iodaVarsMasked.preQc_;
151-
floatMetadata_ = iodaVarsMasked.floatMetadata_;
152-
intMetadata_ = iodaVarsMasked.intMetadata_;
153-
154-
// Update obs count
155-
location_ = iodaVarsMasked.location_;
156-
oops::Log::info() << "IodaVars::IodaVars done masking." << std::endl;
157-
}
19+
# include "util.h"
15820

159-
// Testing
160-
void testOutput() {
161-
oops::Log::test() << referenceDate_ << std::endl;
162-
oops::Log::test() <<
163-
gdasapp::testutils::checksum(obsVal_, "obsVal") << std::endl;
164-
oops::Log::test() <<
165-
gdasapp::testutils::checksum(obsError_, "obsError") << std::endl;
166-
oops::Log::test() <<
167-
gdasapp::testutils::checksum(preQc_, "preQc") << std::endl;
168-
oops::Log::test() <<
169-
gdasapp::testutils::checksum(longitude_, "longitude") << std::endl;
170-
oops::Log::test() <<
171-
gdasapp::testutils::checksum(latitude_, "latitude") << std::endl;
172-
oops::Log::test() <<
173-
gdasapp::testutils::checksum(datetime_, "datetime") << std::endl;
174-
}
175-
};
21+
namespace gdasapp {
17622

17723
// Base class for the converters
17824
class NetCDFToIodaConverter {
@@ -212,7 +58,7 @@ namespace gdasapp {
21258
ASSERT(comm_.size() <= inputFilenames_.size());
21359

21460
// Read the provider's netcdf file
215-
gdasapp::IodaVars iodaVars = providerToIodaVars(inputFilenames_[myrank]);
61+
gdasapp::obsproc::iodavars::IodaVars iodaVars = providerToIodaVars(inputFilenames_[myrank]);
21662
for (int i = myrank + comm_.size(); i < inputFilenames_.size(); i += comm_.size()) {
21763
iodaVars.append(providerToIodaVars(inputFilenames_[i]));
21864
oops::Log::info() << " appending: " << inputFilenames_[i] << std::endl;
@@ -223,7 +69,7 @@ namespace gdasapp {
22369
// Get the total number of obs across pe's
22470
int nobsAll(0);
22571
comm_.allReduce(nobs, nobsAll, eckit::mpi::sum());
226-
gdasapp::IodaVars iodaVarsAll(nobsAll,
72+
gdasapp::obsproc::iodavars::IodaVars iodaVarsAll(nobsAll,
22773
iodaVars.floatMetadataName_,
22874
iodaVars.intMetadataName_);
22975

@@ -316,7 +162,8 @@ namespace gdasapp {
316162
private:
317163
// Virtual method that reads the provider's netcdf file and store the relevant
318164
// info in a IodaVars struct
319-
virtual gdasapp::IodaVars providerToIodaVars(const std::string fileName) = 0;
165+
virtual gdasapp::obsproc::iodavars::IodaVars providerToIodaVars(
166+
const std::string fileName) = 0;
320167

321168
// Gather for eigen array
322169
template <typename T>

utils/obsproc/Rads2Ioda.h

+26-6
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace gdasapp {
2525
}
2626

2727
// Read netcdf file and populate iodaVars
28-
gdasapp::IodaVars providerToIodaVars(const std::string fileName) final {
28+
gdasapp::obsproc::iodavars::IodaVars providerToIodaVars(const std::string fileName) final {
2929
oops::Log::info() << "Processing files provided by the RADS" << std::endl;
3030

3131
// Open the NetCDF file in read-only mode
@@ -35,13 +35,13 @@ namespace gdasapp {
3535
int nobs = ncFile.getDim("time").getSize();
3636

3737
// Set the int metadata names
38-
std::vector<std::string> intMetadataNames = {"pass", "cycle", "mission"};
38+
std::vector<std::string> intMetadataNames = {"pass", "cycle", "mission", "oceanBasin"};
3939

4040
// Set the float metadata name
4141
std::vector<std::string> floatMetadataNames = {"mdt"};
4242

4343
// Create instance of iodaVars object
44-
gdasapp::IodaVars iodaVars(nobs, floatMetadataNames, intMetadataNames);
44+
gdasapp::obsproc::iodavars::IodaVars iodaVars(nobs, floatMetadataNames, intMetadataNames);
4545

4646
// Read non-optional metadata: datetime, longitude and latitude
4747
int lat[iodaVars.location_]; // NOLINT
@@ -89,8 +89,9 @@ namespace gdasapp {
8989
int cycle[iodaVars.location_]; // NOLINT
9090
ncFile.getVar("cycle").getVar(cycle);
9191

92+
// Store optional metadata, set ocean basins to -999 for now
9293
for (int i = 0; i < iodaVars.location_; i++) {
93-
iodaVars.intMetadata_.row(i) << pass[i], cycle[i], mission_index;
94+
iodaVars.intMetadata_.row(i) << pass[i], cycle[i], mission_index, -999;
9495
}
9596

9697
// Get adt_egm2008 obs values and attributes
@@ -100,7 +101,7 @@ namespace gdasapp {
100101
ncFile.getVar("adt_egm2008").getAtt("scale_factor").getValues(&scaleFactor);
101102

102103
// Read sla
103-
int sla[iodaVars.location_]; // NOLINT
104+
short sla[iodaVars.location_]; // NOLINT
104105
ncFile.getVar("sla").getVar(sla);
105106

106107
// Update non-optional Eigen arrays
@@ -112,9 +113,28 @@ namespace gdasapp {
112113
iodaVars.obsError_(i) = 0.1; // Do something for obs error
113114
iodaVars.preQc_(i) = 0;
114115
// Save MDT in optional floatMetadata
115-
iodaVars.floatMetadata_(i, 0) = iodaVars.obsVal_(i) -
116+
iodaVars.floatMetadata_.row(i) << iodaVars.obsVal_(i) -
116117
static_cast<float>(sla[i])*scaleFactor;
117118
}
119+
120+
// Basic QC
121+
Eigen::Array<bool, Eigen::Dynamic, 1> boundsCheck =
122+
(iodaVars.obsVal_ > -4.0 && iodaVars.obsVal_ < 4.0);
123+
iodaVars.trim(boundsCheck);
124+
125+
// get ocean basin masks if asked in the config
126+
obsproc::oceanmask::OceanMask* oceanMask = nullptr;
127+
if (fullConfig_.has("ocean basin")) {
128+
std::string fileName;
129+
fullConfig_.get("ocean basin", fileName);
130+
oceanMask = new obsproc::oceanmask::OceanMask(fileName);
131+
132+
for (int i = 0; i < iodaVars.location_; i++) {
133+
iodaVars.intMetadata_.coeffRef(i, 3) =
134+
oceanMask->getOceanMask(iodaVars.longitude_[i], iodaVars.latitude_[i]);
135+
}
136+
}
137+
118138
return iodaVars;
119139
};
120140
}; // class Rads2Ioda

utils/obsproc/Smap2Ioda.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace gdasapp {
2525
}
2626

2727
// Read netcdf file and populate iodaVars
28-
gdasapp::IodaVars providerToIodaVars(const std::string fileName) final {
28+
gdasapp::obsproc::iodavars::IodaVars providerToIodaVars(const std::string fileName) final {
2929
oops::Log::info() << "Processing files provided by SMAP" << std::endl;
3030

3131
// Open the NetCDF file in read-only mode
@@ -36,7 +36,7 @@ namespace gdasapp {
3636
int dim1 = ncFile.getDim("phony_dim_1").getSize();
3737
int nobs = dim0 * dim1;
3838

39-
gdasapp::IodaVars iodaVars(nobs, {}, {});
39+
gdasapp::obsproc::iodavars::IodaVars iodaVars(nobs, {}, {});
4040

4141
// TODO(AFE): these arrays can be done as 1D vectors, but those need proper ushorts in
4242
// the input files, at odd with the current ctests

utils/obsproc/Smos2Ioda.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace gdasapp {
2424
}
2525

2626
// Read netcdf file and populate iodaVars
27-
gdasapp::IodaVars providerToIodaVars(const std::string fileName) final {
27+
gdasapp::obsproc::iodavars::IodaVars providerToIodaVars(const std::string fileName) final {
2828
oops::Log::info() << "Processing files provided by SMOS" << std::endl;
2929

3030
// Open the NetCDF file in read-only mode
@@ -43,7 +43,7 @@ namespace gdasapp {
4343
// std::vector<std::string> floatMetadataNames = {"mdt"};
4444
std::vector<std::string> floatMetadataNames = {};
4545
// Create instance of iodaVars object
46-
gdasapp::IodaVars iodaVars(nobs, floatMetadataNames, intMetadataNames);
46+
gdasapp::obsproc::iodavars::IodaVars iodaVars(nobs, floatMetadataNames, intMetadataNames);
4747

4848
std::vector<float> lat(iodaVars.location_);
4949
ncFile.getVar("Latitude").getVar(lat.data());

0 commit comments

Comments
 (0)