mirror of https://github.com/ecmwf/eccodes.git
1291 lines
39 KiB
C++
1291 lines
39 KiB
C++
/*
|
|
* (C) Copyright 1996- ECMWF.
|
|
*
|
|
* This software is licensed under the terms of the Apache Licence Version 2.0
|
|
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
*
|
|
* In applying this licence, ECMWF does not waive the privileges and immunities
|
|
* granted to it by virtue of its status as an intergovernmental organisation nor
|
|
* does it submit to any jurisdiction.
|
|
*/
|
|
|
|
|
|
#include "mir/input/GribInput.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <numeric>
|
|
#include <ostream>
|
|
#include <sstream>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "eckit/config/Resource.h"
|
|
#include "eckit/io/Buffer.h"
|
|
#include "eckit/io/MemoryHandle.h"
|
|
#include "eckit/io/StdFile.h"
|
|
#include "eckit/serialisation/HandleStream.h"
|
|
#include "eckit/types/FloatCompare.h"
|
|
#include "eckit/types/Fraction.h"
|
|
|
|
#include "mir/config/LibMir.h"
|
|
#include "mir/data/MIRField.h"
|
|
#include "mir/grib/Config.h"
|
|
#include "mir/repres/Representation.h"
|
|
#include "mir/util/Exceptions.h"
|
|
#include "mir/util/Grib.h"
|
|
#include "mir/util/Log.h"
|
|
#include "mir/util/LongitudeDouble.h"
|
|
#include "mir/util/Mutex.h"
|
|
#include "mir/util/ValueMap.h"
|
|
#include "mir/util/Wind.h"
|
|
|
|
|
|
namespace mir::input {
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
class Condition {
|
|
public:
|
|
Condition() = default;
|
|
|
|
Condition(const Condition&) = delete;
|
|
Condition(Condition&&) = delete;
|
|
Condition& operator=(const Condition&) = delete;
|
|
Condition& operator=(Condition&&) = delete;
|
|
|
|
virtual ~Condition() = default;
|
|
virtual bool eval(grib_handle*) const = 0;
|
|
};
|
|
|
|
|
|
template <class T>
|
|
class ConditionT : public Condition {
|
|
const char* key_;
|
|
T value_;
|
|
bool eval(grib_handle* /*unused*/) const override;
|
|
|
|
public:
|
|
ConditionT(const char* key, const T& value) : key_(key), value_(value) {}
|
|
};
|
|
|
|
|
|
template <>
|
|
bool ConditionT<long>::eval(grib_handle* h) const {
|
|
long value;
|
|
ASSERT(h);
|
|
int err = codes_get_long(h, key_, &value);
|
|
|
|
if (err == CODES_NOT_FOUND) {
|
|
return false;
|
|
}
|
|
|
|
if (err != 0) {
|
|
// Log::debug() << "ConditionT<long>::eval(" << ",key=" << key_ << ") failed " << err <<
|
|
// std::endl;
|
|
GRIB_ERROR(err, key_);
|
|
}
|
|
|
|
return value_ == value;
|
|
}
|
|
|
|
|
|
template <>
|
|
bool ConditionT<double>::eval(grib_handle* h) const {
|
|
double value;
|
|
ASSERT(h);
|
|
int err = codes_get_double(h, key_, &value);
|
|
|
|
if (err == CODES_NOT_FOUND) {
|
|
return false;
|
|
}
|
|
|
|
if (err != 0) {
|
|
// Log::debug() << "ConditionT<double>::eval(" << ",key=" << key_ << ") failed " << err <<
|
|
// std::endl;
|
|
GRIB_ERROR(err, key_);
|
|
}
|
|
|
|
return value_ == value; // Want an epsilon?
|
|
}
|
|
|
|
|
|
template <>
|
|
bool ConditionT<std::string>::eval(grib_handle* h) const {
|
|
char buffer[10240];
|
|
size_t size = sizeof(buffer);
|
|
ASSERT(h);
|
|
int err = codes_get_string(h, key_, buffer, &size);
|
|
|
|
if (err == CODES_NOT_FOUND) {
|
|
return false;
|
|
}
|
|
|
|
if (err != 0) {
|
|
Log::debug() << "ConditionT<std::string>::eval(" << ",key=" << key_ << ") failed " << err << std::endl;
|
|
GRIB_ERROR(err, key_);
|
|
}
|
|
|
|
return value_ == buffer;
|
|
}
|
|
|
|
|
|
class ConditionOR : public Condition {
|
|
const Condition* left_;
|
|
const Condition* right_;
|
|
bool eval(grib_handle* h) const override { return left_->eval(h) || right_->eval(h); }
|
|
~ConditionOR() override {
|
|
delete right_;
|
|
delete left_;
|
|
}
|
|
|
|
public:
|
|
ConditionOR(const Condition* left, const Condition* right) : left_(left), right_(right) {}
|
|
|
|
ConditionOR(const ConditionOR&) = delete;
|
|
ConditionOR(ConditionOR&&) = delete;
|
|
ConditionOR& operator=(const ConditionOR&) = delete;
|
|
ConditionOR& operator=(ConditionOR&&) = delete;
|
|
};
|
|
|
|
|
|
/*
|
|
class ConditionAND : public Condition {
|
|
const Condition* left_;
|
|
const Condition* right_;
|
|
bool eval(grib_handle* h) const override { return left_->eval(h) && right_->eval(h); }
|
|
~ConditionAND() override {
|
|
delete right_;
|
|
delete left_;
|
|
}
|
|
|
|
public:
|
|
ConditionAND(const Condition* left, const Condition* right) : left_(left), right_(right) {}
|
|
|
|
ConditionAND(const ConditionAND&) = delete;
|
|
ConditionAND( ConditionAND&&) = delete;
|
|
ConditionAND& operator=(const ConditionAND&) = delete;
|
|
ConditionAND& operator=( ConditionAND&&) = delete;
|
|
};
|
|
*/
|
|
|
|
|
|
/*
|
|
class ConditionNOT : public Condition {
|
|
const Condition* c_;
|
|
bool eval(grib_handle* h) const override { return !c_->eval(h); }
|
|
~ConditionNOT() override {
|
|
delete c_;
|
|
}
|
|
|
|
public:
|
|
ConditionNOT(const Condition* c) : c_(c) {}
|
|
};
|
|
*/
|
|
|
|
|
|
void wrongly_encoded_grib(const std::string& msg) {
|
|
static bool abortIfWronglyEncodedGRIB = eckit::Resource<bool>("$MIR_ABORT_IF_WRONGLY_ENCODED_GRIB", false);
|
|
|
|
if (abortIfWronglyEncodedGRIB) {
|
|
Log::error() << msg << std::endl;
|
|
throw exception::UserError(msg);
|
|
}
|
|
|
|
Log::warning() << msg << std::endl;
|
|
}
|
|
|
|
|
|
size_t fix_pl_array_zeros(std::vector<long>& pl) {
|
|
wrongly_encoded_grib("GribInput: wrongly encoded pl array contains zeros");
|
|
|
|
size_t new_entries = 0;
|
|
|
|
// if a zero is found, copy the *following* non-zero value into the range "current entry -> non-zero entry"
|
|
for (auto p = pl.begin(); p != pl.end(); ++p) {
|
|
if (*p == 0) {
|
|
|
|
auto nz = std::find_if(p, pl.end(), [](long x) { return x != 0; });
|
|
if (nz != pl.end()) {
|
|
new_entries += size_t(*nz) * size_t(std::distance(p, nz));
|
|
std::fill(p, nz, *nz);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if a zero is found, copy the *previous* non-zero value into the range "non-zero entry -> current entry"
|
|
for (auto p = pl.rbegin(); p != pl.rend(); ++p) {
|
|
if (*p == 0) {
|
|
|
|
auto nz = std::find_if(p, pl.rend(), [](long x) { return x != 0; });
|
|
if (nz != pl.rend()) {
|
|
new_entries += size_t(*nz) * size_t(std::distance(p, nz));
|
|
std::fill(p, nz, *nz);
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(0 == std::count(pl.begin(), pl.end(), 0));
|
|
ASSERT(new_entries);
|
|
return new_entries;
|
|
}
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
template <class T>
|
|
static Condition* is(const char* key, const T& value) {
|
|
return new ConditionT<T>(key, value);
|
|
}
|
|
|
|
static Condition* is(const char* key, const char* value) {
|
|
return new ConditionT<std::string>(key, value);
|
|
}
|
|
|
|
/*
|
|
static Condition *_and(const Condition *left, const Condition *right) {
|
|
return new ConditionAND(left, right);
|
|
}
|
|
*/
|
|
|
|
static Condition* _or(const Condition* left, const Condition* right) {
|
|
return new ConditionOR(left, right);
|
|
}
|
|
|
|
/*
|
|
static Condition *_not(const Condition *c) {
|
|
return new ConditionNOT(c);
|
|
}
|
|
*/
|
|
|
|
|
|
static const char* get_key(const std::string& name, grib_handle* h) {
|
|
struct P {
|
|
const std::string name;
|
|
const char* key;
|
|
const std::unique_ptr<const Condition> condition;
|
|
P(const std::string _name, const char* _key, const Condition* _condition = nullptr) :
|
|
name(_name), key(_key), condition(_condition) {}
|
|
};
|
|
|
|
static const std::initializer_list<P> mappings{
|
|
{"west_east_increment", "iDirectionIncrementInDegrees_fix_for_periodic_regular_grids",
|
|
is("gridType", "regular_ll")},
|
|
{"west_east_increment", "iDirectionIncrementInDegrees"},
|
|
{"south_north_increment", "jDirectionIncrementInDegrees"},
|
|
|
|
{"west", "longitudeOfFirstGridPointInDegrees"},
|
|
{"east", "longitudeOfLastGridPointInDegrees_fix_for_global_reduced_grids", is("gridType", "reduced_gg")},
|
|
{"east", "longitudeOfLastGridPointInDegrees"},
|
|
|
|
{"north", "latitudeOfFirstGridPointInDegrees", is("scanningMode", 0L)},
|
|
{"south", "latitudeOfLastGridPointInDegrees", is("scanningMode", 0L)},
|
|
|
|
{"north", "latitudeOfLastGridPointInDegrees", is("jScansPositively", 1L)},
|
|
{"south", "latitudeOfFirstGridPointInDegrees", is("jScansPositively", 1L)},
|
|
{"north", "latitudeOfFirstGridPointInDegrees"},
|
|
{"south", "latitudeOfLastGridPointInDegrees"},
|
|
|
|
{"truncation", "pentagonalResolutionParameterJ"}, // Assumes triangular truncation
|
|
{"accuracy", "bitsPerValue"},
|
|
|
|
{"south_pole_latitude", "latitudeOfSouthernPoleInDegrees"},
|
|
{"south_pole_longitude", "longitudeOfSouthernPoleInDegrees"},
|
|
{"south_pole_rotation_angle", "angleOfRotationInDegrees"},
|
|
|
|
{"proj", "projTargetString"},
|
|
{"projSource", "projSourceString"},
|
|
|
|
// This will be just called for has()
|
|
{
|
|
"gridded",
|
|
"Nx",
|
|
_or(_or(_or(is("gridType", "polar_stereographic"), is("gridType", "lambert_azimuthal_equal_area")),
|
|
is("gridType", "lambert")),
|
|
is("gridType", "space_view")),
|
|
},
|
|
{
|
|
"gridded",
|
|
"Ni",
|
|
is("gridType", "triangular_grid"),
|
|
},
|
|
{
|
|
"gridded",
|
|
"numberOfGridInReference" /*just a dummy*/,
|
|
is("gridType", "unstructured_grid"),
|
|
},
|
|
{
|
|
"gridded",
|
|
"Nside" /*just a dummy*/,
|
|
is("gridType", "healpix"),
|
|
},
|
|
{"gridded", "numberOfPointsAlongAMeridian"}, // Is that always true?
|
|
{"gridded_regular_ll", "Ni", _or(is("gridType", "regular_ll"), is("gridType", "rotated_ll"))},
|
|
|
|
{"grid", "gridName",
|
|
_or(_or(_or(_or(_or(is("gridType", "regular_gg"), is("gridType", "reduced_gg")), is("gridType", "rotated_gg")),
|
|
is("gridType", "reduced_rotated_gg")),
|
|
is("gridType", "unstructured_grid")),
|
|
is("gridType", "healpix"))},
|
|
|
|
{"spectral", "pentagonalResolutionParameterJ"},
|
|
|
|
{"uid", "uuidOfHGrid", is("gridType", "unstructured_grid")},
|
|
|
|
/// FIXME: Find something that does no clash
|
|
{"reduced", "numberOfParallelsBetweenAPoleAndTheEquator", is("isOctahedral", 0L)},
|
|
{"regular", "N", is("gridType", "regular_gg")},
|
|
{"octahedral", "numberOfParallelsBetweenAPoleAndTheEquator", is("isOctahedral", 1L)},
|
|
|
|
/// TODO: is that a good idea?
|
|
{"param", "paramId"},
|
|
{"statistics", ""}, // (avoid ecCodes error "statistics: Function not yet implemented")
|
|
};
|
|
|
|
for (const auto& m : mappings) {
|
|
if (name == m.name) {
|
|
if (!m.condition || m.condition->eval(h)) {
|
|
return m.key;
|
|
}
|
|
}
|
|
}
|
|
|
|
const auto* key = name.c_str();
|
|
return key;
|
|
}
|
|
|
|
|
|
template <typename T>
|
|
struct ProcessingT {
|
|
using fun_t = std::function<bool(grib_handle*, T&)>;
|
|
fun_t fun_;
|
|
ProcessingT(fun_t&& fun) : fun_(fun) {}
|
|
~ProcessingT() = default;
|
|
ProcessingT(const ProcessingT&) = delete;
|
|
ProcessingT(ProcessingT&&) = delete;
|
|
void operator=(const ProcessingT&) = delete;
|
|
void operator=(ProcessingT&&) = delete;
|
|
bool eval(grib_handle* h, T& v) const { return fun_(h, v); }
|
|
};
|
|
|
|
|
|
static ProcessingT<long>* is_wind_component_uv() {
|
|
return new ProcessingT<long>([](grib_handle* h, long& value) {
|
|
long paramId = 0;
|
|
GRIB_CALL(codes_get_long(h, "paramId", ¶mId));
|
|
static const util::Wind::Defaults def;
|
|
long ind = paramId % 1000;
|
|
value = (ind == def.u ? 1 : ind == def.v ? 2 : 0);
|
|
return value;
|
|
});
|
|
}
|
|
|
|
|
|
static ProcessingT<long>* is_wind_component_vod() {
|
|
return new ProcessingT<long>([](grib_handle* h, long& value) {
|
|
long paramId = 0;
|
|
GRIB_CALL(codes_get_long(h, "paramId", ¶mId));
|
|
static const util::Wind::Defaults def;
|
|
long ind = paramId % 1000;
|
|
value = (ind == def.vo ? 1 : ind == def.d ? 2 : 0);
|
|
return value;
|
|
});
|
|
}
|
|
|
|
|
|
static ProcessingT<double>* angular_precision() {
|
|
return new ProcessingT<double>([](grib_handle* h, double& value) {
|
|
auto well_defined = [](grib_handle* h, const char* key) -> bool {
|
|
long dummy = 0;
|
|
int err = 0;
|
|
return (codes_is_defined(h, key) != 0) && (codes_is_missing(h, key, &err) == 0) && (err == CODES_SUCCESS) &&
|
|
(codes_get_long(h, key, &dummy) == CODES_SUCCESS) && (dummy != 0);
|
|
};
|
|
|
|
if (well_defined(h, "basicAngleOfTheInitialProductionDomain") && well_defined(h, "subdivisionsOfBasicAngle")) {
|
|
value = 0.;
|
|
return true;
|
|
}
|
|
|
|
long angleSubdivisions = 0;
|
|
GRIB_CALL(codes_get_long(h, "angleSubdivisions", &angleSubdivisions));
|
|
|
|
value = angleSubdivisions > 0 ? 1. / double(angleSubdivisions) : 0.;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
|
|
static ProcessingT<double>* longitudeOfLastGridPointInDegrees_fix_for_global_reduced_grids() {
|
|
return new ProcessingT<double>([](grib_handle* h, double& Lon2) {
|
|
Lon2 = 0;
|
|
GRIB_CALL(codes_get_double(h, "longitudeOfLastGridPointInDegrees", &Lon2));
|
|
|
|
if (codes_is_defined(h, "pl") != 0) {
|
|
|
|
double Lon1 = 0;
|
|
GRIB_CALL(codes_get_double(h, "longitudeOfFirstGridPointInDegrees", &Lon1));
|
|
|
|
if (eckit::types::is_approximately_equal<double>(Lon1, 0)) {
|
|
|
|
// get pl array maximum and sum
|
|
// if sum equals values size the grid must be global
|
|
size_t plSize = 0;
|
|
GRIB_CALL(codes_get_size(h, "pl", &plSize));
|
|
ASSERT(plSize);
|
|
|
|
std::vector<long> pl(plSize, 0);
|
|
size_t plSizeAsRead = plSize;
|
|
GRIB_CALL(codes_get_long_array(h, "pl", pl.data(), &plSizeAsRead));
|
|
ASSERT(plSize == plSizeAsRead);
|
|
|
|
long plMax = 0;
|
|
long plSum = 0;
|
|
for (auto& p : pl) {
|
|
plSum += p;
|
|
if (plMax < p) {
|
|
plMax = p;
|
|
}
|
|
}
|
|
ASSERT(plMax > 0);
|
|
|
|
size_t valuesSize;
|
|
GRIB_CALL(codes_get_size(h, "values", &valuesSize));
|
|
|
|
if (size_t(plSum) == valuesSize) {
|
|
|
|
double eps = 0.;
|
|
std::unique_ptr<ProcessingT<double>> precision_in_degrees(angular_precision());
|
|
ASSERT(precision_in_degrees->eval(h, eps));
|
|
|
|
eckit::Fraction Lon2_expected(360L * (plMax - 1L), plMax);
|
|
|
|
if (!eckit::types::is_approximately_equal<double>(Lon2, Lon2_expected, eps)) {
|
|
|
|
std::ostringstream msgs;
|
|
msgs.precision(32);
|
|
msgs << "GribInput: wrongly encoded longitudeOfLastGridPointInDegrees:"
|
|
<< "\n"
|
|
"encoded: "
|
|
<< Lon2
|
|
<< "\n"
|
|
"expected: "
|
|
<< double(Lon2_expected) << " (" << Lon2_expected << " +- " << eps << ")";
|
|
|
|
wrongly_encoded_grib(msgs.str());
|
|
|
|
Lon2 = Lon2_expected;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
};
|
|
|
|
|
|
static ProcessingT<double>* iDirectionIncrementInDegrees_fix_for_periodic_regular_grids() {
|
|
return new ProcessingT<double>([](grib_handle* h, double& we) {
|
|
long iScansPositively = 0L;
|
|
GRIB_CALL(codes_get_long(h, "iScansPositively", &iScansPositively));
|
|
ASSERT(iScansPositively == 1L);
|
|
|
|
ASSERT(GRIB_CALL(codes_get_double(h, "iDirectionIncrementInDegrees", &we)));
|
|
ASSERT(we > 0.);
|
|
|
|
double Lon1 = 0.;
|
|
double Lon2 = 0.;
|
|
long Ni = 0;
|
|
GRIB_CALL(codes_get_double(h, "longitudeOfFirstGridPointInDegrees", &Lon1));
|
|
GRIB_CALL(codes_get_double(h, "longitudeOfLastGridPointInDegrees", &Lon2));
|
|
GRIB_CALL(codes_get_long(h, "Ni", &Ni));
|
|
ASSERT(Ni > 0);
|
|
|
|
Lon2 = LongitudeDouble(Lon2).normalise(Lon1).value();
|
|
ASSERT(Lon2 >= Lon1);
|
|
|
|
// angles are within +-1/2 precision, so (Lon2 - Lon1 + we) uses factor 3*1/2
|
|
double eps = 0.;
|
|
std::unique_ptr<ProcessingT<double>> precision_in_degrees(angular_precision());
|
|
ASSERT(precision_in_degrees->eval(h, eps));
|
|
eps *= 1.5;
|
|
|
|
auto Nid = double(Ni);
|
|
double globe = LongitudeDouble::GLOBE.value();
|
|
if (eckit::types::is_approximately_equal(Lon2 - Lon1 + we, globe, eps)) {
|
|
we = globe / Nid;
|
|
}
|
|
else if (!eckit::types::is_approximately_equal(Lon1 + (Nid - 1) * we, Lon2, eps)) {
|
|
|
|
// TODO refactor, not really specific to "periodic regular grids", but useful
|
|
std::ostringstream msgs;
|
|
msgs.precision(32);
|
|
msgs << "GribInput: wrongly encoded iDirectionIncrementInDegrees:"
|
|
"\n"
|
|
"encoded: "
|
|
<< we
|
|
<< "\n"
|
|
"Ni: "
|
|
<< Ni
|
|
<< "\n"
|
|
"longitudeOfFirstGridPointInDegree: "
|
|
<< Lon1
|
|
<< "\n"
|
|
"longitudeOfLastGridPointInDegrees: "
|
|
<< Lon2;
|
|
|
|
wrongly_encoded_grib(msgs.str());
|
|
}
|
|
|
|
return true;
|
|
});
|
|
};
|
|
|
|
|
|
static ProcessingT<std::vector<double>>* vector_double(std::initializer_list<std::string> keys) {
|
|
const std::vector<std::string> keys_(keys);
|
|
return new ProcessingT<std::vector<double>>([=](grib_handle* h, std::vector<double>& values) {
|
|
ASSERT(keys.size());
|
|
|
|
values.assign(keys_.size(), 0);
|
|
size_t i = 0;
|
|
for (const auto& key : keys_) {
|
|
if (codes_is_defined(h, key.c_str()) == 0) {
|
|
return false;
|
|
}
|
|
GRIB_CALL(codes_get_double(h, key.c_str(), &values[i++]));
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
|
|
static ProcessingT<std::string>* packing() {
|
|
return new ProcessingT<std::string>([](grib_handle* h, std::string& value) {
|
|
auto get = [](grib_handle* h, const char* key) -> std::string {
|
|
if (codes_is_defined(h, key) != 0) {
|
|
char buffer[64];
|
|
size_t size = sizeof(buffer);
|
|
|
|
GRIB_CALL(codes_get_string(h, key, buffer, &size));
|
|
ASSERT(size < sizeof(buffer) - 1);
|
|
|
|
if (::strcmp(buffer, "MISSING") != 0) {
|
|
return buffer;
|
|
}
|
|
}
|
|
return "";
|
|
};
|
|
|
|
auto packingType = get(h, "packingType");
|
|
for (auto& prefix : std::vector<std::string>{"grid_", "spectral_"}) {
|
|
if (packingType.find(prefix) == 0) {
|
|
value = packingType.substr(prefix.size());
|
|
std::replace(value.begin(), value.end(), '_', '-');
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
|
|
template <class T>
|
|
struct ConditionedProcessingT {
|
|
const std::string name;
|
|
const std::unique_ptr<const T> processing;
|
|
const std::unique_ptr<const Condition> condition;
|
|
ConditionedProcessingT(const std::string& _name, const T* _processing, const Condition* _condition = nullptr) :
|
|
name(_name), processing(_processing), condition(_condition) {}
|
|
};
|
|
|
|
|
|
template <class T>
|
|
using ProcessingList = std::initializer_list<ConditionedProcessingT<ProcessingT<T>>>;
|
|
|
|
|
|
template <typename T>
|
|
static bool get_value(const std::string& name, grib_handle* h, T& value, const ProcessingList<T>& process) {
|
|
for (auto& p : process) {
|
|
if (name == p.name) {
|
|
if (!p.condition || p.condition->eval(h)) {
|
|
ASSERT(p.processing);
|
|
return p.processing->eval(h, value);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
GribInput::GribInput() : cache_(*this), grib_(nullptr) {}
|
|
|
|
|
|
GribInput::~GribInput() {
|
|
handle(nullptr); // Will delete handle
|
|
}
|
|
|
|
|
|
const param::MIRParametrisation& GribInput::parametrisation(size_t which) const {
|
|
ASSERT(which == 0);
|
|
return cache_;
|
|
}
|
|
|
|
|
|
data::MIRField GribInput::field() const {
|
|
|
|
// Protect the grib_handle, as eccodes may update its internals
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(grib_);
|
|
|
|
long localDefinitionNumber = 0;
|
|
if (codes_get_long(grib_, "localDefinitionNumber", &localDefinitionNumber) == CODES_SUCCESS) {
|
|
if (localDefinitionNumber == 4) {
|
|
throw exception::UserError("GribInput: GRIB localDefinitionNumber=4 ('ocean') not supported");
|
|
}
|
|
}
|
|
|
|
long earthIsOblate = 0;
|
|
if (GRIB_GET(codes_get_long(grib_, "earthIsOblate", &earthIsOblate))) {
|
|
if (earthIsOblate != 0) {
|
|
wrongly_encoded_grib("GribInput: only spherical earth supported (earthIsOblate != 0)");
|
|
}
|
|
}
|
|
|
|
size_t count;
|
|
GRIB_CALL(codes_get_size(grib_, "values", &count));
|
|
|
|
size_t size = count;
|
|
MIRValuesVector values(count);
|
|
GRIB_CALL(codes_get_double_array(grib_, "values", values.data(), &size));
|
|
ASSERT(count == size);
|
|
|
|
long missingValuesPresent;
|
|
GRIB_CALL(codes_get_long(grib_, "missingValuesPresent", &missingValuesPresent));
|
|
|
|
double missingValue;
|
|
GRIB_CALL(codes_get_double(grib_, "missingValue", &missingValue));
|
|
|
|
// Ensure missingValue is unique, so values are not wrongly "missing"
|
|
long numberOfMissingValues = 0;
|
|
if (codes_get_long(grib_, "numberOfMissingValues", &numberOfMissingValues) == CODES_SUCCESS &&
|
|
numberOfMissingValues == 0) {
|
|
grib_get_unique_missing_value(values, missingValue);
|
|
}
|
|
|
|
// If grib has a 0-containing pl array, add missing values in their place
|
|
if (has("pl")) {
|
|
size_t count_pl = 0;
|
|
GRIB_CALL(codes_get_size(grib_, "pl", &count_pl));
|
|
ASSERT(count_pl);
|
|
|
|
std::vector<long> pl(count_pl, 0);
|
|
size = count_pl;
|
|
GRIB_CALL(codes_get_long_array(grib_, "pl", pl.data(), &size));
|
|
ASSERT(count_pl == size);
|
|
|
|
// NOTE: this fix ties with the method get(const std::string &name, std::vector<long> &value)
|
|
if (std::find(pl.rbegin(), pl.rend(), 0) != pl.rend()) {
|
|
|
|
// if there are no missing values yet, set them
|
|
if (missingValuesPresent == 0) {
|
|
Log::debug() << "GribInput: introducing missing values (setting bitmap)" << std::endl;
|
|
missingValuesPresent = 1;
|
|
grib_get_unique_missing_value(values, missingValue);
|
|
}
|
|
|
|
// pl array: insert entries in place of zeros
|
|
std::vector<long> pl_fixed = pl;
|
|
size_t new_values = fix_pl_array_zeros(pl_fixed);
|
|
ASSERT(new_values > 0);
|
|
|
|
// values array: copy values row by row, and when a fixed (0) entry is found, insert missing values
|
|
Log::debug() << "GribInput: correcting values array with " << new_values << " new missing values"
|
|
<< std::endl;
|
|
|
|
MIRValuesVector values_extended;
|
|
values_extended.reserve(count + new_values);
|
|
|
|
ASSERT(pl.size() == pl_fixed.size());
|
|
size_t i = 0;
|
|
for (auto p1 = pl.begin(), p2 = pl_fixed.begin(); p1 != pl.end(); ++p1, ++p2) {
|
|
if (*p1 == 0) {
|
|
ASSERT(*p2 > 0);
|
|
auto Ni = size_t(*p2);
|
|
values_extended.insert(values_extended.end(), Ni, missingValue);
|
|
}
|
|
else {
|
|
auto Ni = size_t(*p1);
|
|
ASSERT(i + Ni <= count);
|
|
|
|
values_extended.insert(values_extended.end(), &values[i], &values[i + Ni]);
|
|
i += Ni;
|
|
}
|
|
}
|
|
|
|
// set the new (extended) values vector, confirm it is compatible with a returned pl array
|
|
ASSERT(values.size() + new_values == values_extended.size());
|
|
values.swap(values_extended);
|
|
|
|
ASSERT(get("pl", pl));
|
|
size_t pl_sum = size_t(std::accumulate(pl.begin(), pl.end(), 0L));
|
|
ASSERT(pl_sum == values.size());
|
|
}
|
|
}
|
|
|
|
data::MIRField field(cache_, missingValuesPresent != 0, missingValue);
|
|
|
|
long scanningMode = 0;
|
|
if (codes_get_long(grib_, "scanningMode", &scanningMode) == CODES_SUCCESS && scanningMode != 0) {
|
|
field.representation()->reorder(scanningMode, values);
|
|
}
|
|
|
|
field.update(values, 0);
|
|
field.validate();
|
|
|
|
return field;
|
|
}
|
|
|
|
|
|
grib_handle* GribInput::gribHandle(size_t which) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(which == 0);
|
|
return grib_;
|
|
}
|
|
|
|
|
|
bool GribInput::has(const std::string& name) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(grib_);
|
|
const auto* key = get_key(name, grib_);
|
|
|
|
ASSERT(key != nullptr);
|
|
if (std::strlen(key) == 0) {
|
|
return false;
|
|
}
|
|
|
|
bool ok = codes_is_defined(grib_, key) != 0;
|
|
|
|
// Log::debug() << "GribInput::has(" << name << ",key=" << key << ") " << (ok ? "yes" : "no") <<
|
|
// std::endl;
|
|
return ok;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& name, bool& value) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(grib_);
|
|
const auto* key = get_key(name, grib_);
|
|
|
|
ASSERT(key != nullptr);
|
|
if (std::strlen(key) == 0) {
|
|
return false;
|
|
}
|
|
|
|
// NOTE: They key has to return a non-zero value
|
|
// FIXME: make sure that 'temp' is not set if CODES_MISSING_LONG
|
|
long temp = CODES_MISSING_LONG;
|
|
int err = codes_get_long(grib_, key, &temp);
|
|
if (err == CODES_NOT_FOUND || codes_is_missing(grib_, key, &err) != 0) {
|
|
return FieldParametrisation::get(name, value);
|
|
}
|
|
|
|
if (err != 0) {
|
|
// Log::debug() << "codes_get_bool(" << name << ",key=" << key << ") failed " << err <<
|
|
// std::endl;
|
|
GRIB_ERROR(err, key);
|
|
}
|
|
|
|
value = temp != 0;
|
|
|
|
// Log::debug() << "codes_get_bool(" << name << ",key=" << key << ") " << value << std::endl;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& name, int& value) const {
|
|
long v;
|
|
if (get(name, v)) {
|
|
ASSERT(long(int(v)) == v);
|
|
value = int(v);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& name, long& value) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(grib_);
|
|
const std::string key = get_key(name, grib_);
|
|
if (key.empty()) {
|
|
return false;
|
|
}
|
|
|
|
std::string packing;
|
|
if (key == "bitsPerValue" && get("packing", packing) && packing == "ieee") {
|
|
// GRIB2 Section 5 Code Table 7
|
|
// NOTE:
|
|
// - has to be done here as GRIBs packingType=grid_ieee ignores bitsPerValue (usually 0?)
|
|
// - packingType=grid_ieee has "precision", but "spectral_ieee" doesn't
|
|
long precision = 0;
|
|
codes_get_long(grib_, "precision", &precision);
|
|
value = precision == 1 ? 32 : precision == 2 ? 64 : precision == 3 ? 128 : 0;
|
|
return value != 0;
|
|
}
|
|
|
|
// FIXME: make sure that 'value' is not set if CODES_MISSING_LONG
|
|
int err = codes_get_long(grib_, key.c_str(), &value);
|
|
if (err == CODES_NOT_FOUND || codes_is_missing(grib_, key.c_str(), &err) != 0) {
|
|
static const ProcessingList<long> process{
|
|
{"is_wind_component_uv", is_wind_component_uv()},
|
|
{"is_wind_component_vod", is_wind_component_vod()},
|
|
};
|
|
|
|
return get_value(key, grib_, value, process) || FieldParametrisation::get(name, value);
|
|
}
|
|
|
|
if (err != 0) {
|
|
Log::debug() << "codes_get_long(" << name << ",key=" << key << ") failed " << err << std::endl;
|
|
GRIB_ERROR(err, key.c_str());
|
|
}
|
|
|
|
// Log::debug() << "codes_get_long(" << name << ",key=" << key << ") " << value << std::endl;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& name, float& value) const {
|
|
double v;
|
|
if (get(name, v)) {
|
|
value = float(v);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& name, double& value) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(grib_);
|
|
|
|
ASSERT(name != "grid");
|
|
|
|
const auto* key = get_key(name, grib_);
|
|
|
|
ASSERT(key != nullptr);
|
|
if (std::strlen(key) == 0) {
|
|
return false;
|
|
}
|
|
|
|
// FIXME: make sure that 'value' is not set if CODES_MISSING_DOUBLE
|
|
int err = codes_get_double(grib_, key, &value);
|
|
if (err == CODES_NOT_FOUND || codes_is_missing(grib_, key, &err) != 0) {
|
|
static const ProcessingList<double> process{
|
|
{"angular_precision", angular_precision()},
|
|
{"longitudeOfLastGridPointInDegrees_fix_for_global_reduced_grids",
|
|
longitudeOfLastGridPointInDegrees_fix_for_global_reduced_grids()},
|
|
{"iDirectionIncrementInDegrees_fix_for_periodic_regular_grids",
|
|
iDirectionIncrementInDegrees_fix_for_periodic_regular_grids()},
|
|
};
|
|
|
|
return get_value(key, grib_, value, process) || FieldParametrisation::get(name, value);
|
|
}
|
|
|
|
if (err != 0) {
|
|
// Log::debug() << "codes_get_double(" << name << ",key=" << key << ") failed " << err <<
|
|
// std::endl;
|
|
GRIB_ERROR(err, key);
|
|
}
|
|
|
|
// Log::debug() << "codes_get_double(" << name << ",key=" << key << ") " << value << std::endl;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& /*name*/, std::vector<int>& /*value*/) const {
|
|
NOTIMP;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& name, std::vector<long>& value) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(grib_);
|
|
const auto* key = get_key(name, grib_);
|
|
|
|
ASSERT(key != nullptr);
|
|
if (std::strlen(key) == 0) {
|
|
return false;
|
|
}
|
|
|
|
size_t count = 0;
|
|
int err = codes_get_size(grib_, key, &count);
|
|
|
|
if (err == CODES_NOT_FOUND) {
|
|
return FieldParametrisation::get(name, value);
|
|
}
|
|
|
|
if (err != 0) {
|
|
Log::debug() << "codes_get_long_array(" << name << ",key=" << key << ") failed " << err << " count=" << count
|
|
<< std::endl;
|
|
GRIB_ERROR(err, key);
|
|
}
|
|
|
|
size_t size = count;
|
|
|
|
value.resize(count);
|
|
|
|
GRIB_CALL(codes_get_long_array(grib_, key, value.data(), &size));
|
|
ASSERT(count == size);
|
|
|
|
ASSERT(!value.empty());
|
|
|
|
// Log::debug() << "codes_get_long_array(" << name << ",key=" << key << ") size=" << value.size() <<
|
|
// std::endl;
|
|
if (name == "pl") {
|
|
|
|
// try locating a zero in the pl array, in which case adjust it before continuing
|
|
// NOTE: reverse search is more suitable for latest global reduced_ll grids
|
|
if (std::find(value.rbegin(), value.rend(), 0) != value.rend()) {
|
|
fix_pl_array_zeros(value);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& name, std::vector<float>& value) const {
|
|
std::vector<double> v;
|
|
if (get(name, v)) {
|
|
value.clear();
|
|
value.reserve(v.size());
|
|
for (const double& l : v) {
|
|
ASSERT(l >= 0);
|
|
value.push_back(float(l));
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& name, std::string& value) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(grib_);
|
|
const auto* key = get_key(name, grib_);
|
|
|
|
ASSERT(key != nullptr);
|
|
if (std::strlen(key) == 0) {
|
|
return false;
|
|
}
|
|
|
|
char buffer[10240];
|
|
size_t size = sizeof(buffer);
|
|
int err = codes_get_string(grib_, key, buffer, &size);
|
|
|
|
if (err == CODES_NOT_FOUND) {
|
|
static const ProcessingList<std::string> process{{"packing", packing()}};
|
|
|
|
return get_value(key, grib_, value, process) || FieldParametrisation::get(name, value);
|
|
}
|
|
|
|
if (err != 0) {
|
|
// Log::debug() << "codes_get_string(" << name << ",key=" << key << ") failed " << err <<
|
|
// std::endl;
|
|
GRIB_ERROR(err, key);
|
|
}
|
|
|
|
// Log::info() << err << " " << size << " " << name << std::endl;
|
|
|
|
ASSERT(size < sizeof(buffer) - 1);
|
|
|
|
if (::strcmp(buffer, "MISSING") == 0) {
|
|
return false;
|
|
}
|
|
|
|
value = buffer;
|
|
|
|
// Log::debug() << "codes_get_string(" << name << ",key=" << key << ") " << value << std::endl;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& name, std::vector<double>& value) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(grib_);
|
|
const auto* key = get_key(name, grib_);
|
|
|
|
// NOTE: MARS client sets 'grid=vector' (deprecated) which needs to be compared against GRIB gridName
|
|
ASSERT(key != nullptr);
|
|
if (std::strlen(key) == 0 || std::strncmp(key, "gridName", 8) == 0) {
|
|
return false;
|
|
}
|
|
|
|
static const ProcessingList<std::vector<double>> process{
|
|
{"grid", vector_double({"iDirectionIncrementInDegrees", "jDirectionIncrementInDegrees"}),
|
|
_or(is("gridType", "regular_ll"), is("gridType", "rotated_ll"))},
|
|
{"grid", vector_double({"xDirectionGridLengthInMetres", "yDirectionGridLengthInMetres"}),
|
|
is("gridType", "lambert_azimuthal_equal_area")},
|
|
{"grid", vector_double({"DxInMetres", "DyInMetres"}),
|
|
_or(is("gridType", "lambert"), is("gridType", "polar_stereographic"))},
|
|
{"grid", vector_double({"DiInMetres", "DjInMetres"}), is("gridType", "mercator")},
|
|
{"grid", vector_double({"dx", "dy"}), is("gridType", "space_view")},
|
|
{"rotation", vector_double({"latitudeOfSouthernPoleInDegrees", "longitudeOfSouthernPoleInDegrees"}),
|
|
_or(_or(_or(is("gridType", "rotated_ll"), is("gridType", "rotated_gg")), is("gridType", "rotated_sh")),
|
|
is("gridType", "reduced_rotated_gg"))},
|
|
};
|
|
|
|
if (get_value(key, grib_, value, process)) {
|
|
return true;
|
|
}
|
|
|
|
size_t count = 0;
|
|
int err = codes_get_size(grib_, key, &count);
|
|
|
|
if (err == CODES_NOT_FOUND) {
|
|
return FieldParametrisation::get(name, value);
|
|
}
|
|
|
|
if (err != 0) {
|
|
// Log::debug() << "codes_get_double_array(" << name << ",key=" << key << ") failed " << err << "
|
|
// count=" << count << std::endl;
|
|
GRIB_ERROR(err, key);
|
|
}
|
|
|
|
ASSERT(count > 0);
|
|
size_t size = count;
|
|
|
|
value.resize(count);
|
|
|
|
GRIB_CALL(codes_get_double_array(grib_, key, value.data(), &size));
|
|
ASSERT(count == size);
|
|
|
|
// Log::debug() << "codes_get_double_array(" << name << ",key=" << key << ") size=" << value.size()
|
|
// << std::endl;
|
|
|
|
ASSERT(!value.empty());
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GribInput::get(const std::string& /*name*/, std::vector<std::string>& /*value*/) const {
|
|
NOTIMP;
|
|
}
|
|
|
|
|
|
bool GribInput::handle(grib_handle* h) {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
FieldParametrisation::reset();
|
|
cache_.reset();
|
|
|
|
codes_handle_delete(grib_);
|
|
grib_ = h;
|
|
|
|
if (h != nullptr) {
|
|
long value = 0;
|
|
GRIB_CALL(codes_get_long(h, "7777", &value));
|
|
|
|
// apply user-defined fixes, if any
|
|
static const grib::Config config(LibMir::configFile(LibMir::config_file::GRIB_INPUT), false);
|
|
config.find(parametrisation(0)).copyValuesTo(cache_.cache_);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void GribInput::auxilaryValues(const std::string& path, std::vector<double>& values) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
eckit::AutoStdFile f(path);
|
|
|
|
int e;
|
|
grib_handle* h = nullptr;
|
|
|
|
// We cannot use GribFileInput to read these files, because lat/lon files are also
|
|
// has grid_type = triangular_grid, and we will create a loop
|
|
|
|
try {
|
|
h = codes_grib_handle_new_from_file(nullptr, f, &e);
|
|
grib_call(e, path.c_str());
|
|
size_t count;
|
|
GRIB_CALL(codes_get_size(h, "values", &count));
|
|
|
|
size_t size = count;
|
|
values.resize(count);
|
|
GRIB_CALL(codes_get_double_array(h, "values", values.data(), &size));
|
|
ASSERT(count == size);
|
|
|
|
long missingValuesPresent;
|
|
GRIB_CALL(codes_get_long(h, "missingValuesPresent", &missingValuesPresent));
|
|
ASSERT(!missingValuesPresent);
|
|
|
|
codes_handle_delete(h);
|
|
}
|
|
catch (...) {
|
|
codes_handle_delete(h);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
|
|
void GribInput::setAuxiliaryInformation(const util::ValueMap& map) {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
for (const auto& kv : map) {
|
|
if (kv.first == "latitudes") {
|
|
Log::debug() << "Loading auxilary file '" << kv.second << "'" << std::endl;
|
|
auxilaryValues(kv.second, latitudes_);
|
|
}
|
|
else if (kv.first == "longitudes") {
|
|
Log::debug() << "Loading auxilary file '" << kv.second << "'" << std::endl;
|
|
auxilaryValues(kv.second, longitudes_);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool GribInput::only(size_t paramId) {
|
|
auto paramIdOnly = long(paramId);
|
|
|
|
while (next()) {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(grib_);
|
|
|
|
long paramIdAsLong;
|
|
GRIB_CALL(codes_get_long(grib_, "paramId", ¶mIdAsLong));
|
|
|
|
if (paramIdOnly == paramIdAsLong) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
size_t GribInput::dimensions() const {
|
|
// This will be one probably for a long time
|
|
return 1;
|
|
}
|
|
|
|
|
|
// TODO: some caching, also next() should maybe advance the auxilary files
|
|
void GribInput::latitudes(std::vector<double>& values) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
values.clear();
|
|
values.reserve(latitudes_.size());
|
|
std::copy(latitudes_.begin(), latitudes_.end(), std::back_inserter(values));
|
|
}
|
|
|
|
|
|
void GribInput::longitudes(std::vector<double>& values) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
values.clear();
|
|
values.reserve(longitudes_.size());
|
|
std::copy(longitudes_.begin(), longitudes_.end(), std::back_inserter(values));
|
|
}
|
|
|
|
|
|
void GribInput::marsRequest(std::ostream& out) const {
|
|
util::lock_guard<util::recursive_mutex> lock(mutex_);
|
|
|
|
ASSERT(grib_);
|
|
|
|
static std::string gribToRequestNamespace = eckit::Resource<std::string>("gribToRequestNamespace", "mars");
|
|
|
|
auto* keys = codes_keys_iterator_new(grib_, CODES_KEYS_ITERATOR_ALL_KEYS, gribToRequestNamespace.c_str());
|
|
ASSERT(keys);
|
|
|
|
try {
|
|
const char* sep = "";
|
|
while (codes_keys_iterator_next(keys) != 0) {
|
|
|
|
char value[1024];
|
|
size_t size = sizeof(value);
|
|
out << sep << codes_keys_iterator_get_name(keys);
|
|
GRIB_CALL(codes_keys_iterator_get_string(keys, value, &size));
|
|
out << "=" << value;
|
|
sep = ",";
|
|
}
|
|
codes_keys_iterator_delete(keys);
|
|
keys = nullptr;
|
|
|
|
size_t size = 0;
|
|
int err = codes_get_size(grib_, "freeFormData", &size);
|
|
|
|
if (err == 0) {
|
|
eckit::Buffer buffer(size);
|
|
char* b = buffer;
|
|
|
|
GRIB_CALL(codes_get_bytes(grib_, "freeFormData", (unsigned char*)b, &size));
|
|
ASSERT(size == buffer.size());
|
|
|
|
eckit::MemoryHandle h(buffer);
|
|
eckit::HandleStream in(h);
|
|
int n;
|
|
in >> n; // Number of requests
|
|
ASSERT(n == 1);
|
|
std::string verb;
|
|
in >> verb;
|
|
|
|
in >> n;
|
|
for (int i = 0; i < n; i++) {
|
|
std::string param;
|
|
in >> param;
|
|
out << sep << param;
|
|
sep = ",";
|
|
const char* slash = "=";
|
|
int m;
|
|
in >> m;
|
|
for (int j = 0; j < m; j++) {
|
|
std::string value;
|
|
in >> value;
|
|
out << slash << value;
|
|
slash = "/";
|
|
}
|
|
}
|
|
|
|
NOTIMP;
|
|
}
|
|
|
|
if (err != CODES_NOT_FOUND) {
|
|
grib_call(err, "freeFormData");
|
|
}
|
|
}
|
|
catch (...) {
|
|
if (keys != nullptr) {
|
|
codes_keys_iterator_delete(keys);
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
|
|
} // namespace mir::input
|