Browse Source

Redone abstractions

interpolate
Michael Uleysky 2 years ago
parent
commit
8906f25927
  1. 305
      include/NEMO.h
  2. 7
      include/ParseArgs.h
  3. 24
      include/basedata.h
  4. 180
      include/odm.h
  5. 13
      include/simple2ddata.h
  6. 63
      include/tindexes.h
  7. 118
      include/vartype.h
  8. 5
      src/ParseArgs.cpp
  9. 45
      src/actioninfo.cpp
  10. 165
      src/actiontsc.cpp
  11. 3
      src/basedata.cpp
  12. 23
      src/odm.cpp

305
include/NEMO.h

@ -1,16 +1,19 @@
#pragma once #pragma once
#include "DataAdapters/ncfilealt.h" #include "DataAdapters/ncfilealt.h"
#include "ParseArgs.h"
#include "mdatetime.h" #include "mdatetime.h"
#include "simple2ddata.h" #include "simple2ddata.h"
#include "specfunc.h" #include "specfunc.h"
#include "vartype.h" #include <memory>
#include <regex.h> #include <regex.h>
#include <set> #include <set>
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
using michlib::Ceil; using michlib::Ceil;
using michlib::errmessage;
using michlib::Floor; using michlib::Floor;
using michlib::GPL;
using michlib::int2; using michlib::int2;
using michlib::MDateTime; using michlib::MDateTime;
using michlib::NCFileA; using michlib::NCFileA;
@ -26,11 +29,19 @@ class NEMOData
TYPE_NRT6 TYPE_NRT6
}; };
struct Parameters: public BaseParameters
{
size_t xb, yb, xe, ye, layer;
MString varname;
virtual ~Parameters() override = default;
};
std::vector<NCFileA> nc; std::vector<NCFileA> nc;
size_t xb, yb, xe, ye, layer;
std::vector<real> depths; std::vector<real> depths;
std::vector<MDateTime> times; std::vector<MDateTime> times;
size_t nx; size_t nx, ny;
real lonb, latb, lone, late;
real lonstep, latstep;
Type type = TYPE_UNKNOWN; Type type = TYPE_UNKNOWN;
class EnvVar class EnvVar
@ -73,7 +84,7 @@ class NEMOData
using Data = Simple2DData; using Data = Simple2DData;
private: private:
template<class DataType> Data ReadVarRaw(const NCFileA& f, const MString& name, size_t i, bool nodepth) const template<class DataType> Data ReadVarRaw(const NCFileA& f, const MString& name, size_t i, bool nodepth, const Parameters* p) const
{ {
real unitmul = 1.0; real unitmul = 1.0;
DataType fill; DataType fill;
@ -92,12 +103,12 @@ class NEMOData
auto unit = f.A<MString>(name, "units"); auto unit = f.A<MString>(name, "units");
if(unit && unit.Get() == "m s-1") unitmul = 100.0; if(unit && unit.Get() == "m s-1") unitmul = 100.0;
Data data((xb < xe) ? (xe - xb + 1) : (nx + xe - xb + 1), ye - yb + 1, Lonb(), Latb(), 1.0 / 12.0, 1.0 / 12.0); Data data((p->xb < p->xe) ? (p->xe - p->xb + 1) : (nx + p->xe - p->xb + 1), p->ye - p->yb + 1, Lon(p->xb), Lat(p->yb), lonstep, latstep);
if(xb < xe) if(p->xb < p->xe)
{ {
auto var = nodepth ? f.V<DataType>(name, {"longitude", xb, xe - xb + 1}, {"latitude", yb, ye - yb + 1}, {"time", i, 1}) auto var = nodepth ? f.V<DataType>(name, {"longitude", p->xb, p->xe - p->xb + 1}, {"latitude", p->yb, p->ye - p->yb + 1}, {"time", i, 1})
: f.V<DataType>(name, {"longitude", xb, xe - xb + 1}, {"latitude", yb, ye - yb + 1}, {"time", i, 1}, {"depth", layer, 1}); : f.V<DataType>(name, {"longitude", p->xb, p->xe - p->xb + 1}, {"latitude", p->yb, p->ye - p->yb + 1}, {"time", i, 1}, {"depth", p->layer, 1});
if(!var) return Data(); if(!var) return Data();
if(var.DimLen(0) != data.Nx() || var.DimLen(1) != data.Ny()) return Data(); if(var.DimLen(0) != data.Nx() || var.DimLen(1) != data.Ny()) return Data();
@ -110,10 +121,10 @@ class NEMOData
} }
else else
{ {
auto var1 = nodepth ? f.V<DataType>(name, {"longitude", xb}, {"latitude", yb, ye - yb + 1}, {"time", i, 1}) auto var1 = nodepth ? f.V<DataType>(name, {"longitude", p->xb}, {"latitude", p->yb, p->ye - p->yb + 1}, {"time", i, 1})
: f.V<DataType>(name, {"longitude", xb}, {"latitude", yb, ye - yb + 1}, {"time", i, 1}, {"depth", layer, 1}); : f.V<DataType>(name, {"longitude", p->xb}, {"latitude", p->yb, p->ye - p->yb + 1}, {"time", i, 1}, {"depth", p->layer, 1});
auto var2 = nodepth ? f.V<DataType>(name, {"longitude", 0, xe + 1}, {"latitude", yb, ye - yb + 1}, {"time", i, 1}) auto var2 = nodepth ? f.V<DataType>(name, {"longitude", 0, p->xe + 1}, {"latitude", p->yb, p->ye - p->yb + 1}, {"time", i, 1})
: f.V<DataType>(name, {"longitude", 0, xe + 1}, {"latitude", yb, ye - yb + 1}, {"time", i, 1}, {"depth", layer, 1}); : f.V<DataType>(name, {"longitude", 0, p->xe + 1}, {"latitude", p->yb, p->ye - p->yb + 1}, {"time", i, 1}, {"depth", p->layer, 1});
if(!(var1 && var2)) return Data(); if(!(var1 && var2)) return Data();
if((var1.DimLen(0) + var2.DimLen(0)) != data.Nx() || var1.DimLen(1) != data.Ny() || var2.DimLen(1) != data.Ny()) return Data(); if((var1.DimLen(0) + var2.DimLen(0)) != data.Nx() || var1.DimLen(1) != data.Ny() || var2.DimLen(1) != data.Ny()) return Data();
for(size_t ix = 0; ix < var1.DimLen(0); ix++) for(size_t ix = 0; ix < var1.DimLen(0); ix++)
@ -132,12 +143,24 @@ class NEMOData
return data; return data;
} }
static MString VName2IName(const MString& vname)
{
if(vname == "temp") return "thetao";
if(vname == "sal") return "so";
if(vname == "mld") return "mlotst";
if(vname == "ssh") return "zos";
if(vname == "w") return "wo";
return "";
}
public: public:
Data ReadVar(const MString& name, size_t i) const Data Read(const BaseParameters* ip, size_t i) const
{ {
if(!isOk()) return Data(); if(!isOk()) return Data();
bool nodepth = false; bool nodepth = false;
auto p = dynamic_cast<const struct Parameters*>(ip);
MString name = VName2IName(p->varname);
for(const auto& f: nc) for(const auto& f: nc)
{ {
auto head = f.Header(); auto head = f.Header();
@ -145,88 +168,84 @@ class NEMOData
if(v.Name() == name) if(v.Name() == name)
{ {
if(v.Dimensions().size() == 3) nodepth = true; if(v.Dimensions().size() == 3) nodepth = true;
if(v.Type().Id() == NC_SHORT) return ReadVarRaw<int2>(f, name, i, nodepth); if(v.Type().Id() == NC_SHORT) return ReadVarRaw<int2>(f, name, i, nodepth, p);
if(v.Type().Id() == NC_FLOAT) return ReadVarRaw<float>(f, name, i, nodepth); if(v.Type().Id() == NC_FLOAT) return ReadVarRaw<float>(f, name, i, nodepth, p);
} }
} }
return Data(); return Data();
} }
Data ReadVar(const MString& name, const std::vector<size_t>& tindex) const bool HasVar(const MString vname) const
{ {
Averager<Data> out; MString name = VName2IName(vname);
if(tindex.size() == 0 || !isOk()) return out; if(name == "") return false;
for(size_t i = 0; i < tindex.size(); i++) for(const auto& f: nc)
{ {
Data dat = ReadVar(name, tindex[i]); auto head = f.Header();
if(!dat) return Data(); for(const auto& v: head.Variables())
out.Add(dat); if(v.Name() == name) return true;
} }
out.Div(); return false;
return out;
} }
public: public:
NEMOData() = default; NEMOData() = default;
// TODO: RetVal // TODO: RetVal
bool Open(const MString& stype, const MString& cred, const MString& proxyurl = "") MString Open(const CLArgs& args)
{ {
MString oldprefix = GPL.UsePrefix("AVISO");
MString dataset = args.contains("dataset") ? args.at("dataset") : "DT";
MString cred = GPL.ParameterSValue("COPERNICUS_USER", "");
MString proxyurl = GPL.ParameterSValue("COPERNICUS_PROXY", "");
GPL.UsePrefix(oldprefix);
nc.clear(); nc.clear();
if(proxyurl.Exist()) proxy.Activate("all_proxy", proxyurl); if(proxyurl.Exist()) proxy.Activate("all_proxy", proxyurl);
if(stype == "DT")
std::vector<MString> urls;
if(dataset == "DT")
{ {
NCFileA newnc; urls = {"https://" + cred + "@my.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy_my_0.083_P1D-m"};
newnc.Reset("https://" + cred + "@my.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy_my_0.083_P1D-m");
if(!newnc) return false;
nc.push_back(std::move(newnc));
type = TYPE_DT; type = TYPE_DT;
} }
if(stype == "NRT") else if(dataset == "NRT")
{ {
std::vector<MString> urls{"https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-cur_anfc_0.083deg_P1D-m", urls = {"https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-cur_anfc_0.083deg_P1D-m",
"https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-thetao_anfc_0.083deg_P1D-m", "https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-thetao_anfc_0.083deg_P1D-m",
"https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-so_anfc_0.083deg_P1D-m", "https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-so_anfc_0.083deg_P1D-m",
"https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy_anfc_0.083deg_P1D-m", "https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy_anfc_0.083deg_P1D-m",
"https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-wcur_anfc_0.083deg_P1D-m"}; "https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-wcur_anfc_0.083deg_P1D-m"};
for(const auto& url: urls)
{
NCFileA newnc;
newnc.Reset(url);
if(!newnc)
{
nc.clear();
return false;
}
nc.push_back(std::move(newnc));
}
type = TYPE_NRT; type = TYPE_NRT;
} }
if(stype == "NRT6") else if(dataset == "NRT6")
{ {
std::vector<MString> urls{"https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-cur_anfc_0.083deg_PT6H-i", urls = {"https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-cur_anfc_0.083deg_PT6H-i",
"https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-thetao_anfc_0.083deg_PT6H-i", "https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-thetao_anfc_0.083deg_PT6H-i",
"https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-so_anfc_0.083deg_PT6H-i"}; "https://" + cred + "@nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy-so_anfc_0.083deg_PT6H-i"};
for(const auto& url: urls) type = TYPE_NRT6;
}
else
return "Unknown dataset: " + dataset;
nc.resize(urls.size());
for(size_t i = 0; i < urls.size(); i++)
{
nc[i].Reset(urls[i]);
if(!nc[i])
{ {
NCFileA newnc; nc.clear();
newnc.Reset(url); return "Can't connect to database";
if(!newnc)
{
nc.clear();
return false;
}
nc.push_back(std::move(newnc));
} }
type = TYPE_NRT6;
} }
auto rdepths = nc[0].V<float>("depth"); auto rdepths = nc[0].V<float>("depth");
if(!rdepths) if(!rdepths)
{ {
nc.clear(); nc.clear();
return false; return "Can't read depths";
} }
depths.resize(rdepths.DimLen(0)); depths.resize(rdepths.DimLen(0));
for(size_t i = 0; i < depths.size(); i++) depths[i] = rdepths(i); for(size_t i = 0; i < depths.size(); i++) depths[i] = rdepths(i);
@ -236,21 +255,46 @@ class NEMOData
if(!(timeD || timeF)) if(!(timeD || timeF))
{ {
nc.clear(); nc.clear();
return false; return "Can't read times";
} }
MDateTime refdate("1950-01-01"); MDateTime refdate("1950-01-01");
timeD ? times.resize(timeD.DimLen(0)) : times.resize(timeF.DimLen(0)); timeD ? times.resize(timeD.DimLen(0)) : times.resize(timeF.DimLen(0));
for(size_t i = 0; i < times.size(); i++) times[i] = refdate + static_cast<time_t>(timeD ? timeD(i) : timeF(i)) * 3600; for(size_t i = 0; i < times.size(); i++) times[i] = refdate + static_cast<time_t>(timeD ? timeD(i) : timeF(i)) * 3600;
return true; auto nlon = nc[0].D("longitude");
auto nlat = nc[0].D("latitude");
if(!(nlon && nlat))
{
nc.clear();
return "Can't get number of longitudes/latitudes";
}
nx = nlon;
ny = nlat;
auto lons = nc[0].V<float>("longitude");
auto lats = nc[0].V<float>("latitude");
if(!(lons && lats))
{
nc.clear();
return "Can't get longitudes/latitudes";
}
lonb = lons(0);
latb = lats(0);
lone = lons(nx - 1);
late = lats(ny - 1);
lonstep = (lone - lonb) / (nx - 1);
latstep = (late - latb) / (ny - 1);
return "";
} }
MString Info() const MString Info() const
{ {
if(!isOk()) return "";
MString d; MString d;
for(size_t i = 0; i < NDepths(); i++) d += MString(" ") + "(" + i + " " + Depth(i) + ")"; for(size_t i = 0; i < NDepths(); i++) d += MString(" ") + "(" + i + " " + Depth(i) + ")";
std::set<VarType> vars; std::set<MString> vars;
for(const auto& f: nc) for(const auto& f: nc)
{ {
auto head = f.Header(); auto head = f.Header();
@ -268,7 +312,7 @@ class NEMOData
bool first = true; bool first = true;
for(const auto& v: vars) for(const auto& v: vars)
{ {
svars += (first ? "" : ", ") + v.ShortName(); svars += (first ? "" : ", ") + v;
first = false; first = false;
} }
} }
@ -280,70 +324,81 @@ class NEMOData
" End date: " + Time(NTimes()-1).ToString() + "\n" + " End date: " + Time(NTimes()-1).ToString() + "\n" +
" Time step: " + Timestep() + " seconds\n" + " Time step: " + Timestep() + " seconds\n" +
" Time moments: " + NTimes() + "\n" + " Time moments: " + NTimes() + "\n" +
" Region: (" + lonb + " : " + lone + ") x (" + latb + " : " + late + ")\n" +
" Grid: " + nx + "x" + ny + " (" + lonstep + " x " + latstep + ")\n" +
" Depths:" + d + "\n" + " Depths:" + d + "\n" +
" Supported variables: " + svars; " Supported variables: " + svars;
// clang-format on // clang-format on
} }
MString Dump() const std::pair<const BaseParameters*, MString> Parameters(michlib_internal::ParameterListEx& pars, const CLArgs& args) const
{ {
// clang-format off std::unique_ptr<struct Parameters> ppar{new struct Parameters};
return
"Current settings:\n" + MString() +
" Longitudes: from " + Lonb() + " (" + xb + ") to "+ Lone() + " (" + xe + ")\n" +
" Latitudes: from " + Latb() + " (" + yb + ") to "+ Late() + " (" + ye + ")\n" +
" Depth: layer " + layer + ", depth " + Depth() + " m\n";
// clang-format on
}
bool SetRegion(real lonbin, real lonein, real latb, real late, real depth) if(!args.contains("var")) return {nullptr, "Variable not specified"};
{ ppar->varname = args.at("var");
if(!isOk()) return false; if(!HasVar(ppar->varname)) return {nullptr, "Variable " + ppar->varname + " not exists in this dataset"};
real lonb = ToGeoDomainNeg(lonbin), lone = ToGeoDomainNeg(lonein); if(args.contains("layer")) ppar->layer = args.at("layer").ToInteger<size_t>();
if(!args.contains("depth") && ppar->layer >= NDepths()) return {nullptr, MString("Layer ") + ppar->layer + " is too deep!"};
real depth = args.contains("depth") ? args.at("depth").ToReal() : Depth(ppar->layer);
size_t xb_, xe_, yb_, ye_, layer_ = 0; if(!(args.contains("lonb") && args.contains("lone") && args.contains("latb") && args.contains("late"))) return {nullptr, "Region not specified (lonb, lone, latb, late)"};
yb_ = static_cast<size_t>(Floor((latb + 80.0) * 12.0)); {
ye_ = static_cast<size_t>(Ceil((late + 80.0) * 12.0)); real lon1 = ToGeoDomainNeg(args.at("lonb").ToReal());
if(ye_ > 2040) ye_ = 2040; real lon2 = ToGeoDomainNeg(args.at("lone").ToReal());
if(yb_ >= ye_) return false; real lat1 = ToGeoDomainNeg(args.at("latb").ToReal());
real lat2 = ToGeoDomainNeg(args.at("late").ToReal());
xb_ = static_cast<size_t>(Floor((lonb + 180.0) * 12.0)); ppar->yb = static_cast<size_t>(Floor((lat1 - latb) / latstep));
xe_ = static_cast<size_t>(Ceil((lone + 180.0) * 12.0)); ppar->ye = static_cast<size_t>(Ceil((lat2 - latb) / latstep));
if(xb_ == xe_) return false; if(ppar->ye > 2040) ppar->ye = 2040;
if(ppar->yb >= ppar->ye) return {nullptr, "Latb must be lesser then late"};
if(depth < 0.0 || depth > depths.back()) ppar->xb = static_cast<size_t>(Floor((lon1 - lonb) / lonstep));
layer_ = (depth < 0.0) ? 0 : (depths.size() - 1); ppar->xe = static_cast<size_t>(Ceil((lon2 - lonb) / lonstep));
else
for(size_t i = 0; i < depths.size() - 1; i++) if(ppar->xb == ppar->xe) return {nullptr, "Lonb must be not equal late"};
{
if(depth >= depths[i] && depth <= depths[i + 1]) if(depth < 0.0 || depth > depths.back())
ppar->layer = (depth < 0.0) ? 0 : (depths.size() - 1);
else
for(size_t i = 0; i < depths.size() - 1; i++)
{ {
layer_ = (depth - depths[i] <= depths[i + 1] - depth) ? i : (i + 1); if(depth >= depths[i] && depth <= depths[i + 1])
break; {
ppar->layer = (depth - depths[i] <= depths[i + 1] - depth) ? i : (i + 1);
break;
}
} }
} }
xb = xb_;
xe = xe_; pars.SetParameter("variable", ppar->varname);
yb = yb_; pars.SetParameter("depth", Depth(ppar->layer));
ye = ye_; pars.SetParameter("layer", ppar->layer);
layer = layer_; pars.SetParameter("dataset", Title());
return true; pars.SetParameter("lonb", Lon(ppar->xb));
pars.SetParameter("latb", Lat(ppar->yb));
pars.SetParameter("lone", Lon(ppar->xe));
pars.SetParameter("late", Lat(ppar->ye));
return {ppar.release(), ""};
}
MString Dump(const struct Parameters* ppar) const
{
// clang-format off
return
"Current settings:\n" + MString() +
" Longitudes: from " + Lon(ppar->xb) + " (" + ppar->xb + ") to "+ Lon(ppar->xe) + " (" + ppar->xe + ")\n" +
" Latitudes: from " + Lat(ppar->yb) + " (" + ppar->yb + ") to "+ Lat(ppar->ye) + " (" + ppar->ye + ")\n" +
" Depth: layer " + ppar->layer + ", depth " + Depth(ppar->layer) + " m\n";
// clang-format on
} }
real Lonb() const { return isOk() ? (-180.0 + xb / 12.0) : -1000.0; } real Lon(size_t ix) const { return isOk() ? (-180.0 + ix / 12.0) : -1000.0; }
real Lone() const { return isOk() ? (-180.0 + xe / 12.0) : -1000.0; } real Lat(size_t iy) const { return isOk() ? (-80.0 + iy / 12.0) : -1000.0; }
real Latb() const { return isOk() ? (-80.0 + yb / 12.0) : -1000.0; }
real Late() const { return isOk() ? (-80.0 + ye / 12.0) : -1000.0; }
size_t Xb() const { return isOk() ? xb : 0; }
size_t Xe() const { return isOk() ? xe : 0; }
size_t Yb() const { return isOk() ? yb : 0; }
size_t Ye() const { return isOk() ? ye : 0; }
size_t Nx() const { return isOk() ? ((xb < xe) ? (xe - xb + 1) : (nx + xe - xb + 1)) : 0; }
size_t Ny() const { return isOk() ? (ye - yb + 1) : 0; }
real Depth(size_t l) const { return isOk() ? depths[l] : -1000.0; } real Depth(size_t l) const { return isOk() ? depths[l] : -1000.0; }
real Depth() const { return Depth(layer); }
size_t Layer() const { return layer; }
time_t Timestep() const { return isOk() ? (times[1] - times[0]) : 0; } time_t Timestep() const { return isOk() ? (times[1] - times[0]) : 0; }
MDateTime Time(size_t i) const MDateTime Time(size_t i) const
{ {
@ -351,16 +406,12 @@ class NEMOData
return times[i]; return times[i];
} }
bool isOk() const { return nc.size() > 0; } bool isOk() const { return nc.size() > 0; }
explicit operator bool() const { return nc.size() > 0; } explicit operator bool() const { return nc.size() > 0; }
size_t NDepths() const { return depths.size(); } size_t NDepths() const { return depths.size(); }
size_t NTimes() const { return times.size(); } size_t NTimes() const { return times.size(); }
template<vartype::Vartype vt> Data Read(size_t it) const = delete;
template<vartype::Vartype vt> Data Read(const std::vector<size_t>& tindex) const = delete;
MString Title() const MString Title() const
{ {
switch(type) switch(type)
@ -374,19 +425,3 @@ class NEMOData
static real Fillval() { return Data::Fillval(); } static real Fillval() { return Data::Fillval(); }
}; };
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::U>(size_t it) const { return ReadVar("uo", it); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::V>(size_t it) const { return ReadVar("vo", it); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::TEMP>(size_t it) const { return ReadVar("thetao", it); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::SAL>(size_t it) const { return ReadVar("so", it); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::MLD>(size_t it) const { return ReadVar("mlotst", it); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::SSH>(size_t it) const { return ReadVar("zos", it); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::W>(size_t it) const { return ReadVar("wo", it); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::U>(const std::vector<size_t>& tindex) const { return ReadVar("uo", tindex); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::V>(const std::vector<size_t>& tindex) const { return ReadVar("vo", tindex); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::TEMP>(const std::vector<size_t>& tindex) const { return ReadVar("thetao", tindex); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::SAL>(const std::vector<size_t>& tindex) const { return ReadVar("so", tindex); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::MLD>(const std::vector<size_t>& tindex) const { return ReadVar("mlotst", tindex); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::SSH>(const std::vector<size_t>& tindex) const { return ReadVar("zos", tindex); }
template<> inline NEMOData::Data NEMOData::Read<vartype::Vartype::W>(const std::vector<size_t>& tindex) const { return ReadVar("wo", tindex); }

7
include/ParseArgs.h

@ -0,0 +1,7 @@
#pragma once
#include "GPL.h"
using michlib::MString;
using CLArgs = std::map<MString, MString>;
CLArgs ParseArgs(int argc, char** argv);

24
include/basedata.h

@ -1,25 +1,45 @@
#pragma once #pragma once
#include "comdefs.h" #include "comdefs.h"
#include <vector>
using michlib::real; using michlib::real;
class BaseParameters
{
public:
virtual ~BaseParameters();
};
class BaseData class BaseData
{ {
protected: protected:
static constexpr real fillval = 1.0e10; static constexpr real fillval = 1.0e10;
std::vector<real> data; std::vector<real> data;
std::vector<real> lons, lats;
BaseData() = default; BaseData(size_t n): data(n), lons(n), lats(n) {}
BaseData(size_t n): data(n) {} template<class Lon, class Lat> BaseData(size_t n, Lon genlon, Lat genlat): data(n), lons(n), lats(n)
{
for(size_t i = 0; i < n; i++)
{
lons[i] = genlon(i);
lats[i] = genlat(i);
}
};
public: public:
BaseData() = default;
const real& V(size_t i) const { return data[i]; } const real& V(size_t i) const { return data[i]; }
real& V(size_t i) { return data[i]; } real& V(size_t i) { return data[i]; }
const real& operator()(size_t i) const { return data[i]; } const real& operator()(size_t i) const { return data[i]; }
real& operator()(size_t i) { return data[i]; } real& operator()(size_t i) { return data[i]; }
real Lon(size_t i) const { return lons[i]; }
real Lat(size_t i) const { return lats[i]; }
size_t N() const { return data.size(); } size_t N() const { return data.size(); }
static real Fillval() { return fillval; } static real Fillval() { return fillval; }

180
include/odm.h

@ -1,29 +1,121 @@
#pragma once #pragma once
#include "BFileW.h" #include "BFileW.h"
#include "GPL.h"
#include "NEMO.h" #include "NEMO.h"
#include "tindexes.h" #include <utility>
#include <variant>
using michlib::BFileW; using michlib::BFileW;
using michlib::errmessage; using michlib::errmessage;
using michlib::GPL; using michlib::GPL;
using michlib::message; using michlib::message;
using michlib::SList;
using DataVariants = std::variant<NEMOData>; using DataVariants = std::variant<NEMOData>;
using CLArgs = std::map<MString, MString>;
template<class T>
concept InfoSupported = requires(T t) {
{
t.template Info()
} -> std::convertible_to<MString>;
};
template<class T>
concept ParametersSupported = requires(T t, michlib_internal::ParameterListEx& pars, const CLArgs& args) {
{
t.template Parameters(pars, args).first
} -> std::convertible_to<const BaseParameters*>;
};
template<class T>
concept ReadPSupported = requires(T t, const BaseParameters* ip, size_t i) {
{
t.template Read(ip, i)(0)
} -> std::convertible_to<real>;
};
template<class T>
concept ReadSupported = requires(T t, size_t i) {
{
t.template Read(i)(0)
} -> std::convertible_to<real>;
};
template<class T>
concept ActionTscSupported = requires(T t, michlib_internal::ParameterListEx& pars, const CLArgs& args, const BaseParameters* ip, size_t i) {
{
t.template Parameters(pars, args).first
} -> std::convertible_to<const BaseParameters*>;
{
t.template Read(ip, i)(0)
} -> std::convertible_to<real>;
};
class Data: public DataVariants class Data: public DataVariants
{ {
template<class D> static bool WriteData(BFileW& fw, const D& data) public:
using TIndex = std::vector<size_t>;
private:
TIndex GetTIndexes(const MDateTime& b, const MDateTime& e) const
{ {
for(size_t i = 0; i < data.N(); i++) TIndex out;
auto nt = NTimes();
const MDateTime& beg = (b < e) ? b : e;
const MDateTime& end = (b > e) ? b : e;
if(beg > Time(nt - 1) || end < Time(0)) return out;
size_t ib = 0, ie = nt - 1;
for(size_t i = 0; i < nt; i++)
if(Time(i) >= beg)
{
ib = i;
break;
}
for(size_t i = nt; i != 0; i--)
if(Time(i - 1) <= end)
{
ie = i - 1;
break;
}
out.resize(ie - ib + 1);
for(size_t i = 0; i < ie - ib + 1; i++) out[i] = i + ib;
return out;
}
TIndex GetTIndexes(const MString& regex)
{
{
MDateTime time;
if(time.FromString(regex)) return TIndex(1, GetTIndex(time)); // Time, not regex
if(regex == "BEGIN" || regex == "BEG" || regex == "FIRST") return TIndex(1, 0); // First time
if(regex == "END" || regex == "LAST") return TIndex(1, NTimes() - 1); // Last time
}
TIndex out;
std::unique_ptr<regex_t> regbuf(new regex_t);
if(0 != regcomp(regbuf.get(), regex.Buf(), REG_EXTENDED | REG_NOSUB)) return out;
for(size_t i = 0; i < NTimes(); i++)
{ {
fw.Write(data.Lon(i)); MString date = Time(i).ToString();
fw.Write(data.Lat(i)); if(0 != regexec(regbuf.get(), date.Buf(), 0, 0, 0)) continue;
fw.Write(data(i) == data.Fillval() ? NAN : data(i)); out.push_back(i);
} }
return true;
return out;
}
size_t GetTIndex(const MDateTime& t)
{
size_t nt = NTimes();
if(t <= Time(0)) return 0;
if(t >= Time(nt - 1)) return nt - 1;
for(size_t i = 0; i < nt - 1; i++)
if(t >= Time(i) && t <= Time(i + 1)) return (t - Time(i) <= Time(i + 1) - t) ? i : (i + 1);
return 0;
} }
public: public:
@ -31,59 +123,31 @@ class Data: public DataVariants
Data(DataVariants&& d): DataVariants(std::move(d)) {} Data(DataVariants&& d): DataVariants(std::move(d)) {}
auto Time(size_t it) const MDateTime Time(size_t it) const
{ {
return std::visit( return std::visit([it = it](const auto& arg) -> auto { return arg.Time(it); }, *this);
[it = it](const auto& arg) -> auto{ return arg.Time(it); }, *this);
} }
auto NTimes() const size_t NTimes() const
{ {
return std::visit( return std::visit([](const auto& arg) -> auto { return arg.NTimes(); }, *this);
[](const auto& arg) -> auto{ return arg.NTimes(); }, *this);
}
auto Info() const
{
return std::visit(
[](const auto& arg) -> auto{ return arg.Info(); }, *this);
} }
bool Write(BFileW& fw, VarType vt, const std::vector<size_t>& tindexes) const MString Init(const CLArgs& args)
{ {
return std::visit( MString src = args.contains("source") ? args.at("source") : "";
[&fw = fw, vt = vt, &ti = tindexes](const auto& d) -> bool if(!src.Exist()) return "No source specified";
{ if(src == "NEMO")
using T = std::decay_t<decltype(d)>; {
NEMOData data;
return std::visit( auto res = data.Open(args);
[&fw = fw, &ti = ti, &d = d](auto v) -> bool if(res.Exist()) return "Can't open source " + src + ":\n" + res;
{ *this = Data(std::move(data));
constexpr auto cvt = decltype(v)::vt; }
if constexpr(isDataSupported<T, cvt>) else
return WriteData(fw, d.template Read<cvt>(ti)); return "Unknown source: " + src;
else return "";
return false;
},
vt);
},
*this);
} }
};
CLArgs ParseArgs(int argc, char** argv);
int actioninfo(const CLArgs& args); MString ActionInfo(const CLArgs& args);
int actiontsc(const CLArgs& args); MString ActionTsc(const CLArgs& args);
};
inline NEMOData NEMOOpen(const CLArgs& args)
{
NEMOData ndata;
MString oldprefix = GPL.UsePrefix("AVISO");
MString dataset = args.contains("dataset") ? args.at("dataset") : "DT";
MString cred = GPL.ParameterSValue("COPERNICUS_USER", "");
MString proxy = GPL.ParameterSValue("COPERNICUS_PROXY", "");
GPL.UsePrefix(oldprefix);
ndata.Open(dataset, cred, proxy);
return ndata;
}

13
include/simple2ddata.h

@ -10,7 +10,18 @@ class Simple2DData: public BaseData
public: public:
Simple2DData() = default; Simple2DData() = default;
Simple2DData(size_t nx_, size_t ny_, real x0_, real y0_, real xs_, real ys_): BaseData(nx_ * ny_), x0(x0_), y0(y0_), nx(nx_), ny(ny_), xstep(xs_), ystep(ys_) {} Simple2DData(size_t nx_, size_t ny_, real x0_, real y0_, real xs_, real ys_):
BaseData(
nx_ * ny_, [x0 = x0_, nx = nx_, xstep = xs_](size_t i) -> real { return x0 + (i % nx) * xstep; },
[y0 = y0_, nx = nx_, ystep = ys_](size_t i) -> real { return y0 + (i / nx) * ystep; }),
x0(x0_),
y0(y0_),
nx(nx_),
ny(ny_),
xstep(xs_),
ystep(ys_)
{
}
const real& V(size_t i) const { return BaseData::V(i); } const real& V(size_t i) const { return BaseData::V(i); }
real& V(size_t i) { return BaseData::V(i); } real& V(size_t i) { return BaseData::V(i); }

63
include/tindexes.h

@ -1,63 +0,0 @@
#pragma once
#include "mdatetime.h"
#include <memory>
#include <vector>
using michlib::MDateTime;
template<class T> std::vector<size_t> GetTIndexes(const T& adapter, const MDateTime& b, const MDateTime& e)
{
std::vector<size_t> out;
auto nt = adapter.NTimes();
const MDateTime& beg = (b < e) ? b : e;
const MDateTime& end = (b > e) ? b : e;
if(beg > adapter.Time(nt - 1) || end < adapter.Time(0)) return out;
size_t ib = 0, ie = nt - 1;
for(size_t i = 0; i < nt; i++)
if(adapter.Time(i) >= beg)
{
ib = i;
break;
}
for(size_t i = nt; i != 0; i--)
if(adapter.Time(i - 1) <= end)
{
ie = i - 1;
break;
}
out.resize(ie - ib + 1);
for(size_t i = 0; i < ie - ib + 1; i++) out[i] = i + ib;
return out;
}
template<class T> std::vector<size_t> GetTIndexes(const T& adapter, const MString& regex)
{
std::vector<size_t> out;
std::unique_ptr<regex_t> regbuf(new regex_t);
if(0 != regcomp(regbuf.get(), regex.Buf(), REG_EXTENDED | REG_NOSUB)) return out;
for(size_t i = 0; i < adapter.NTimes(); i++)
{
MString date = adapter.Time(i).ToString();
if(0 != regexec(regbuf.get(), date.Buf(), 0, 0, 0)) continue;
out.push_back(i);
}
return out;
}
template<class T> size_t GetTIndex(const T& adapter, const MDateTime& t)
{
size_t nt = adapter.NTimes();
if(t <= adapter.Time(0)) return 0;
if(t >= adapter.Time(nt - 1)) return nt - 1;
for(size_t i = 0; i < nt - 1; i++)
if(t >= adapter.Time(i) && t <= adapter.Time(i + 1)) return (t - adapter.Time(i) <= adapter.Time(i + 1) - t) ? i : (i + 1);
return 0;
}

118
include/vartype.h

@ -1,118 +0,0 @@
#pragma once
#include "MString.h"
#include <variant>
using michlib::MString;
namespace vartype
{
enum class Vartype
{
NONE,
U,
V,
TEMP,
SAL,
CHL,
MLD,
SSH,
W
};
template<Vartype vt_> struct VartypeCarrier
{
static const Vartype vt = vt_;
};
using VartypeUnion =
std::variant<VartypeCarrier<Vartype::NONE>, VartypeCarrier<Vartype::U>, VartypeCarrier<Vartype::V>, VartypeCarrier<Vartype::TEMP>, VartypeCarrier<Vartype::SAL>,
VartypeCarrier<Vartype::CHL>, VartypeCarrier<Vartype::MLD>, VartypeCarrier<Vartype::SSH>, VartypeCarrier<Vartype::W>>;
template<class T, Vartype vt>
concept HasVar = requires(T t) {
{
t.template Read<vt>(0)(0, 0)
} -> std::convertible_to<real>;
{
t.template Read<vt>(std::vector<size_t>())(0, 0)
} -> std::convertible_to<real>;
};
} // namespace vartype
template<class DT, vartype::Vartype vt> static constexpr bool isDataSupported = vartype::HasVar<DT, vt>;
class VarType: public vartype::VartypeUnion
{
auto VT() const
{
return std::visit(
[](auto v) -> auto{ return decltype(v)::vt; }, *this);
}
public:
template<vartype::Vartype vt> VarType(vartype::VartypeCarrier<vt>&& vc): vartype::VartypeUnion(std::move(vc)) {}
VarType(MString str)
{
str.ToLower();
if(str == "u") *this = vartype::VartypeCarrier<vartype::Vartype::U>();
if(str == "v") *this = vartype::VartypeCarrier<vartype::Vartype::V>();
if(str == "t" || str == "temp" || str == "temperature") *this = vartype::VartypeCarrier<vartype::Vartype::TEMP>();
if(str == "s" || str == "sal" || str == "salinity") *this = vartype::VartypeCarrier<vartype::Vartype::SAL>();
if(str == "c" || str == "chl" || str == "chlorophyll") *this = vartype::VartypeCarrier<vartype::Vartype::CHL>();
if(str == "mld") *this = vartype::VartypeCarrier<vartype::Vartype::MLD>();
if(str == "ssh") *this = vartype::VartypeCarrier<vartype::Vartype::SSH>();
if(str == "w") *this = vartype::VartypeCarrier<vartype::Vartype::W>();
}
MString Name() const
{
switch(VT())
{
case(vartype::Vartype::NONE): return "none";
case(vartype::Vartype::U): return "U";
case(vartype::Vartype::V): return "V";
case(vartype::Vartype::TEMP): return "Temperature";
case(vartype::Vartype::SAL): return "Salinity";
case(vartype::Vartype::CHL): return "Chlorophyll";
case(vartype::Vartype::MLD): return "Mixed layer depth";
case(vartype::Vartype::SSH): return "Sea surface height";
case(vartype::Vartype::W): return "W";
}
return "none";
}
MString ShortName() const
{
switch(VT())
{
case(vartype::Vartype::NONE): return "none";
case(vartype::Vartype::U): return "u";
case(vartype::Vartype::V): return "v";
case(vartype::Vartype::TEMP): return "temp";
case(vartype::Vartype::SAL): return "sal";
case(vartype::Vartype::CHL): return "chl";
case(vartype::Vartype::MLD): return "mld";
case(vartype::Vartype::SSH): return "ssh";
case(vartype::Vartype::W): return "w";
}
return "none";
}
bool Ok() const { return VT() != vartype::Vartype::NONE; }
explicit operator bool() const { return Ok(); }
bool operator==(const VarType& vt) const { return VT() == vt.VT(); }
auto operator<=>(const VarType& vt) const { return VT() <=> vt.VT(); }
template<class Data> bool isSupported() const
{
return std::visit(
[](const auto v) -> bool
{
if constexpr(isDataSupported<Data, decltype(v)::vt>)
return true;
else
return false;
},
*this);
}
};

5
src/ParseArgs.cpp

@ -1,6 +1,7 @@
#define MICHLIB_NOSOURCE #define MICHLIB_NOSOURCE
#include "GPL.h" #include "ParseArgs.h"
#include "odm.h"
using michlib::SList;
CLArgs ParseArgs(int argc, char** argv) CLArgs ParseArgs(int argc, char** argv)
{ {

45
src/actioninfo.cpp

@ -1,36 +1,19 @@
#define MICHLIB_NOSOURCE #define MICHLIB_NOSOURCE
#include "GPL.h"
#include "odm.h" #include "odm.h"
int actioninfo(const CLArgs& args) MString Data::ActionInfo(const CLArgs& args)
{ {
if(!args.contains("source")) MString info = std::visit(
{ [](const auto& arg) -> auto
errmessage("No source specified!"); {
return 1; using T = std::decay_t<decltype(arg)>;
} if constexpr(InfoSupported<T>)
return arg.Info();
Data data; else
return MString();
if(args.at("source") == "NEMO") },
{ *this);
NEMOData ndata = NEMOOpen(args); if(!info.Exist()) return "Unsupported combination of action and source";
message(info);
if(!ndata) return "";
{
errmessage("Can't open NEMO dataset");
return 1;
}
data = DataVariants(std::move(ndata));
}
else
{
errmessage("Unknown source " + args.at("source"));
return 1;
}
message(data.Info());
return 0;
} }

165
src/actiontsc.cpp

@ -2,132 +2,97 @@
#include "GPL.h" #include "GPL.h"
#include "odm.h" #include "odm.h"
int actiontsc(const CLArgs& args) MString Data::ActionTsc(const CLArgs& args)
{ {
if(!args.contains("source")) if(args.contains("time") && (args.contains("timeb") || args.contains("timee"))) return "Time must be set via time parameter or timeb and timee parameter but not via both";
{ if(!(args.contains("time") || (args.contains("timeb") && args.contains("timee")))) return "Time must be set via time parameter or timeb and timee parameter";
errmessage("No source specified!");
return 1;
}
VarType vtype(args.at("var"));
if(!vtype)
{
errmessage("Incorrect or no variable specified");
return 1;
}
michlib_internal::ParameterListEx pars; michlib_internal::ParameterListEx pars;
pars.UsePrefix(""); pars.UsePrefix("");
pars.SetParameter("source", args.at("source")); pars.SetParameter("source", args.at("source"));
pars.SetParameter("variable", vtype.Name()); TIndex tindexes;
Data data; if(args.contains("time"))
if(args.at("source") == "NEMO")
{ {
if(!vtype.isSupported<NEMOData>()) tindexes = GetTIndexes(args.at("time")); // Regular expression or single date
{ if(tindexes.size() == 0) return "There are no times matching the regular expression";
errmessage("Variable " + args.at("var") + " is unsupported by NEMO"); if(tindexes.size() == 1)
return 1; pars.SetParameter("time", Time(tindexes[0]).ToString());
} else
pars.SetParameter("timeregex", args.at("time"));
NEMOData ndata = NEMOOpen(args);
if(!ndata)
{
errmessage("Can't open NEMO dataset");
return 1;
}
size_t layer = 0;
if(args.contains("layer")) layer = args.at("layer").ToInteger<size_t>();
if(!args.contains("depth") && layer >= ndata.NDepths())
{
errmessage(MString("Layer ") + layer + " is too deep!");
return 1;
}
real depth = args.contains("depth") ? args.at("depth").ToReal() : ndata.Depth(layer);
if(!(args.contains("lonb") && args.contains("lone") && args.contains("latb") && args.contains("late")))
{
errmessage("Region not specified (lonb, lone, latb, late)");
return 1;
}
if(!ndata.SetRegion(args.at("lonb").ToReal(), args.at("lone").ToReal(), args.at("latb").ToReal(), args.at("late").ToReal(), depth))
{
errmessage("Can't set region");
return 1;
}
pars.SetParameter("depth", depth);
pars.SetParameter("layer", ndata.Layer());
pars.SetParameter("dataset", ndata.Title());
pars.SetParameter("lonb", args.at("lonb").ToReal());
pars.SetParameter("latb", args.at("latb").ToReal());
pars.SetParameter("lone", args.at("lone").ToReal());
pars.SetParameter("late", args.at("late").ToReal());
data = DataVariants(std::move(ndata));
} }
else else
{ {
errmessage("Unknown source " + args.at("source")); MDateTime tb(args.at("timeb")), te(args.at("timee"));
return 1; tindexes = GetTIndexes(tb, te);
if(tindexes.size() == 0) return "There are no times between " + tb.ToString() + " and " + te.ToString();
pars.SetParameter("timeb", tb.ToString());
pars.SetParameter("timee", te.ToString());
} }
if(args.contains("time") && (args.contains("timeb") || args.contains("timee"))) std::unique_ptr<const BaseParameters> sourcepars;
{ {
errmessage("Time must be set via time parameter or timeb and timee parameter but not via both"); std::pair<const BaseParameters*, MString> ppar = std::visit(
return 1; [&pars = pars, &args = args](const auto& arg) -> std::pair<const BaseParameters*, MString>
{
using T = std::decay_t<decltype(arg)>;
if constexpr(ParametersSupported<T>)
return arg.Parameters(pars, args);
else
return {nullptr, ""};
},
*this);
if(ppar.second.Exist()) return ppar.second;
sourcepars.reset(ppar.first);
} }
if(!(args.contains("time") || (args.contains("timeb") && args.contains("timee")))) auto p = sourcepars.get();
size_t ind;
auto read = [p = p, &ind = ind](const auto& arg) -> BaseData
{
using T = std::decay_t<decltype(arg)>;
if constexpr(ParametersSupported<T> && ReadPSupported<T>)
return arg.Read(p, ind);
else if constexpr(!ParametersSupported<T> && ReadSupported<T>)
return arg.Read(ind);
else
return BaseData();
};
BaseData data;
if(tindexes.size() == 1)
data = std::visit(read, *this);
else
{ {
errmessage("Time must be set via time parameter or timeb and timee parameter"); Averager<BaseData> out;
return 1; bool ok = true;
for(size_t i = 0; i < tindexes.size(); i++)
{
if(!ok) break;
ind = tindexes[i];
auto dat = std::visit(read, *this);
if(dat)
out.Add(dat);
else
ok = false;
}
if(ok) data = out.Div();
} }
if(!data) return "Can't read data";
BFileW fw; BFileW fw;
MString name = args.contains("out") ? args.at("out") : ""; MString name = args.contains("out") ? args.at("out") : "";
if(!name.Exist()) name = "out.bin"; if(!name.Exist()) name = "out.bin";
fw.Create(name, 3); fw.Create(name, 3);
/*
if(!fw.Create(name,3))
{
errmessage("Can't create file "+name);
return 1;
}
*/
fw.SetParameters(pars); fw.SetParameters(pars);
fw.UsePrefix(""); for(size_t i = 0; i < data.N(); i++)
if(args.contains("time"))
{ {
MDateTime time; fw.Write(data.Lon(i));
if(time.FromString(args.at("time"))) // One date fw.Write(data.Lat(i));
{ fw.Write(data(i) == data.Fillval() ? NAN : data(i));
auto it = GetTIndex(data, time);
fw.SetParameter("time", data.Time(it).ToString());
data.Write(fw, vtype, std::vector<size_t>(1, it));
}
else // Regular expression
{
fw.SetParameter("timeregex", args.at("time"));
data.Write(fw, vtype, GetTIndexes(data, args.at("time")));
}
} }
else // Bdate, edate
{
MDateTime tb(args.at("timeb")), te(args.at("timee"));
fw.SetParameter("timeb", tb.ToString());
fw.SetParameter("timee", te.ToString());
data.Write(fw, vtype, GetTIndexes(data, tb, te));
}
fw.Finalize();
fw.Close();
return 0; return "";
} }

3
src/basedata.cpp

@ -0,0 +1,3 @@
#include "basedata.h"
BaseParameters::~BaseParameters() {}

23
src/odm.cpp

@ -33,16 +33,31 @@ int main(int argc, char** argv)
MString action = args.contains("action") ? args["action"] : "tsc"; MString action = args.contains("action") ? args["action"] : "tsc";
int ret = 1; Data data;
{
auto ret = data.Init(args);
if(ret.Exist())
{
errmessage(ret);
return 1;
}
}
MString ret;
if(action == "info") if(action == "info")
ret = actioninfo(args); ret = data.ActionInfo(args);
else if(action == "tsc") else if(action == "tsc")
ret = actiontsc(args); ret = data.ActionTsc(args);
else else
{ {
errmessage("Unknown action " + action); errmessage("Unknown action " + action);
return 1; return 1;
} }
if(ret.Exist())
{
errmessage(ret);
return 1;
}
return ret; return 0;
} }

Loading…
Cancel
Save