Compare commits

..

67 Commits

Author SHA1 Message Date
Michael Uleysky f7a786e1be Fixed incorrect read request 18 hours ago
Michael Uleysky 2d46db5bea Added new datasource for HYCOM 3 days ago
Michael Uleysky ce31ba091c Using proxy for zarr data and metadata 2 weeks ago
Michael Uleysky 5b5a1c8fd1 Fixed some clang warnings 2 weeks ago
Michael Uleysky 5c32a8bdfe Added udunit2 dependency 2 weeks ago
Michael Uleysky 19f63956a5 Added class FileInfoCache - PostgreSQL-based cache for the information about files 2 months ago
Michael Uleysky 29a7a98548 RAII class for the PGresult* 2 months ago
Michael Uleysky c38a74402d Added nofollow flag (true by default) for ReadLocalFileList function 2 months ago
Michael Uleysky 802dcb55cd New dataset for NEMO (temporary fix) 3 months ago
Michael Uleysky 6180648ac5 Changed cache description string parsing 3 months ago
Michael Uleysky c00ce94180 The names of products and datasets are obtained from the href field, not the title field 4 months ago
Michael Uleysky 4294be57d1 Added supporting proxy for access to the COPERNICUS catalog 4 months ago
Michael Uleysky 6d6b77f7d3 Added filter parameter for the COPERNICUS source. Now we can select subset of files for mirroring. 5 months ago
Michael Uleysky 815ae6b4dd qqq 5 months ago
Michael Uleysky 28a003c488 We use only one global connection to the database 5 months ago
Michael Uleysky 85212ae018 Support for dissolved oxygen concentration 5 months ago
Michael Uleysky b4b11005a2 Calculatin gradients in the action tsc 5 months ago
Michael Uleysky 7c3c254b72 Support for multiple open files or datasets in the NCZarr interface. 5 months ago
Michael Uleysky c46abb091d Removed some features from the NCZarr interface. Removed direct access to variables and dimensions in NCZarr, dimensions are now bound to variables. 5 months ago
Michael Uleysky 9a38bdb28b Common class for NCSimple and Zarr 6 months ago
Michael Uleysky 91579910ff We distinguish between conventional and geostrophic velocities, since some data sets contain both. 7 months ago
Michael Uleysky a36d6ce955 Always read variables as real values 7 months ago
Michael Uleysky ae5fea9fd3 The inverted vertical axis treatment is placed in the proper place 7 months ago
Michael Uleysky 86c40b42f2 Update michlib 7 months ago
Michael Uleysky 400de53eef ZARR support for AVISO and NEMOBIO sources 7 months ago
Michael Uleysky dc92f4697f Remove some unused datasets 7 months ago
Michael Uleysky 93cee61939 Workaround for unknown bug in libcurl and/or ssl 7 months ago
Michael Uleysky 82b233b443 Handle situation when fill=null in zarr file 7 months ago
Michael Uleysky 3e9430673d Some files define time shift as double, not int 7 months ago
Michael Uleysky f0fefd5f90 Restore GPL prefix 8 months ago
Michael Uleysky 8bfa07b9c9 PostgreSQL backend for the cache 8 months ago
Michael Uleysky 5ca27193f7 Rewrite NEMO source for using new zarr data 8 months ago
Michael Uleysky 2a99a98192 Fix some errors in ZARR reader 8 months ago
Michael Uleysky 01af4109c1 ZARR-compatible interface to the netcdf files 8 months ago
Michael Uleysky 8a4fbea40a Simple ZARR format reader 8 months ago
Michael Uleysky 8670511319 Added buffer for error messages in the CURLRAII class 8 months ago
Michael Uleysky 775a038263 Working with the Copernicus catalog is placed in a separate class. 8 months ago
Michael Uleysky 4627f76a0d Update michlib, fix errors and warnings 8 months ago
Michael Uleysky e191f99c8f Fixed some memory leaks 9 months ago
Michael Uleysky 4274cda5b3 Added mirror action and COPERNICUS source for it with caches, fucnctions, etc 9 months ago
Michael Uleysky be7103e718 Added #pragma once to the header 9 months ago
Michael Uleysky 0f49d1254b Update michlib 9 months ago
Michael Uleysky b983023d49 New source GRIDVEL for reading pair of Surfer 6 grd files for velocity 9 months ago
Michael Uleysky 932191ec95 Fixed error with output files names for the action uv 9 months ago
Michael Uleysky 93f6ccc563 New action - int 9 months ago
Michael Uleysky 04f45ba459 Support for time column in netcdf files generated by uv and tsc actions 10 months ago
Michael Uleysky b4bcd0e770 Added TimeData structure for easy calculation of time column parameters of netcdf files. 10 months ago
Michael Uleysky f27ad2ce72 The code for obtaining time point indices has been simplified. The indexes are returned sorted by the time points they represent. 10 months ago
Michael Uleysky 9d2f411e04 Added interface for partially writing a variable to a netcdf file 10 months ago
Michael Uleysky 7a1690e6f0 Added support for writing attributes to netcdf files 11 months ago
Michael Uleysky 17002e2d9f Time was not written to the output file 11 months ago
Michael Uleysky 2eb57ac715 Fixed a bug with a crash if the link with the data cannot be opened 11 months ago
Michael Uleysky ece69ce56d Many new regional datasets for the NEMO source 12 months ago
Michael Uleysky d2d516aabb Support for regional grids in the LayeredData class. 12 months ago
Michael Uleysky 81fb8d1ddc Update michlib 12 months ago
Michael Uleysky 1e8d401737 Support for second as time unit in NCFuncs::Refdate 12 months ago
Michael Uleysky cccd67aab4 Added an interface class for writing netcdf files 1 year ago
Michael Uleysky 0509ca31f6 Information about geostrophic or non-geostrophic velocities is added to the output file for the genintfile action 1 year ago
Michael Uleysky bf659b1b81 NCFuncs::CheckVar now returns a VarPresence enum instead of a bool. 1 year ago
Michael Uleysky afe773876e Traits for non-geographical arrays 1 year ago
Michael Uleysky 09f13ac243 Gradient calculation code (without filtering) 1 year ago
Michael Uleysky 90dab32ee5 Action class inheritance is made protected 1 year ago
Michael Uleysky 81d8545244 Fixed incorrect error message 1 year ago
Michael Uleysky dcbc249fd7 Netcdf output files made more COARDS-compliant 1 year ago
Michael Uleysky ef98f3f63d Fixed incorrect setting of variable names in tsc action. 1 year ago
Michael Uleysky cfb40badbf Added long names for all variables supported by NCFuncs 1 year ago
Michael Uleysky 18b1f939b3 New class for access to vylet data 1 year ago
  1. 9
      CMakeLists.txt
  2. 44
      actions/actiongenintfile.h
  3. 130
      actions/actiongrad.cpp
  4. 133
      actions/actiongrad.h
  5. 210
      actions/actionint.h
  6. 10
      actions/actioninterpolate2d.cpp
  7. 181
      actions/actioninterpolate2d.h
  8. 22
      actions/actionmirror.h
  9. 85
      actions/actiontsc.h
  10. 118
      actions/actionuv.cpp
  11. 197
      actions/actionuv.h
  12. 195
      include/actiondep.h
  13. 18
      include/basedata.h
  14. 447
      include/cache.h
  15. 54
      include/copcat.h
  16. 36
      include/curlfuncs.h
  17. 51
      include/interpolation.h
  18. 11
      include/layereddata.h
  19. 207
      include/layereddataz.h
  20. 64
      include/mirrorfuncs.h
  21. 547
      include/ncfilew.h
  22. 207
      include/ncfuncs.h
  23. 87
      include/ncsimple.h
  24. 166
      include/nczarr.h
  25. 826
      include/nczarrcommon.h
  26. 206
      include/nczarrmulti.h
  27. 132
      include/simple2ddata.h
  28. 29
      include/traits.h
  29. 178
      include/zarr.h
  30. 2
      michlib
  31. 6
      sources/AVISO.h
  32. 26
      sources/AVISOLOCAL.cpp
  33. 4
      sources/AVISOLOCAL.h
  34. 9
      sources/BINFILE.h
  35. 222
      sources/COPERNICUS.cpp
  36. 21
      sources/COPERNICUS.h
  37. 86
      sources/GRIDVEL.cpp
  38. 50
      sources/GRIDVEL.h
  39. 6
      sources/HYCOM.h
  40. 7
      sources/MODISBINLOCAL.h
  41. 56
      sources/NEMO.h
  42. 6
      sources/NEMOBIO.h
  43. 129
      sources/TSCDATA.cpp
  44. 35
      sources/TSCDATA.h
  45. 526
      sources/VYLET.cpp
  46. 145
      sources/VYLET.h
  47. 13
      src/CMakeLists.txt
  48. 254
      src/cache.cpp
  49. 180
      src/copcat.cpp
  50. 41
      src/curlfuncs.cpp
  51. 102
      src/layereddata.cpp
  52. 295
      src/layereddataz.cpp
  53. 134
      src/mirrorfuncs.cpp
  54. 168
      src/ncfilew.cpp
  55. 129
      src/ncfuncs.cpp
  56. 231
      src/ncsimple.cpp
  57. 250
      src/zarr.cpp

9
CMakeLists.txt

@ -39,7 +39,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
include_directories(include sources sources-add GSW-C)
add_compile_options(-Wall)
add_compile_options(-Wall -Wno-deprecated-declarations)
# Dwarf-4 support check
include(CheckCXXCompilerFlag)
@ -54,6 +54,13 @@ if(COMPILER_SUPPORTS_ANALYZER AND STATIC_ANALYZER)
add_compile_options(-fanalyzer)
endif()
# Disable michlib warnings
add_compile_options(-Wno-deprecated-declarations)
CHECK_CXX_COMPILER_FLAG(-Wno-class-memaccess COMPILER_SUPPORTS_CLASSMEMACCESS)
if(COMPILER_SUPPORTS_CLASSMEMACCESS)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-class-memaccess>)
endif()
add_library(teos STATIC GSW-C/gsw_oceanographic_toolbox.c GSW-C/gsw_saar.c)
set_target_properties(teos PROPERTIES LINKER_LANGUAGE C)

44
actions/actiongenintfile.h

@ -23,6 +23,20 @@ template<class D> MString ActionGenIntFile::DoAction(const CLArgs& args, D& ds)
if(!args.contains("out")) return "Output file name not specified";
VelSource mode = VelSource::AUTOSEL;
if(args.contains("geostrophic"))
{
auto modestr = args.at("geostrophic");
if(modestr == "" || modestr == "ssh")
mode = VelSource::GEOSTROPHICSSH;
else if(modestr == "surf" || modestr == "surface")
mode = VelSource::GEOSTROPHIC;
else if(modestr == "nongeo")
mode = VelSource::STANDART;
else
return "Parameter \"geostrophic\" have incorrect value " + modestr;
}
auto [tindexes, err] = GetTIndexes(ds, args, pars);
if(err.Exist()) return err;
if(tindexes.size() < 10) return "Too few time moments";
@ -56,7 +70,7 @@ template<class D> MString ActionGenIntFile::DoAction(const CLArgs& args, D& ds)
for(size_t it = 0; it < tindexes.size(); it++)
{
auto data = ReadUV(ds, p, tindexes[it]);
auto data = ReadUV(ds, p, tindexes[it], mode);
if(!data) return "Can't read data";
if(it == 0)
{
@ -67,9 +81,32 @@ template<class D> MString ActionGenIntFile::DoAction(const CLArgs& args, D& ds)
fw.SetParameter("dx", data.XStep() * 60.0);
fw.SetParameter("dy", data.YStep() * 60.0);
fw.SetParameter("nt", tindexes.size());
fw.SetParameter("dt", (ds.Time(tindexes[1]) - ds.Time(tindexes[0])) / 86400.0);
fw.SetParameter("dt", (ds.Time(tindexes[1]) - ds.Time(tindexes[0])).D());
fw.SetParameter("FillValue", data.Fillval());
fw.UsePrefix("Info");
switch(mode)
{
case(VelSource::AUTOSEL):
{
fw.SetParameter("Mode", "autoselect between standart and geostrophic");
break;
}
case(VelSource::STANDART):
{
fw.SetParameter("Mode", "standart");
break;
}
case(VelSource::GEOSTROPHIC):
{
fw.SetParameter("Mode", "geostrophic");
break;
}
case(VelSource::GEOSTROPHICSSH):
{
fw.SetParameter("Mode", "geostrophic from ssh");
break;
}
}
fw.SetParameter("lonb", lonb);
fw.SetParameter("latb", latb);
fw.SetParameter("lone", lone);
@ -89,8 +126,9 @@ template<class D> MString ActionGenIntFile::DoAction(const CLArgs& args, D& ds)
fw.SetParameter("dotxdotylonlat2v", "(1.852/0.864)*%doty");
fw.SetParameter("BeginDate", ds.Time(tindexes.front()).ToTString());
fw.SetParameter("EndDate", ds.Time(tindexes.back()).ToTString());
fw.SetParameter("Timestep", ds.Time(tindexes[1]) - ds.Time(tindexes[0]));
fw.SetParameter("Timestep", (ds.Time(tindexes[1]) - ds.Time(tindexes[0])).Seconds());
fw.SetParameter("DataID", michlib::UniqueId());
fw.SetParameter("Creation command", args.at("_cmdline"));
}
for(size_t ilat = 0; ilat < data.Ny(); ilat++)

130
actions/actiongrad.cpp

@ -0,0 +1,130 @@
#define MICHLIB_NOSOURCE
#include "actiongrad.h"
MString GradMethods::NCFileW::Create(const MString& name, const MString& history, const std::vector<MString>& vnames, const std::vector<MString>& lnames,
const std::vector<GradMethods::DataType>& lons, const std::vector<GradMethods::DataType>& lats, int compress)
{
const float node_offset = 0.0;
auto nx = lons.size();
auto ny = lats.size();
const auto fill = static_cast<GradMethods::DataType>(std::numeric_limits<GradMethods::Matrix::MDataType>::max());
// Creating
Open(name);
if(!*this) return "Can't create netcdf file " + name + ": " + ErrMessage();
AddAtt("history", history);
AddAtt("node_offset", node_offset);
AddDim("longitude", nx);
AddDim("latitude", ny);
AddVar("longitude", NC_FLOAT, "longitude");
AddVar("latitude", NC_FLOAT, "latitude");
SetComp("longitude", compress);
SetComp("latitude", compress);
AddAtt("longitude", "standard_name", "longitude");
AddAtt("longitude", "long_name", "Longitude");
AddAtt("latitude", "standard_name", "latitude");
AddAtt("latitude", "long_name", "Latitude");
// Variables
for(size_t i = 0; i < vnames.size(); i++)
{
AddVar(vnames[i], NC_FLOAT, "latitude", "longitude");
SetComp(vnames[i], compress);
if(lnames[i].Exist()) AddAtt(vnames[i], "long_name", lnames[i]);
AddAtt(vnames[i], "_FillValue", fill);
}
// End definitions
EndDef();
// Writing lon, lat
WriteVar("longitude", lons.data());
WriteVar("latitude", lats.data());
if(!*this) return "Can't set grid in the netcdf file " + name + ": " + ErrMessage();
return "";
}
MString GradMethods::NCFileW::WriteVariable(const MString& name, const GradMethods::Matrix& data)
{
WriteVar(name, data.Data().data());
if(!*this) return "Can't write variable " + name + ": " + ErrMessage();
return "";
}
GradMethods::Matrix::Matrix(const std::vector<GradMethods::DataType>& in, size_t nx_, size_t ny_, struct GradMethods::MinMax minmax): nx(nx_), ny(ny_), data(nx_ * ny_)
{
if(minmax.automin || minmax.automax)
{
DataType min = in[0];
DataType max = in[0];
for(size_t i = 1; i < in.size(); i++)
if(in[i] != minmax.fill)
{
min = std::min(min, in[i]);
max = std::max(max, in[i]);
}
if(minmax.automin) minmax.min = min;
if(minmax.automax) minmax.max = max;
}
if(minmax.log)
{
minmax.min = michlib_internal::RealType<sizeof(DataType)>::Log(minmax.min);
minmax.max = michlib_internal::RealType<sizeof(DataType)>::Log(minmax.max);
}
DataType a = (std::numeric_limits<MDataType>::max() - 1) / (minmax.max - minmax.min);
for(size_t i = 1; i < in.size(); i++)
{
DataType v = minmax.log ? michlib_internal::RealType<sizeof(DataType)>::Log(in[i]) : in[i];
if(in[i] == minmax.fill)
data[i] = std::numeric_limits<MDataType>::max();
else if(v <= minmax.min)
data[i] = 0;
else if(v >= minmax.max)
data[i] = std::numeric_limits<MDataType>::max() - 1;
else
data[i] = static_cast<MDataType>(michlib_internal::RealType<sizeof(DataType)>::Round(a * (v - minmax.min)));
}
}
void GradMethods::Matrix::Grad()
{
std::vector<MDataType> out(data.size());
const auto bad = std::numeric_limits<MDataType>::max();
for(size_t iy = 0; iy < ny; iy++)
for(size_t ix = 0; ix < nx; ix++)
{
if(iy < 1 || ix < 1 || iy > ny - 2 || ix > nx - 2)
out[iy * nx + ix] = bad;
else if(V(ix - 1, iy - 1) == bad || V(ix, iy - 1) == bad || V(ix + 1, iy - 1) == bad || V(ix - 1, iy) == bad || V(ix, iy) == bad || V(ix + 1, iy) == bad ||
V(ix - 1, iy + 1) == bad || V(ix, iy + 1) == bad || V(ix + 1, iy + 1) == bad)
out[iy * nx + ix] = bad;
else
{
using IT = michlib::int4;
// Possible but unlikely overflow
const IT m1 = -1;
const IT m2 = -2;
const IT p1 = 1;
const IT p2 = 2;
IT gx = m1 * V(ix - 1, iy + 1) + p1 * V(ix + 1, iy + 1) + m2 * V(ix - 1, iy) + p2 * V(ix + 1, iy) + m1 * V(ix - 1, iy - 1) + p1 * V(ix + 1, iy - 1);
IT gy = m1 * V(ix - 1, iy - 1) + p1 * V(ix - 1, iy + 1) + m2 * V(ix, iy - 1) + p2 * V(ix, iy + 1) + m1 * V(ix + 1, iy - 1) + p1 * V(ix + 1, iy + 1);
auto sq = static_cast<IT>(michlib::Round(michlib::Hypot(gx, gy)));
if(sq >= bad) sq = bad - 1;
out[iy * nx + ix] = static_cast<MDataType>(sq);
}
}
data = std::move(out);
}

133
actions/actiongrad.h

@ -0,0 +1,133 @@
#pragma once
#include "actiondep.h"
#include "ncfilew.h"
#include "ncfuncs.h"
class GradMethods
{
public:
using DataType = float;
static DataType ToNum(const MString& str) { return michlib_internal::RealType<sizeof(DataType)>::String2Real(str.Buf()); }
struct MinMax
{
bool automin, automax, log;
DataType min, max;
DataType fill;
};
class Matrix;
class NCFileW;
};
class GradMethods::Matrix
{
public:
using MDataType = michlib::uint2;
private:
size_t nx, ny;
std::vector<MDataType> data;
public:
Matrix(const std::vector<DataType>& in, size_t nx_, size_t ny_, struct MinMax minmax);
void Grad();
auto Nx() const { return nx; }
auto Ny() const { return ny; }
const auto& V(size_t ix, size_t iy) const { return data[iy * nx + ix]; }
auto& V(size_t ix, size_t iy) { return data[iy * nx + ix]; }
const auto& Data() const { return data; }
};
class GradMethods::NCFileW: public NCFileWBase
{
public:
MString Create(const MString& name, const MString& history, const std::vector<MString>& vnames, const std::vector<MString>& lnames, const std::vector<GradMethods::DataType>& lons,
const std::vector<GradMethods::DataType>& lats, int compress);
MString WriteVariable(const MString& name, const GradMethods::Matrix& data);
};
template<class T>
concept GradSupported = requires(T t, const MString& vname) {
{
t.ReadVar(vname)
} -> std::same_as<std::vector<GradMethods::DataType>>;
};
ADD_ACTION(GRAD, grad, GradSupported<Source>, GradMethods);
template<class D> MString ActionGRAD::DoAction(const CLArgs& args, D& ds)
{
auto resop = ds.Open(args);
if(resop.Exist()) return "Can't open source: " + resop;
MString name = args.contains("out") ? args.at("out") : "out.nc";
MString min = args.contains("min") ? args.at("min") : "auto";
MString max = args.contains("max") ? args.at("max") : "auto";
int compress = args.contains("compress") ? args.at("compress").ToInt() : 3;
std::vector<Matrix> data;
std::vector<MString> lnames;
// Read data
for(size_t i = 0; i < ds.NVar(); i++)
{
const MString& name = ds.VarNames()[i];
const MString& lname = ds.LongNames()[i];
bool hmin = args.contains(name + "_min");
bool hmax = args.contains(name + "_max");
struct MinMax minmax;
minmax.log = args.contains(name + "_log");
if(hmin)
{
MString vmin = args.at(name + "_min");
minmax.automin = (vmin == "auto");
minmax.min = ToNum(vmin);
}
else
{
minmax.automin = (min == "auto");
minmax.min = ToNum(min);
}
if(hmax)
{
MString vmax = args.at(name + "_max");
minmax.automax = (vmax == "auto");
minmax.max = ToNum(vmax);
}
else
{
minmax.automax = (max == "auto");
minmax.max = ToNum(max);
}
minmax.fill = ds.FillVal(name);
data.emplace_back(ds.ReadVar(name), ds.Nx(), ds.Ny(), minmax);
lnames.emplace_back(lname + ", gradient");
}
NCFileW fw;
fw.Create(name, (ds.History().Exist() ? (ds.History() + "; ") : "") + args.at("_cmdline"), ds.VarNames(), lnames, ds.ReadLons(), ds.ReadLats(), compress);
for(size_t i = 0; i < ds.NVar(); i++)
{
const MString& name = ds.VarNames()[i];
data[i].Grad();
fw.WriteVariable(name, data[i]);
}
return "";
};

210
actions/actionint.h

@ -0,0 +1,210 @@
#pragma once
#include "BFileR.h"
#include "BFileW.h"
#include "actiondep.h"
#include "interpolation.h"
#include "ncfilew.h"
#include "ncfuncs.h"
#include <memory>
template<class T>
concept ReadIsInterpolable = requires {
{
std::declval<ReadType<T>>().GridPos(0.0, 0.0)
} -> std::convertible_to<struct GridPoint>;
};
ADD_ACTION(INT, int, (ReadPSupported<Source> || ReadSupported<Source>)&&ReadIsInterpolable<Source>);
template<class D> MString ActionINT::DoAction(const CLArgs& args, D& ds)
{
struct TPoint
{
struct Point2D p;
MDateTime t;
real lon0, lat0, dtime;
};
MDateTime refdate("1980-01-01");
if(!args.contains("input")) return "No input file given";
michlib::BFileR in;
if(in.Open(args.at("input")) != ERR_NOERR) return "Can't open input file " + args.at("input");
std::vector<TPoint> points;
struct Region reg;
{
michlib::uint loncol = in.Columns(), latcol = in.Columns(), timecol = in.Columns();
michlib::uint lon0col = in.Columns(), lat0col = in.Columns(), dtimecol = in.Columns();
for(uint i = 0; i < in.Columns(); i++)
{
if(in.ColumnName(i + 1) == "lon") loncol = i;
if(in.ColumnName(i + 1) == "lat") latcol = i;
if(in.ColumnName(i + 1) == "rtime") timecol = i;
if(in.ColumnName(i + 1) == "lon0") lon0col = i;
if(in.ColumnName(i + 1) == "lat0") lat0col = i;
if(in.ColumnName(i + 1) == "time") dtimecol = i;
}
if(loncol >= in.Columns()) return "Lon column not found in input file " + args.at("input");
if(latcol >= in.Columns()) return "Lat column not found in input file " + args.at("input");
if(timecol >= in.Columns()) return "Time column not found in input file " + args.at("input");
if(lon0col >= in.Columns()) return "Lon0 column not found in input file " + args.at("input");
if(lat0col >= in.Columns()) return "Lat0 column not found in input file " + args.at("input");
if(dtimecol >= in.Columns()) return "Age column not found in input file " + args.at("input");
points.resize(in.Rows());
reg.lonb = reg.lone = in[loncol][0];
reg.latb = reg.late = in[latcol][0];
for(uint i = 0; i < in.Rows(); i++)
{
points[i].p.x = in[loncol][i];
points[i].p.y = in[latcol][i];
points[i].t = refdate + static_cast<time_t>(michlib::Round(in[timecol][i] * 86400));
points[i].lon0 = in[lon0col][i];
points[i].lat0 = in[lat0col][i];
points[i].dtime = in[dtimecol][i];
reg.lonb = std::min(reg.lonb, points[i].p.x);
reg.lone = std::max(reg.lone, points[i].p.x);
reg.latb = std::min(reg.latb, points[i].p.y);
reg.late = std::max(reg.late, points[i].p.y);
}
std::ranges::sort(points, {}, &TPoint::t);
}
in.Close();
auto resop = ds.Open(args);
if(resop.Exist()) return "Can't open source: " + resop;
michlib_internal::ParameterListEx pars;
pars.UsePrefix("");
pars.SetParameter("source", args.at("source"));
pars.SetParameter("history", args.at("_cmdline"));
MString varstring;
if(args.contains("var"))
varstring = args.at("var");
else
{
if(args.contains("vars"))
varstring = args.at("vars");
else
{
if constexpr(HasDefVars<D>)
varstring = ds.DefaultVars();
else
return "Variables not specified";
}
}
auto vlist = michlib::Split_on_words(varstring, " ,", false);
std::vector<MString> vnames{std::make_move_iterator(std::begin(vlist)), std::make_move_iterator(std::end(vlist))};
{
std::set<MString> used;
for(const auto& vname: vnames)
{
if(used.contains(vname)) return "Duplicate variable " + vname + " in list " + varstring;
if(ds.CheckVar(vname) == VarPresence::NONE) return "Variable " + vname + " not exists in this dataset";
used.insert(vname);
}
}
pars.SetParameter("variables", varstring);
//int compress = 3;
//if(args.contains("compress")) compress = args.at("compress").ToInt();
std::unique_ptr<const BaseParameters> sourcepars;
if constexpr(ParametersSupported<D>)
{
if constexpr(ParametersRequiredRegion<D>)
{
auto [p, err] = ds.Parameters(pars, args, reg);
if(err.Exist()) return err;
sourcepars.reset(p);
}
else
{
auto [p, err] = ds.Parameters(pars, args);
if(err.Exist()) return err;
sourcepars.reset(p);
}
}
auto p = sourcepars.get();
MString name = args.contains("out") ? args.at("out") : "out.bin";
//MString outfmt = args.contains("format") ? args.at("format") : (GetExt(name) == "nc" ? "nc" : "bin");
size_t loind = ds.NTimes(), hiind = ds.NTimes(), curind = 0;
michlib::BFileW fw;
const MDateTime first = ds.Time(0), last = ds.Time(ds.NTimes() - 1);
std::vector<LinearInterpolator<ReadType<D>>> lo, hi;
{
size_t ic = 0;
fw.Create(name, 6 + vnames.size());
fw.SetColumnName(++ic, "lon0");
fw.SetColumnName(++ic, "lat0");
fw.SetColumnName(++ic, "lon");
fw.SetColumnName(++ic, "lat");
fw.SetColumnName(++ic, "time");
fw.SetColumnName(++ic, "rtime");
for(size_t i = 0; i < vnames.size(); i++) fw.SetColumnName(++ic, vnames[i]);
fw.SetParameters(pars);
}
for(size_t i = 0; i < points.size(); i++)
{
if(points[i].t < first || points[i].t > last) continue;
while(!(points[i].t >= ds.Time(curind) && points[i].t <= ds.Time(curind + 1))) curind++;
if(curind != loind && curind != hiind)
{
loind = curind;
hiind = curind + 1;
{
auto temp = Read(ds, vnames, p, loind);
if(temp.size() != vnames.size()) return "Can't read data";
//lo.resize(temp.size());
for(size_t j = 0; j < temp.size(); j++) lo.emplace_back(std::move(temp[j]));
}
{
auto temp = Read(ds, vnames, p, hiind);
if(temp.size() != vnames.size()) return "Can't read data";
//hi.resize(temp.size());
for(size_t j = 0; j < temp.size(); j++) hi.emplace_back(std::move(temp[j]));
}
}
else if(curind == hiind)
{
loind = curind;
hiind = curind + 1;
lo = std::move(hi);
{
auto temp = Read(ds, vnames, p, hiind);
if(temp.size() != vnames.size()) return "Can't read data";
//hi.resize(temp.size());
for(size_t j = 0; j < temp.size(); j++) hi.emplace_back(std::move(temp[j]));
}
}
auto step = (ds.Time(hiind) - ds.Time(loind)).S();
auto delta = (points[i].t - ds.Time(loind)).S();
real trel = delta / step;
fw.Write(points[i].lon0);
fw.Write(points[i].lat0);
fw.Write(points[i].p.x);
fw.Write(points[i].p.y);
fw.Write(points[i].dtime);
fw.Write(DeltaDates(refdate, points[i].t));
for(size_t iv = 0; iv < lo.size(); iv++)
{
real vlo = lo[iv](points[i].p);
real vhi = hi[iv](points[i].p);
fw.Write(vlo + (vhi - vlo) * trel);
}
}
fw.Finalize();
fw.Close();
return "";
};

10
actions/actioninterpolate2d.cpp

@ -1,10 +0,0 @@
#define MICHLIB_NOSOURCE
#include "actioninterpolate2d.h"
InterpolateMethods::Mode InterpolateMethods::GetMode(const CLArgs& args)
{
MString mode = args.contains("mode") ? args.at("mode") : "vylet_start";
if(mode == "vylet_start") return Mode::VYLETSTART;
if(mode == "vylet_end") return Mode::VYLETEND;
return Mode::INVALID;
}

181
actions/actioninterpolate2d.h

@ -1,181 +0,0 @@
#pragma once
#include "BFileR.h"
#include "actiondep.h"
//using michlib::message;
class InterpolateMethods
{
protected:
enum class Mode
{
INVALID,
VYLETSTART,
VYLETEND
};
struct DataPoint
{
real lon, lat;
MDateTime t;
};
class VyletFile
{
michlib::BFileR fr;
bool invtime;
bool usestart;
MDateTime beg, end, t0;
michlib::CompiledParser xy2lon, xy2lat;
real x, y;
public:
MString Open(const MString& name, Mode mode);
};
static Mode GetMode(const CLArgs& args);
};
ADD_ACTION(Interpolate, interpolate, CanInterpolate2D<Source>, InterpolateMethods);
template<class D> MString ActionInterpolate::DoAction(const CLArgs& args, D& ds)
{
Mode mode = GetMode(args);
if(mode == Mode::INVALID) return "Unknown mode";
if(!args.contains("in")) return "No vylet data file specified";
MString in = args.at("in");
if(!args.contains("out")) return "No output file specified";
MString out = args.at("out");
michlib::BFileR fr;
if(fr.Open(in) != ERR_NOERR) return "Can't open file " + in;
std::vector<struct DataPoint> datapoints;
{
bool invtime;
{
fr.UsePrefix("");
MString method = fr.ParameterSValue("Method", "");
if(method == "Bicubic" || method == "BicubicL")
invtime = false;
else if(method == "BicubicI" || method == "BicubicIL")
invtime = true;
else
return "Unknown method in the file " + in;
}
MDateTime beg, end;
{
fr.UsePrefix("Datafile_Info");
MString s;
s = fr.ParameterSValue("BeginDate", "");
beg.FromString(s);
s = fr.ParameterSValue("EndDate", "");
end.FromString(s);
}
MDateTime t0;
{
fr.UsePrefix("");
auto tbeg = static_cast<time_t>(fr.ParameterRValue("tbeg", 0.0) * 86400);
if(invtime)
{
t0 = end;
t0.AddSeconds(-tbeg);
}
else
{
t0 = beg;
t0.AddSeconds(tbeg);
}
}
michlib::CompiledParser xy2lon, xy2lat;
real x, y;
michlib::ParserVars pv;
pv["x"] = &x;
pv["y"] = &y;
fr.UsePrefix("Datafile_Info");
if(!ArifmeticCompiler(fr.ParameterSValue("xy2lon", ""), xy2lon, &pv)) return "Can't find xy2lon in the file " + in;
if(!ArifmeticCompiler(fr.ParameterSValue("xy2lat", ""), xy2lat, &pv)) return "Can't find xy2lat in the file " + in;
auto resop = ds.Open(args);
if(resop.Exist()) return "Can't open source: " + resop;
for(size_t i = 0; i < fr.Rows(); i++)
{
if(mode == Mode::VYLETSTART)
{
x = fr[0][i];
y = fr[1][i];
real lon, lat;
xy2lon.Run(lon);
xy2lat.Run(lat);
datapoints.emplace_back(lon, lat, t0);
}
if(mode == Mode::VYLETEND)
{
x = fr[2][i];
y = fr[3][i];
real lon, lat;
xy2lon.Run(lon);
xy2lat.Run(lat);
time_t t = static_cast<time_t>(fr[4][i] * 86400);
MDateTime time = invtime ? end : beg;
time.AddSeconds((invtime ? -1 : 1) * t);
datapoints.emplace_back(lon, lat, time);
}
}
}
if(datapoints.size() == 0) return "No data readed from file " + in;
struct Region reg;
reg.lonb = datapoints[0].lon;
reg.lone = datapoints[0].lon;
reg.latb = datapoints[0].lat;
reg.late = datapoints[0].lat;
for(const auto& p: datapoints)
{
if(p.lon < reg.lonb) reg.lonb = p.lon;
if(p.lat < reg.latb) reg.latb = p.lat;
if(p.lon > reg.lone) reg.lone = p.lon;
if(p.lat > reg.late) reg.late = p.lat;
}
return "";
/*
if(!args.contains("var")) return "Variable not specified";
MString vname = args.at("var");
if(!ds.CheckVar(vname)) return "Variable " + vname + " not exists in this dataset";
pars.SetParameter("variable", vname);
std::unique_ptr<const BaseParameters> sourcepars;
if constexpr(ParametersSupported<D>)
{
if constexpr(ParametersRequiredRegion<D>)
{
auto [p, err] = ds.Parameters(pars, args, reg);
if(err.Exist()) return err;
sourcepars.reset(p);
}
else
{
auto [p, err] = ds.Parameters(pars, args);
if(err.Exist()) return err;
sourcepars.reset(p);
}
}
auto p = sourcepars.get();
auto data = Read(ds, vname, p, tindexes);
if(!data) return "Can't read data";
return "";
*/
};

22
actions/actionmirror.h

@ -0,0 +1,22 @@
#pragma once
#include "actiondep.h"
#include "merrors.h"
using michlib::message;
using michlib::Error;
template<class T>
concept MirrorSupported = requires(T t, const CLArgs& args) {
{
t.Mirror(args)
} -> std::convertible_to<Error>;
};
ADD_ACTION(Mirror, mirror, MirrorSupported<Source>);
template<class D> MString ActionMirror::DoAction(const CLArgs& args, D& data)
{
auto res = data.Mirror(args);
if(!res) return "Mirroring failed";
return "";
};

85
actions/actiontsc.h

@ -20,6 +20,7 @@ template<class D> MString ActionTSC::DoAction(const CLArgs& args, D& ds)
michlib_internal::ParameterListEx pars;
pars.UsePrefix("");
pars.SetParameter("source", args.at("source"));
pars.SetParameter("history", args.at("_cmdline"));
auto [tindexes, err] = GetTIndexes(ds, args, pars);
if(err.Exist()) return err;
@ -40,6 +41,15 @@ template<class D> MString ActionTSC::DoAction(const CLArgs& args, D& ds)
}
}
bool average = args.contains("average");
bool gradient = args.contains("gradient");
if(gradient)
if constexpr(!ReadIs2DGeoArray<D>) return "Gradient calculation not supported for this source";
int compress = 3;
if(args.contains("compress")) compress = args.at("compress").ToInt();
auto vlist = michlib::Split_on_words(varstring, " ,", false);
std::vector<MString> vnames{std::make_move_iterator(std::begin(vlist)), std::make_move_iterator(std::end(vlist))};
{
@ -47,7 +57,7 @@ template<class D> MString ActionTSC::DoAction(const CLArgs& args, D& ds)
for(const auto& vname: vnames)
{
if(used.contains(vname)) return "Duplicate variable " + vname + " in list " + varstring;
if(!ds.CheckVar(vname)) return "Variable " + vname + " not exists in this dataset";
if(ds.CheckVar(vname) == VarPresence::NONE) return "Variable " + vname + " not exists in this dataset";
used.insert(vname);
}
}
@ -72,19 +82,28 @@ template<class D> MString ActionTSC::DoAction(const CLArgs& args, D& ds)
}
auto p = sourcepars.get();
auto data = Read(ds, vnames, p, tindexes);
if(data.size() != vnames.size()) return "Can't read data";
TimeData tdata(ds, tindexes);
MString name = args.contains("out") ? args.at("out") : "out.bin";
MString outfmt = args.contains("format") ? args.at("format") : (GetExt(name) == "nc" ? "nc" : "bin");
if(outfmt == "bin" && !average && tindexes.size() > 1) return "Multiple time moments does'nt supported by the bin format";
bool headwrited = false;
NCFileW ncfw;
for(size_t it = 0; it < tindexes.size(); it++)
{
auto data = average ? Read(ds, vnames, p, tindexes) : Read(ds, vnames, p, tindexes[it]);
if(data.size() != vnames.size()) return "Can't read data";
if(!headwrited)
for(size_t i = 0; i < vnames.size(); i++)
{
if(!data[i].Unit().Exist()) michlib::errmessage("Unknown measurement unit for variable " + vnames[i] + "!");
if(!data[i].StandartName().Exist()) data[i].SetStandartName(NCFuncs::Name2StName(vnames[i]));
if(!data[i].LongName().Exist()) data[i].SetStandartName(NCFuncs::Name2LongName(vnames[i]));
if(!data[i].LongName().Exist()) data[i].SetLongName(NCFuncs::Name2LongName(vnames[i]));
}
MString name = args.contains("out") ? args.at("out") : "out.bin";
MString outfmt = args.contains("format") ? args.at("format") : (GetExt(name) == "nc" ? "nc" : "bin");
if(outfmt == "bin")
{
BFileW fw;
@ -93,38 +112,60 @@ template<class D> MString ActionTSC::DoAction(const CLArgs& args, D& ds)
fw.Create(name, 2 + data.size());
fw.SetColumnName(++ic, "Longitude");
fw.SetColumnName(++ic, "Latitude");
for(size_t i = 0; i < data.size(); i++) fw.SetColumnName(++ic, vnames[i] + ", " + (data[i].Unit().Exist() ? data[i].Unit() : "unknown"));
for(size_t i = 0; i < data.size(); i++)
fw.SetColumnName(++ic, (gradient ? "gradient of " : "") + vnames[i] + ", " + (data[i].Unit().Exist() ? data[i].Unit() : "unknown") + (gradient ? "/km" : ""));
fw.SetParameters(pars);
for(size_t r = 0; r < data[0].N(); r++)
{
fw.Write(data[0].Lon(r));
fw.Write(data[0].Lat(r));
if(gradient)
{
if constexpr(ReadIs2DGeoArray<D>)
for(size_t i = 0; i < data.size(); i++)
{
auto val = data[i].Grad(r);
fw.Write(val == data[i].Fillval() ? NAN : val);
}
}
else
for(size_t i = 0; i < data.size(); i++) fw.Write(data[i].IsFill(r) ? NAN : data[i](r));
}
fw.Finalize();
fw.Close();
return "";
}
if(outfmt == "nc" || outfmt == "netcdf")
else if(outfmt == "nc" || outfmt == "netcdf")
{
int compress = 3;
NCFileW fw;
MString err;
if(args.contains("compress")) compress = args.at("compress").ToInt();
if(!err.Exist()) err = fw.Create(data[0], name, args.at("_cmdline"), compress);
if(!err.Exist() && !headwrited) err = ncfw.Create(data[0], name, compress);
if(!err.Exist() && !headwrited) err = ncfw.AddTimeData(tdata, !average);
if(!err.Exist() && !headwrited) err = ncfw.AddAtts(pars);
for(size_t i = 0; i < data.size(); i++)
if(!err.Exist()) err = fw.AddVariable(vnames[i], data[i].StandartName(), data[i].LongName(), data[i].Unit(), data[i].Comment());
if(!err.Exist()) err = fw.WriteGrid(data[0]);
if(!err.Exist() && !headwrited)
err = ncfw.AddVariable((gradient ? "G" : "") + vnames[i], data[i].StandartName(), data[i].LongName(), data[i].Unit() + (gradient ? "/km" : ""), data[i].Comment());
if(!err.Exist() && !headwrited) err = ncfw.WriteGrid(data[0]);
for(size_t i = 0; i < data.size(); i++)
if(!err.Exist()) err = fw.WriteVariable(data[i], vnames[i]);
if(!err.Exist())
{
if(gradient)
{
if constexpr(ReadIs2DGeoArray<D>)
err = average ? ncfw.WriteVariable(data[i], "G" + vnames[i], [&data = std::as_const(data[i])](size_t i, size_t j) { return data.Grad(i, j); })
: ncfw.WriteVariable(data[i], "G" + vnames[i], [&data = std::as_const(data[i])](size_t i, size_t j) { return data.Grad(i, j); }, it);
}
else
err = average ? ncfw.WriteVariable(data[i], vnames[i]) : ncfw.WriteVariable(data[i], vnames[i], it);
}
if(err.Exist()) return err;
fw.Close();
}
else
return "Unknown format: " + outfmt;
return "";
if(average) break;
headwrited = true;
}
ncfw.Close();
return "Unknown format: " + outfmt;
return "";
};

118
actions/actionuv.cpp

@ -1,13 +1,14 @@
#define MICHLIB_NOSOURCE
#include "actionuv.h"
void UVMethods::StPoints::WriteBinBile(const MString& name) const
void UVMethods::StPoints::WriteBinBile(const MString& name, const michlib_internal::ParameterListEx& pars) const
{
BFileW stp;
stp.Create(name, 3);
stp.SetColumnName(1, lonlat ? "Longitude" : "x");
stp.SetColumnName(2, lonlat ? "Latitude" : "x");
stp.SetColumnName(3, "Stability (0 - saddle, 1 - st. anticicl. focus, 2 - st. knot, 3 - unst. anticicl. focus, 4 - unst. knot, 5 - st. cicl. focus, 6 - unst. cicl. focus)");
stp.SetParameters(pars);
for(size_t i = 0; i < N(); i++)
{
@ -19,70 +20,85 @@ void UVMethods::StPoints::WriteBinBile(const MString& name) const
stp.Close();
}
MString UVMethods::StPoints::WriteNcFile(const MString& name, const MString& history, int comp) const
MString UVMethods::StPoints::CreateNcFile(const MString& name, const michlib_internal::ParameterListEx& pars, const MString& history, int comp, const TimeData& tdata, bool timedep)
{
int ret;
int ncid;
int dimid;
int xid, yid, tid;
MString text;
tdep = timedep;
ret = nc_create(name.Buf(), NC_CLOBBER | NC_NETCDF4, &ncid);
if(ret != NC_NOERR) return "Can't create netcdf file: " + name;
const MString xname = lonlat ? "longitude" : "x";
const MString yname = lonlat ? "latitude" : "y";
ret = nc_put_att_text(ncid, NC_GLOBAL, "history", history.Len() + 1, history.Buf());
if(ret != NC_NOERR) return "Can't write history attribute in the netcdf file";
nc.Open(name);
if(!nc) return "Can't create netcdf file " + name + ": " + nc.ErrMessage();
ret = nc_def_dim(ncid, "i", N(), &dimid);
if(ret != NC_NOERR) return "Can't create dimension in the netcdf file";
nc.AddAtt("history", history);
nc.AddAtts(pars);
nc.AddDim("i", N());
nc.AddDim("time", tdata.steps.size());
ret = nc_def_var(ncid, lonlat ? "longitude" : "x", NC_FLOAT, 1, &dimid, &xid);
if(ret != NC_NOERR) return "Can't create x variable in the netcdf file";
ret = nc_def_var(ncid, lonlat ? "latitude" : "y", NC_FLOAT, 1, &dimid, &yid);
if(ret != NC_NOERR) return "Can't create y variable in the netcdf file";
nc.AddVar("time", decltype(nc)::Type2NCType<decltype(tdata.steps)::value_type>, "time");
nc.SetComp("time", comp);
nc.AddAtt("time", "standard_name", "time");
nc.AddAtt("time", "long_name", "time");
nc.AddAtt("time", "units", tdata.UnitName());
if(lonlat)
if(tdep)
{
text = "longitude";
ret = nc_put_att_text(ncid, xid, "standard_name", text.Len() + 1, text.Buf());
if(ret != NC_NOERR) return "Can't write standard_name attribute of longitude variable in the netcdf file";
text = "latitude";
ret = nc_put_att_text(ncid, yid, "standard_name", text.Len() + 1, text.Buf());
if(ret != NC_NOERR) return "Can't write standard_name attribute of latitude variable in the netcdf file";
nc.AddVar(xname, NC_FLOAT, "time", "i");
nc.AddVar(yname, NC_FLOAT, "time", "i");
}
else
{
nc.AddVar(xname, NC_FLOAT, "i");
nc.AddVar(yname, NC_FLOAT, "i");
}
nc.SetComp(xname, comp);
nc.SetComp(yname, comp);
ret = nc_def_var_deflate(ncid, xid, 1, 1, comp);
if(ret != NC_NOERR) return "Can't set deflate parameters for x variable in the netcdf file";
ret = nc_def_var_deflate(ncid, yid, 1, 1, comp);
if(ret != NC_NOERR) return "Can't set deflate parameters for y variable in the netcdf file";
ret = nc_def_var(ncid, "type", NC_UBYTE, 1, &dimid, &tid);
if(ret != NC_NOERR) return "Can't create type variable in the netcdf file";
ret = nc_def_var_deflate(ncid, tid, 1, 1, comp);
if(ret != NC_NOERR) return "Can't set deflate parameters for type variable in the netcdf file";
text = "Stationary point type, 0 - saddle, 1 - st. anticicl. focus, 2 - st. knot, 3 - unst. anticicl. focus, 4 - unst. knot, 5 - st. cicl. focus, 6 - unst. cicl. focus";
ret = nc_put_att_text(ncid, tid, "long_name", text.Len() + 1, text.Buf());
if(ret != NC_NOERR) return "Can't write long_name attribute of type variable in the netcdf file";
ret = nc_enddef(ncid);
if(ret != NC_NOERR) return "Can't finish definition of the netcdf file";
if(lonlat)
{
nc.AddAtt(xname, "standard_name", "longitude");
nc.AddAtt(xname, "long_name", "Longitude");
nc.AddAtt(yname, "standard_name", "latitude");
nc.AddAtt(yname, "long_name", "Latitude");
}
else
{
nc.AddAtt(xname, "long_name", "x-coordinate");
nc.AddAtt(yname, "long_name", "y-coordinate");
}
const size_t i = 0, c = N();
float buf[c];
if(tdep)
nc.AddVar("type", NC_UBYTE, "time", "i");
else
nc.AddVar("type", NC_UBYTE, "i");
nc.SetComp("type", comp);
nc.AddAtt("type", "long_name",
"Stationary point type, 0 - saddle, 1 - st. anticicl. focus, 2 - st. knot, 3 - unst. anticicl. focus, 4 - unst. knot, 5 - st. cicl. focus, 6 - unst. cicl. focus");
for(size_t ix = 0; ix < c; ix++) buf[ix] = x[ix];
ret = nc_put_vara_float(ncid, xid, &i, &c, buf);
if(ret != NC_NOERR) return "Can't write x variable in the netcdf file";
nc.WriteVar("time", tdata.steps.data());
for(size_t ix = 0; ix < c; ix++) buf[ix] = y[ix];
ret = nc_put_vara_float(ncid, yid, &i, &c, buf);
if(ret != NC_NOERR) return "Can't write y variable in the netcdf file";
if(!nc) return "Can't set grid in the netcdf file " + name + ": " + nc.ErrMessage();
return "";
}
ret = nc_put_vara_ubyte(ncid, tid, &i, &c, t.data());
if(ret != NC_NOERR) return "Can't write type variable in the netcdf file";
MString UVMethods::StPoints::WriteNcFile(size_t it)
{
const MString xname = lonlat ? "longitude" : "x";
const MString yname = lonlat ? "latitude" : "y";
ret = nc_close(ncid);
if(ret != NC_NOERR) return "Can't close netcdf file";
if(tdep)
{
nc.WriteVar(xname, it, x.data());
nc.WriteVar(yname, it, y.data());
nc.WriteVar("type", it, t.data());
}
else
{
nc.WriteVar(xname, x.data());
nc.WriteVar(yname, y.data());
nc.WriteVar("type", t.data());
}
if(!nc) return MString("Can't write data to the netcdf file: ") + nc.ErrMessage();
return "";
}

197
actions/actionuv.h

@ -70,6 +70,8 @@ class UVMethods
std::vector<real> x, y;
std::vector<michlib::uint1> t;
bool lonlat;
NCFileWBase nc;
bool tdep;
size_t N() const { return t.size(); }
@ -86,9 +88,19 @@ class UVMethods
}
}
void WriteBinBile(const MString& name) const;
void Reset()
{
x.clear();
y.clear();
t.clear();
}
void WriteBinBile(const MString& name, const michlib_internal::ParameterListEx& pars) const;
MString CreateNcFile(const MString& name, const michlib_internal::ParameterListEx& pars, const MString& history, int comp, const TimeData& tdata, bool timedep);
MString WriteNcFile(size_t it);
MString WriteNcFile(const MString& name, const MString& history, int comp) const;
void CloseNcFile() { nc.Close(); }
};
};
@ -105,10 +117,30 @@ template<class D> MString ActionUV::DoAction(const CLArgs& args, D& ds)
michlib_internal::ParameterListEx pars;
pars.UsePrefix("");
pars.SetParameter("source", args.at("source"));
pars.SetParameter("history", args.at("_cmdline"));
VelSource mode = VelSource::AUTOSEL;
if(args.contains("geostrophic"))
{
auto modestr = args.at("geostrophic");
if(modestr == "" || modestr == "ssh")
mode = VelSource::GEOSTROPHICSSH;
else if(modestr == "surf" || modestr == "surface")
mode = VelSource::GEOSTROPHIC;
else if(modestr == "nongeo")
mode = VelSource::STANDART;
else
return "Parameter \"geostrophic\" have incorrect value " + modestr;
}
auto [tindexes, err] = GetTIndexes(ds, args, pars);
if(err.Exist()) return err;
bool average = args.contains("average");
int compress = 3;
if(args.contains("compress")) compress = args.at("compress").ToInt();
std::unique_ptr<const BaseParameters> sourcepars;
if constexpr(ParametersSupported<D>)
{
@ -127,15 +159,40 @@ template<class D> MString ActionUV::DoAction(const CLArgs& args, D& ds)
}
auto p = sourcepars.get();
auto data = ReadUV(ds, p, tindexes);
if(!data) return "Can't read data";
TimeData tdata(ds, tindexes);
// Main file
MString name = args.contains("out") ? args.at("out") : "";
MString outfmt = args.contains("outformat") ? args.at("outformat") : (GetExt(name) == "nc" ? "nc" : "bin");
MString namevel = args.contains("velout") ? args.at("velout") : "";
MString outfmtvel = args.contains("veloutformat") ? args.at("veloutformat") : (GetExt(namevel) == "nc" ? "nc" : "bin");
MString namestp = args.contains("stpout") ? args.at("stpout") : "";
MString outfmtstp = args.contains("stpoutformat") ? args.at("stpoutformat") : (GetExt(namestp) == "nc" ? "nc" : "bin");
MString u = data.Unit().Exist() ? data.Unit() : "unknown", d = data.DUnit().Exist() ? data.DUnit() : "unknown";
size_t shiftx = args.contains("shiftx") ? args.at("shiftx").ToInteger<size_t>() : 0;
size_t shifty = args.contains("shifty") ? args.at("shifty").ToInteger<size_t>() : 0;
size_t skipx = args.contains("skipx") ? args.at("skipx").ToInteger<size_t>() : 1;
size_t skipy = args.contains("skipy") ? args.at("skipy").ToInteger<size_t>() : 1;
if(!average && tindexes.size() > 1 && ((name.Exist() && outfmt == "bin") || (namevel.Exist() && outfmtvel == "bin") || (namestp.Exist() && outfmtstp == "bin")))
return "Multiple time moments does'nt supported by the bin format";
bool headwrited = false;
MString velunit, distunit;
NCFileW fw, fwfilt;
StPoints stp(true);
for(size_t it = 0; it < tindexes.size(); it++)
{
auto data = average ? ReadUV(ds, p, tindexes, mode) : ReadUV(ds, p, tindexes[it], mode);
if(!headwrited)
{
velunit = data.Unit().Exist() ? data.Unit() : "unknown";
distunit = data.DUnit().Exist() ? data.DUnit() : "unknown";
}
// Main file
if(name.Exist())
{
if(outfmt == "bin")
@ -144,13 +201,13 @@ template<class D> MString ActionUV::DoAction(const CLArgs& args, D& ds)
fw.Create(name, 9);
fw.SetColumnName(1, "Longitude");
fw.SetColumnName(2, "Latitude");
fw.SetColumnName(3, "u, " + u);
fw.SetColumnName(4, "v, " + u);
fw.SetColumnName(5, "Div, (" + u + ")/" + d);
fw.SetColumnName(6, "Rot, (" + u + ")/" + d);
fw.SetColumnName(7, "Okubo-Weiss parameter, (" + u + ")2/" + d + "2");
fw.SetColumnName(8, "Kinetic energy, (" + u + ")2");
fw.SetColumnName(9, "Eddy kinetic energy, (" + u + ")2");
fw.SetColumnName(3, "u, " + velunit);
fw.SetColumnName(4, "v, " + velunit);
fw.SetColumnName(5, "Div, (" + velunit + ")/" + distunit);
fw.SetColumnName(6, "Rot, (" + velunit + ")/" + distunit);
fw.SetColumnName(7, "Okubo-Weiss parameter, (" + velunit + ")2/" + distunit + "2");
fw.SetColumnName(8, "Kinetic energy, (" + velunit + ")2");
fw.SetColumnName(9, "Eddy kinetic energy, (" + velunit + ")2");
fw.SetParameters(pars);
for(size_t i = 0; i < data.N(); i++)
@ -170,58 +227,48 @@ template<class D> MString ActionUV::DoAction(const CLArgs& args, D& ds)
}
else if(outfmt == "nc" || outfmt == "netcdf")
{
int compress = 3;
NCFileW fw;
MString err;
if(args.contains("compress")) compress = args.at("compress").ToInt();
if(!err.Exist()) err = fw.Create(data, name, args.at("_cmdline"), compress);
if(!err.Exist()) err = fw.AddVariable("u", "", "Eastward velocity", u, "");
if(!err.Exist()) err = fw.AddVariable("v", "", "Northward velocity", u, "");
if(!err.Exist()) err = fw.AddVariable("div", "", "Velocity divergence", "(" + u + ")/" + d, "");
if(!err.Exist()) err = fw.AddVariable("rot", "", "Velocity rotor", "(" + u + ")/" + d, "");
if(!err.Exist()) err = fw.AddVariable("ow", "", "Okubo-Weiss parameter", "(" + u + ")2/" + d + "2", "");
if(!err.Exist()) err = fw.AddVariable("ke", "", "Squared velocity module, u^2+v^2", "(" + u + ")2", "");
if(!err.Exist()) err = fw.AddVariable("eke", "", "Squared velocity dispersion aka eddy kinetic energy, <u^2+v^2>-<u>^2-<v>^2", "(" + u + ")2", "");
if(!err.Exist()) err = fw.WriteGrid(data);
if(!err.Exist()) err = fw.WriteVariable(data, "u", [&data = std::as_const(data)](size_t i, size_t j) { return data.U(i, j); });
if(!err.Exist()) err = fw.WriteVariable(data, "v", [&data = std::as_const(data)](size_t i, size_t j) { return data.V(i, j); });
if(!err.Exist()) err = fw.WriteVariable(data, "div", [&data = std::as_const(data)](size_t i, size_t j) { return data.Div(i, j); });
if(!err.Exist()) err = fw.WriteVariable(data, "rot", [&data = std::as_const(data)](size_t i, size_t j) { return data.Rot(i, j); });
if(!err.Exist()) err = fw.WriteVariable(data, "ow", [&data = std::as_const(data)](size_t i, size_t j) { return data.OW(i, j); });
if(!err.Exist()) err = fw.WriteVariable(data, "ke", [&data = std::as_const(data)](size_t i, size_t j) { return data.U2(i, j); });
if(!err.Exist() && !headwrited) err = fw.Create(data, name, compress);
if(!err.Exist() && !headwrited) err = fw.AddTimeData(tdata, !average);
if(!err.Exist() && !headwrited) err = fw.AddAtts(pars);
if(!err.Exist() && !headwrited) err = fw.AddVariable("u", "", "Eastward velocity", velunit, "");
if(!err.Exist() && !headwrited) err = fw.AddVariable("v", "", "Northward velocity", velunit, "");
if(!err.Exist() && !headwrited) err = fw.AddVariable("div", "", "Velocity divergence", "(" + velunit + ")/" + distunit, "");
if(!err.Exist() && !headwrited) err = fw.AddVariable("rot", "", "Velocity rotor", "(" + velunit + ")/" + distunit, "");
if(!err.Exist() && !headwrited) err = fw.AddVariable("ow", "", "Okubo-Weiss parameter", "(" + velunit + ")2/" + distunit + "2", "");
if(!err.Exist() && !headwrited) err = fw.AddVariable("ke", "", "Squared velocity module, u^2+v^2", "(" + velunit + ")2", "");
if(!err.Exist() && !headwrited) err = fw.AddVariable("eke", "", "Squared velocity dispersion aka eddy kinetic energy, <u^2+v^2>-<u>^2-<v>^2", "(" + velunit + ")2", "");
if(!err.Exist() && !headwrited) err = fw.WriteGrid(data);
if(!err.Exist()) err = fw.WriteVariable(data, "u", [&data = std::as_const(data)](size_t i, size_t j) { return data.U(i, j); }, it);
if(!err.Exist()) err = fw.WriteVariable(data, "v", [&data = std::as_const(data)](size_t i, size_t j) { return data.V(i, j); }, it);
if(!err.Exist()) err = fw.WriteVariable(data, "div", [&data = std::as_const(data)](size_t i, size_t j) { return data.Div(i, j); }, it);
if(!err.Exist()) err = fw.WriteVariable(data, "rot", [&data = std::as_const(data)](size_t i, size_t j) { return data.Rot(i, j); }, it);
if(!err.Exist()) err = fw.WriteVariable(data, "ow", [&data = std::as_const(data)](size_t i, size_t j) { return data.OW(i, j); }, it);
if(!err.Exist()) err = fw.WriteVariable(data, "ke", [&data = std::as_const(data)](size_t i, size_t j) { return data.U2(i, j); }, it);
if(!err.Exist())
err = fw.WriteVariable(data, "eke", [&data = std::as_const(data)](size_t i, size_t j) { return data.U2(i, j) - (data.U(i, j) * data.U(i, j) + data.V(i, j) * data.V(i, j)); });
err = fw.WriteVariable(
data, "eke", [&data = std::as_const(data)](size_t i, size_t j) { return data.U2(i, j) - (data.U(i, j) * data.U(i, j) + data.V(i, j) * data.V(i, j)); }, it);
if(err.Exist()) return err;
fw.Close();
}
else
return "Unknown format: " + outfmt;
}
// Filtered vectors file
name = args.contains("velout") ? args.at("velout") : "";
outfmt = args.contains("veloutformat") ? args.at("veloutformat") : (GetExt(name) == "nc" ? "nc" : "bin");
if(name.Exist())
// Filtered file
if(namevel.Exist())
{
size_t shiftx = args.contains("shiftx") ? args.at("shiftx").ToInteger<size_t>() : 0;
size_t shifty = args.contains("shifty") ? args.at("shifty").ToInteger<size_t>() : 0;
size_t skipx = args.contains("skipx") ? args.at("skipx").ToInteger<size_t>() : 1;
size_t skipy = args.contains("skipy") ? args.at("skipy").ToInteger<size_t>() : 1;
Sparser<decltype(data)> sdata(data, shiftx, shifty, skipx, skipy);
if(outfmt == "bin")
if(outfmtvel == "bin")
{
BFileW vel;
vel.Create(name, 4);
vel.Create(namevel, 4);
vel.SetColumnName(1, "Longitude");
vel.SetColumnName(2, "Latitude");
vel.SetColumnName(3, "u, " + u);
vel.SetColumnName(4, "v, " + u);
vel.SetColumnName(3, "u, " + velunit);
vel.SetColumnName(4, "v, " + velunit);
vel.SetParameters(pars);
for(size_t ix = 0; ix < sdata.Nx(); ix++)
for(size_t iy = 0; iy < sdata.Ny(); iy++)
@ -234,50 +281,50 @@ template<class D> MString ActionUV::DoAction(const CLArgs& args, D& ds)
vel.Finalize();
vel.Close();
}
else if(outfmt == "nc" || outfmt == "netcdf")
else if(outfmtvel == "nc" || outfmtvel == "netcdf")
{
int compress = 3;
NCFileW fw;
MString err;
if(args.contains("compress")) compress = args.at("compress").ToInt();
if(!err.Exist()) err = fw.Create(sdata, name, args.at("_cmdline"), compress);
if(!err.Exist()) err = fw.AddVariable("u", "", "Eastward velocity", u, "");
if(!err.Exist()) err = fw.AddVariable("v", "", "Northward velocity", u, "");
if(!err.Exist()) err = fw.WriteGrid(sdata);
if(!err.Exist() && !headwrited) err = fwfilt.Create(sdata, namevel, compress);
if(!err.Exist() && !headwrited) err = fwfilt.AddTimeData(tdata, !average);
if(!err.Exist() && !headwrited) err = fwfilt.AddAtts(pars);
if(!err.Exist() && !headwrited) err = fwfilt.AddVariable("u", "", "Eastward velocity", velunit, "");
if(!err.Exist() && !headwrited) err = fwfilt.AddVariable("v", "", "Northward velocity", velunit, "");
if(!err.Exist() && !headwrited) err = fwfilt.WriteGrid(sdata);
if(!err.Exist()) err = fw.WriteVariable(sdata, "u", [&data = std::as_const(sdata)](size_t i, size_t j) { return data.U(i, j); });
if(!err.Exist()) err = fw.WriteVariable(sdata, "v", [&data = std::as_const(sdata)](size_t i, size_t j) { return data.V(i, j); });
if(!err.Exist()) err = fwfilt.WriteVariable(sdata, "u", [&data = std::as_const(sdata)](size_t i, size_t j) { return data.U(i, j); }, it);
if(!err.Exist()) err = fwfilt.WriteVariable(sdata, "v", [&data = std::as_const(sdata)](size_t i, size_t j) { return data.V(i, j); }, it);
if(err.Exist()) return err;
fw.Close();
}
else
return "Unknown format: " + outfmt;
return "Unknown format: " + outfmtvel;
}
// Stationary points
name = args.contains("stpout") ? args.at("stpout") : "";
outfmt = args.contains("stpoutformat") ? args.at("stpoutformat") : (GetExt(name) == "nc" ? "nc" : "bin");
if(name.Exist())
if(namestp.Exist())
{
StPoints p(true);
stp.Reset();
for(size_t ix = 0; ix < data.Nx() - 1; ix++)
for(size_t iy = 0; iy < data.Ny() - 1; iy++) p.Add(data.StablePoints(ix, iy));
for(size_t iy = 0; iy < data.Ny() - 1; iy++) stp.Add(data.StablePoints(ix, iy));
if(outfmt == "bin")
p.WriteBinBile(name);
else if(outfmt == "nc" || outfmt == "netcdf")
if(outfmtstp == "bin")
stp.WriteBinBile(namestp, pars);
else if(outfmtstp == "nc" || outfmtstp == "netcdf")
{
int compress = args.contains("compress") ? args.at("compress").ToInt() : 3;
MString err = p.WriteNcFile(name, args.at("_cmdline"), compress);
MString err;
if(!err.Exist() && !headwrited) err = stp.CreateNcFile(namestp, pars, args.at("_cmdline"), compress, tdata, !average);
if(!err.Exist()) err = stp.WriteNcFile(it);
if(err.Exist()) return err;
}
else
return "Unknown format: " + outfmt;
}
if(average) break;
headwrited = true;
}
fw.Close();
fwfilt.Close();
stp.CloseNcFile();
return "";
};

195
include/actiondep.h

@ -5,6 +5,8 @@
#include "mregex.h"
#include "traits.h"
#include "uvdata.h"
#include <array>
#include <utility>
using michlib::MDateTime;
@ -12,7 +14,7 @@ using michlib::MDateTime;
#define ADD_ACTION(actclass, actname, suptest, ...) ADD ACTION CLASS: actclass
#else
#define ADD_ACTION(actclass, actname, suptest, ...) \
class Action##actclass __VA_OPT__( : public) __VA_ARGS__ \
class Action##actclass __VA_OPT__( : protected) __VA_ARGS__ \
{ \
public: \
static constexpr const char* name = #actname; \
@ -21,6 +23,49 @@ using michlib::MDateTime;
};
#endif
enum class VelSource
{
AUTOSEL,
STANDART,
GEOSTROPHIC,
GEOSTROPHICSSH
};
struct TimeData
{
static constexpr auto stepunits = std::to_array<uint>({3600 * 24, 3600, 60, 1});
static constexpr std::array<const std::string, stepunits.size()> stepnames{"days", "hours", "minutes", "seconds"};
MDateTime refdate;
size_t unitind;
std::vector<michlib::int4> steps;
template<class D> TimeData(const D& data, const TIndex& tindexes): refdate(), unitind(stepunits.size()), steps(tindexes.size())
{
if(steps.size() == 0) return;
refdate = data.Time(tindexes[0]);
unitind = 0;
for(size_t i = 0; i < steps.size(); i++)
{
auto delta = data.Time(tindexes[i]) - refdate;
while(delta.Seconds() % stepunits[unitind] != 0) unitind++;
}
for(size_t i = 0; i < steps.size(); i++)
{
auto delta = data.Time(tindexes[i]) - refdate;
steps[i] = michlib::int_cast<decltype(steps)::value_type>(delta.Seconds() / stepunits[unitind]);
}
}
MString UnitName() const
{
MString out = stepnames[unitind].c_str();
return out + " since " + refdate.ToString();
}
};
template<class D> size_t GetTIndex(const D& data, const MDateTime& t)
{
size_t nt = data.NTimes();
@ -45,10 +90,14 @@ template<class D> std::pair<TIndex, MString> GetTIndexes(const D& data, const CL
MString regex = args.at("time");
MDateTime time;
if(time.FromString(regex)) return {TIndex(1, GetTIndex(data, 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, data.NTimes() - 1), ""}; // Last time
if(time.FromString(regex)) // Time, not regex
tindexes.push_back(GetTIndex(data, time));
else if(regex == "BEGIN" || regex == "BEG" || regex == "FIRST") // First time
tindexes.push_back(0);
else if(regex == "END" || regex == "LAST") // Last time
tindexes.push_back(data.NTimes() - 1);
else // Regular expression
{
michlib::RegExpSimple reg(regex.Buf());
if(reg.Compile() != 0) return {tindexes, "Bad regular expression: " + regex};
@ -57,9 +106,11 @@ template<class D> std::pair<TIndex, MString> GetTIndexes(const D& data, const CL
MString date = data.Time(i).ToString();
if(reg.Match(date.Buf())) tindexes.push_back(i);
}
}
if(tindexes.size() == 0) return {tindexes, "There are no times matching the regular expression: " + regex};
if(tindexes.size() == 1)
pars.SetParameter("time", data.Time(tindexes[0]).ToString());
pars.SetParameter("time", data.Time(tindexes[0]).ToTString());
else
pars.SetParameter("timeregex", args.at("time"));
}
@ -79,40 +130,23 @@ template<class D> std::pair<TIndex, MString> GetTIndexes(const D& data, const CL
if(beg > data.Time(nt - 1)) return {tindexes, "Begin time " + b.ToTString() + " is greater then end time in the dataset " + data.Time(nt - 1).ToTString()};
if(end < data.Time(0)) return {tindexes, "End time " + e.ToTString() + " is lesser then begin time in the dataset " + data.Time(0).ToTString()};
size_t ib = 0, ie = nt - 1;
for(size_t i = 0; i < nt; i++)
if(data.Time(i) >= beg)
{
ib = i;
break;
}
for(size_t i = nt; i != 0; i--)
if(data.Time(i - 1) <= end)
{
ie = i - 1;
break;
}
tindexes.resize(ie - ib + 1);
for(size_t i = 0; i < ie - ib + 1; i++) tindexes[i] = i + ib;
if(data.Time(i) >= beg && data.Time(i) <= end) tindexes.push_back(i);
if(tindexes.size() == 0) return {tindexes, "There are no times between " + b.ToString() + " and " + e.ToString()};
pars.SetParameter("timeb", b.ToString());
pars.SetParameter("timee", e.ToString());
pars.SetParameter("timeb", b.ToTString());
pars.SetParameter("timee", e.ToTString());
}
std::ranges::sort(tindexes, [&data = std::as_const(data)](size_t a, size_t b) { return data.Time(a) < data.Time(b); });
return {tindexes, ""};
}
template<class D> std::vector<ReadType<D>> Read(const D& data, const std::vector<MString>& vnames, const BaseParameters* p, const TIndex& tindex)
template<class D> std::vector<ReadType<D>> Read(const D& data, const std::vector<MString>& vnames, const BaseParameters* p, size_t ind)
{
using RT = ReadType<D>;
size_t ind;
std::vector<RT> out;
if(tindex.size() == 1)
{
ind = tindex[0];
michlib::message("Time: " + data.Time(ind).ToTString());
std::map<MString, RT> cache;
for(const auto& vname: vnames)
@ -125,7 +159,17 @@ template<class D> std::vector<ReadType<D>> Read(const D& data, const std::vector
if(!res) return out;
}
for(size_t i = 0; i < vnames.size(); i++) out.emplace_back(std::move(cache[vnames[i]]));
}
return out;
}
template<class D> std::vector<ReadType<D>> Read(const D& data, const std::vector<MString>& vnames, const BaseParameters* p, const TIndex& tindex)
{
using RT = ReadType<D>;
size_t ind;
std::vector<RT> out;
if(tindex.size() == 1)
return Read(data, vnames, p, tindex[0]);
else
{
std::vector<Averager<RT>> aver(vnames.size());
@ -151,29 +195,102 @@ template<class D> std::vector<ReadType<D>> Read(const D& data, const std::vector
return out;
}
template<class D> UVData<ReadType<D>> ReadUV(const D& data, const BaseParameters* p, size_t ind)
template<class D> UVData<ReadType<D>> ReadUV(const D& data, const BaseParameters* p, size_t ind, VelSource usegeo)
{
using RT = ReadType<D>;
using UV = UVData<RT>;
michlib::message("Time: " + data.Time(ind).ToTString());
std::map<MString, RT> cache;
bool res = false;
MString uname, vname;
switch(usegeo)
{
case(VelSource::AUTOSEL):
{
if(data.CheckVar("u") == VarPresence::INTERNAL && data.CheckVar("v") == VarPresence::INTERNAL)
{
uname = "u";
vname = "v";
}
else
{
uname = "ugs";
vname = "vgs";
}
break;
}
case(VelSource::STANDART):
{
uname = "u";
vname = "v";
break;
}
case(VelSource::GEOSTROPHIC):
{
uname = "ugs";
vname = "vgs";
break;
}
case(VelSource::GEOSTROPHICSSH):
{
uname = "ugeo";
vname = "vgeo";
break;
}
}
if constexpr(ReadPSupported<D>)
res = data.Read("u", cache, p, ind) && data.Read("v", cache, p, ind);
res = data.Read(uname, cache, p, ind) && data.Read(vname, cache, p, ind);
else if constexpr(ReadSupported<D>)
res = data.Read("u", cache, ind) && data.Read("v", cache, ind);
res = data.Read(uname, cache, ind) && data.Read(vname, cache, ind);
if(!res) return UV();
return UV(cache.at("u"), cache.at("v"));
return UV(cache.at(uname), cache.at(vname));
}
template<class D> UVData<ReadType<D>> ReadUV(const D& data, const BaseParameters* p, const TIndex& tindex)
template<class D> UVData<ReadType<D>> ReadUV(const D& data, const BaseParameters* p, const TIndex& tindex, VelSource usegeo)
{
using RT = ReadType<D>;
using UV = UVData<RT>;
MString uname, vname;
switch(usegeo)
{
case(VelSource::AUTOSEL):
{
if(data.CheckVar("u") == VarPresence::INTERNAL && data.CheckVar("v") == VarPresence::INTERNAL)
{
uname = "u";
vname = "v";
}
else
{
uname = "ugs";
vname = "vgs";
}
break;
}
case(VelSource::STANDART):
{
uname = "u";
vname = "v";
break;
}
case(VelSource::GEOSTROPHIC):
{
uname = "ugs";
vname = "vgs";
break;
}
case(VelSource::GEOSTROPHICSSH):
{
uname = "ugeo";
vname = "vgeo";
break;
}
}
if(tindex.size() == 1)
return ReadUV(data, p, tindex[0]);
return ReadUV(data, p, tindex[0], usegeo);
else
{
Averager<UV> out;
@ -187,12 +304,12 @@ template<class D> UVData<ReadType<D>> ReadUV(const D& data, const BaseParameters
std::map<MString, RT> cache;
bool res = false;
if constexpr(ReadPSupported<D>)
res = data.Read("u", cache, p, ind) && data.Read("v", cache, p, ind);
res = data.Read(uname, cache, p, ind) && data.Read(vname, cache, p, ind);
else if constexpr(ReadSupported<D>)
res = data.Read("u", cache, ind) && data.Read("v", cache, ind);
res = data.Read(uname, cache, ind) && data.Read(vname, cache, ind);
if(!res) return UV();
UV dat(cache.at("u"), cache.at("v"));
UV dat(cache.at(uname), cache.at(vname));
if(dat)
out.Add(std::move(dat));
else

18
include/basedata.h

@ -15,12 +15,26 @@ struct Region
real lonb, lone, latb, late;
};
struct GridPointLocation
struct GridPoint
{
size_t ix = 0, iy = 0;
real x = -1.0, y = -1.0;
bool Valid() const { return x >= 0.0 && y >= 0.0; }
};
struct Point2D
{
size_t ix, iy;
real x, y;
};
enum class VarPresence
{
NONE,
INTERNAL,
DERIVED
};
class BaseData
{
protected:

447
include/cache.h

@ -0,0 +1,447 @@
#pragma once
#include "GPL.h"
#include "mirrorfuncs.h"
#include <functional>
#include <libpq-fe.h>
#include <optional>
#include <sqlite3.h>
#include <time.h>
#include <variant>
using michlib::GPL;
using michlib::int1;
using michlib::int4;
using michlib::int_cast;
using michlib::MString;
using michlib::pointer_cast;
class SQLiteConnection
{
public:
using DBType = sqlite3*;
using FuncType = std::function<void(DBType)>;
private:
static DBType db;
static size_t count;
static std::vector<FuncType> destructs;
public:
SQLiteConnection()
{
count++;
if(db == nullptr)
{
MString oldprefix = GPL.UsePrefix("SQLITE");
MString name = GPL.ParameterSValue("db", "");
GPL.UsePrefix(oldprefix);
auto ret = sqlite3_open(name.Buf(), &db);
if(ret != SQLITE_OK)
{
sqlite3_close(db);
db = nullptr;
}
}
}
SQLiteConnection([[maybe_unused]] const SQLiteConnection& sq): SQLiteConnection() {}
SQLiteConnection(SQLiteConnection&&) = default;
SQLiteConnection& operator=([[maybe_unused]] const SQLiteConnection& sq)
{
*this = {};
return *this;
}
SQLiteConnection& operator=(SQLiteConnection&&) = default;
~SQLiteConnection()
{
if(count == 0) michlib::errmessage("Destructor of SQLiteConnection called on count==0");
if(count > 1)
count--;
else
{
count = 0;
if(db != nullptr)
{
for(const auto& f: destructs) f(db);
sqlite3_close(db);
}
db = nullptr;
}
}
static void AddDestructor(FuncType&& f) { destructs.emplace_back(std::move(f)); }
operator DBType() const { return db; }
static DBType GetDB() { return db; }
explicit operator bool() const { return db != nullptr; }
};
class PostgreSQLConnection
{
public:
using DBType = PGconn*;
using FuncType = std::function<void(DBType)>;
private:
static DBType conn;
static size_t count;
static std::vector<FuncType> destructs;
public:
PostgreSQLConnection()
{
count++;
if(conn == nullptr)
{
MString oldprefix = GPL.UsePrefix("POSTGRES");
MString name = GPL.ParameterSValue("connection", "");
GPL.UsePrefix(oldprefix);
conn = PQconnectdb(name.Buf());
if(PQstatus(conn) != CONNECTION_OK)
{
michlib::errmessage(PQerrorMessage(conn));
PQfinish(conn);
conn = nullptr;
}
}
}
PostgreSQLConnection([[maybe_unused]] const PostgreSQLConnection& pq): PostgreSQLConnection() {}
PostgreSQLConnection(PostgreSQLConnection&&) = default;
PostgreSQLConnection& operator=([[maybe_unused]] const PostgreSQLConnection& pq)
{
*this = {};
return *this;
}
PostgreSQLConnection& operator=(PostgreSQLConnection&&) = default;
~PostgreSQLConnection()
{
if(count == 0) michlib::errmessage("Destructor of PostgreSQLConnection called on count==0");
if(count > 1)
count--;
else
{
count = 0;
if(conn != nullptr)
{
for(const auto& f: destructs) f(conn);
PQfinish(conn);
}
conn = nullptr;
}
}
static void AddDestructor(FuncType&& f) { destructs.emplace_back(std::move(f)); }
operator DBType() const { return conn; }
static DBType GetDB() { return conn; }
explicit operator bool() const { return conn != nullptr; }
};
class GenericCache
{
public:
virtual bool Put(const MString& key, const MString& value, size_t ttl) const = 0;
virtual std::pair<MString, bool> Get(const MString& key) const = 0;
virtual ~GenericCache() {}
};
class FakeCache: public GenericCache
{
public:
virtual bool Put([[maybe_unused]] const MString& key, [[maybe_unused]] const MString& value, [[maybe_unused]] size_t ttl) const override { return false; }
virtual std::pair<MString, bool> Get([[maybe_unused]] const MString& key) const override { return {"", false}; }
virtual ~FakeCache() override {}
};
class SQLiteCache: public GenericCache
{
static bool regdest;
SQLiteConnection db;
public:
bool Init()
{
if(!db) return false;
if(!regdest)
{
// Create table
sqlite3_stmt* sqst;
int i;
i = sqlite3_prepare_v2(db,
"CREATE TABLE IF NOT EXISTS `cache`('key' TEXT PRIMARY KEY ON CONFLICT REPLACE NOT NULL ON CONFLICT FAIL, 'value' BLOB NOT NULL ON CONFLICT FAIL, "
"'exptime' INTEGER NOT NULL ON CONFLICT FAIL) WITHOUT ROWID, STRICT;",
-1, &sqst, 0);
i = sqlite3_step(sqst);
if(i != SQLITE_DONE)
{
sqlite3_finalize(sqst);
return false;
}
sqlite3_finalize(sqst);
sqlite3_busy_timeout(db, 1000);
db.AddDestructor(
[](SQLiteConnection::DBType db)
{
sqlite3_stmt* sqst = nullptr;
int i = SQLITE_OK;
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "DELETE from `cache` WHERE exptime<?1;", -1, &sqst, 0);
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 1, time(nullptr));
if(i == SQLITE_OK) i = sqlite3_step(sqst);
sqlite3_finalize(sqst);
});
regdest = true;
}
return true;
}
virtual bool Put(const MString& key, const MString& value, size_t ttl) const override
{
if(!*this) return false;
sqlite3_stmt* sqst = nullptr;
int i = SQLITE_OK;
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "INSERT OR REPLACE into `cache` VALUES(?1,?2,?3);", -1, &sqst, 0);
if(i == SQLITE_OK) i = sqlite3_bind_text(sqst, 1, key.Buf(), -1, SQLITE_STATIC);
if(i == SQLITE_OK) i = sqlite3_bind_blob64(sqst, 2, value.Buf(), value.Len(), SQLITE_STATIC);
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 3, time(nullptr) + ttl);
if(i == SQLITE_OK) i = sqlite3_step(sqst);
sqlite3_finalize(sqst);
return i == SQLITE_OK;
}
virtual std::pair<MString, bool> Get(const MString& key) const override
{
if(!*this) return {"", false};
sqlite3_stmt* sqst = nullptr;
int i = SQLITE_OK;
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "SELECT value from `cache` WHERE key=?1 AND exptime>?2;", -1, &sqst, 0);
if(i == SQLITE_OK) i = sqlite3_bind_text(sqst, 1, key.Buf(), -1, SQLITE_STATIC);
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 2, time(nullptr));
if(i == SQLITE_OK) i = sqlite3_step(sqst);
if(i == SQLITE_ROW)
{
auto p = sqlite3_column_blob(sqst, 0);
auto sz = sqlite3_column_bytes(sqst, 0);
if(p != nullptr)
{
MString out(p, sz);
sqlite3_finalize(sqst);
return {std::move(out), true};
}
}
sqlite3_finalize(sqst);
return {"", false};
}
virtual ~SQLiteCache() override = default;
explicit operator bool() const { return db != nullptr; }
};
class PostgreSQLHelpers
{
static constexpr time_t postgresepoch = 946648800;
class PGResultRAIIDT
{
public:
// TODO: make static
void operator()(PGresult* res) { PQclear(res); }
};
protected:
PostgreSQLConnection conn;
// Convert Postgres binary representation of timestamp to Unix epoch seconds. Microseconds ignored
static time_t raw2epoch(time_t raw) { return Invert(raw) / 1000000 + postgresepoch; }
// Convert Unix epoch time to Postres binary representation
static time_t epoch2raw(time_t epoch) { return Invert((epoch - postgresepoch) * 1000000); }
class PGresultRAII: public std::unique_ptr<PGresult, PGResultRAIIDT>
{
public:
PGresultRAII() = default;
PGresultRAII(PGresult* res): std::unique_ptr<PGresult, PGResultRAIIDT>(res) {}
operator PGresult*() const { return get(); }
};
bool CheckCon() const
{
if(!*this) return false;
if(PQstatus(conn) == CONNECTION_OK) return true;
PQreset(conn);
return PQstatus(conn) == CONNECTION_OK;
}
template<class D> static D Invert(D d)
{
using michlib::int1;
D out;
Invert(&d, &out, sizeof(D));
return out;
}
static void Invert(const void* src, void* dst, size_t sz)
{
if(sz == 0) return;
const int1* pin = pointer_cast<const int1*>(src);
int1* pout = pointer_cast<int1*>(dst);
for(size_t i = 0; i < sz; i++) pout[sz - i - 1] = pin[i];
}
public:
explicit operator bool() const { return conn != nullptr; }
};
class PostgreSQLCache: public GenericCache, public PostgreSQLHelpers
{
static bool regdest;
public:
bool Init()
{
if(!conn) return false;
if(!regdest)
{
PGresultRAII res;
// Create table
res = PQexec(conn, "SET client_min_messages=WARNING;");
res = PQexec(conn, "CREATE TABLE IF NOT EXISTS cache(key TEXT PRIMARY KEY NOT NULL, value BYTEA, exptime TIMESTAMP(0) NOT NULL);");
if(PQresultStatus(res) != PGRES_COMMAND_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
}
res = PQexec(conn, "SET client_min_messages=NOTICE;");
conn.AddDestructor([](PostgreSQLConnection::DBType conn) { PGresultRAII res = PQexec(conn, "DELETE FROM cache WHERE exptime<localtimestamp;"); });
regdest = true;
}
return true;
}
virtual bool Put(const MString& key, const MString& value, size_t ttl) const override
{
if(!CheckCon()) return false;
auto interval = michlib::int_cast<michlib::int8>(ttl);
michlib::int8 rinterval = Invert(interval);
const char* params[] = {key.Buf(), value.Buf(), pointer_cast<const char*>(&rinterval)};
int plens[] = {int_cast<int>(key.Len()), int_cast<int>(value.Len()), sizeof(rinterval)};
int pfor[] = {0, 1, 1};
PGresultRAII res = PQexecParams(conn,
"INSERT INTO cache(key,value,exptime) VALUES($1,$2,localtimestamp + ($3::bigint ||' seconds')::interval)"
"ON CONFLICT(key) DO UPDATE SET value=EXCLUDED.value, exptime=EXCLUDED.exptime;",
3, nullptr, params, plens, pfor, 1);
if(PQresultStatus(res) != PGRES_COMMAND_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
return false;
}
return true;
}
virtual std::pair<MString, bool> Get(const MString& key) const override
{
if(!CheckCon()) return {"", false};
const char* params[] = {key.Buf()};
int plens[] = {int_cast<int>(key.Len())};
int pfor[] = {0};
PGresultRAII res = PQexecParams(conn, "SELECT value from cache WHERE key=$1::text AND exptime>localtimestamp;", 1, nullptr, params, plens, pfor, 1);
if(PQresultStatus(res) != PGRES_TUPLES_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
return {"", false};
}
else if(PQntuples(res) == 0)
return {"", false};
MString val(PQgetvalue(res, 0, 0), PQgetlength(res, 0, 0));
return {std::move(val), true};
}
virtual ~PostgreSQLCache() override = default;
};
inline GenericCache* CreateCache(const MString& cachedesc)
{
auto i = cachedesc.GetPos(':');
auto name = i == 0 ? cachedesc : cachedesc.SubStr(1, i - 1);
auto par = i == 0 ? "" : cachedesc.SubStr(i + 1, cachedesc.Len() - i);
if(name == "no") return new FakeCache;
if(name == "sqlite")
{
auto ret = new SQLiteCache;
ret->Init();
if(*ret) return ret;
delete ret;
}
if(name == "postgre" || name == "postgres" || name == "postgresql")
{
auto ret = new PostgreSQLCache;
ret->Init();
if(*ret) return ret;
delete ret;
}
return nullptr;
}
class FileInfoCache: public PostgreSQLHelpers
{
public:
using DataType = std::optional<MString>;
using CallbackType = std::function<DataType(const MString&)>;
private:
static bool regdest;
CallbackType readfunc;
MString dir;
int4 dirid;
FileInfoCache() = delete;
CallbackType::result_type GetData(const MString& fname) const { return readfunc(dir + "/" + fname); }
void GetDirId();
public:
FileInfoCache(CallbackType&& readfunc_, const MString& dir_);
Error UpdateCache(bool force = false) const;
DataType GetInfo(const MString& name) const;
};

54
include/copcat.h

@ -0,0 +1,54 @@
#pragma once
#include "cache.h"
#include "curlfuncs.h"
#include "merrors.h"
#include <json/json.h>
using michlib::Error;
using michlib::RetVal;
class CopernicusCatalog
{
static const MString caturl;
std::unique_ptr<GenericCache> cache;
CURLRAII chandle;
Json::Value catalog;
// Download catalog
Error GetCatalog();
// Asset url from dataset
RetVal<MString> AssetURL(const MString& prod, const MString& dataset, const MString& asset) const;
public:
CopernicusCatalog();
// Download JSON from url
RetVal<Json::Value> GetJSON(const MString& url) const;
// List of products
RetVal<std::vector<MString>> ProductList() const;
// List of datasets in product
RetVal<std::vector<MString>> DatasetList(const MString& prod) const;
// URL of product
RetVal<MString> ProductURL(const MString& prod) const;
// URL of dataset
RetVal<MString> DatasetURL(const MString& prod, const MString& dataset) const;
// URL of native data (files) in dataset
RetVal<MString> DatasetNativeURL(const MString& prod, const MString& dataset) const { return AssetURL(prod, dataset, "native"); }
// URL of timechuncked data (files) in dataset
RetVal<MString> DatasetTimeURL(const MString& prod, const MString& dataset) const { return AssetURL(prod, dataset, "timeChunked"); }
// URL of geochuncked data (files) in dataset
RetVal<MString> DatasetGeoURL(const MString& prod, const MString& dataset) const { return AssetURL(prod, dataset, "geoChunked"); }
bool Valid() const { return catalog.isObject(); }
explicit operator bool() const { return Valid(); }
};

36
include/curlfuncs.h

@ -0,0 +1,36 @@
#pragma once
#include "MString.h"
#include <curl/curl.h>
#include <memory>
using michlib::MString;
class CURLRAIIDT
{
public:
// TODO: make static
void operator()(CURL* c) { curl_easy_cleanup(c); }
};
class CURLRAII: public std::unique_ptr<CURL, CURLRAIIDT>
{
char err[CURL_ERROR_SIZE];
public:
CURLRAII()
{
reset(curl_easy_init());
curl_easy_setopt(*this, CURLOPT_ERRORBUFFER, err);
}
operator CURL*() const { return get(); }
const char* Err() const { return err; }
};
// Curl writeback function, write to MString
size_t Write2String(char* ptr, size_t size, size_t n, void* data);
// Curl writeback function, write to file descriptor
size_t Write2File(char* ptr, size_t size, size_t n, void* data);
// Get content of url to MString
std::pair<MString, CURLcode> GetUrl(const CURLRAII& chandle, const MString& url);

51
include/interpolation.h

@ -0,0 +1,51 @@
#pragma once
#include "basedata.h"
#include "comdefs.h"
using michlib::real;
template<class D> class LinearInterpolator: public D
{
public:
LinearInterpolator(D&& d): D(std::move(d)) {}
real operator()(const struct Point2D& in) const
{
const auto gp = D::GridPos(in.x, in.y);
if(!gp.Valid()) return NAN;
if(gp.x == 0.0 && gp.y == 0.0) return D::V(gp.ix, gp.iy);
real v00;
real v10;
real v01;
real v11;
bool isfill = false;
size_t fx, fy;
// Count fills
for(size_t ix = 0; ix <= 1; ix++)
for(size_t iy = 0; iy <= 1; iy++)
{
if(isfill && D::IsFill(gp.ix + ix, gp.iy + iy)) return NAN;
if(D::IsFill(gp.ix + ix, gp.iy + iy))
{
fx = ix;
fy = iy;
isfill = true;
}
}
v00 = D::V(gp.ix, gp.iy);
v10 = D::V(gp.ix + 1, gp.iy);
v01 = D::V(gp.ix, gp.iy + 1);
v11 = D::V(gp.ix + 1, gp.iy + 1);
if(isfill && fx == 0 && fy == 0) v00 = (v10 + v01 + v11) / 3.0;
if(isfill && fx == 1 && fy == 0) v10 = (v00 + v01 + v11) / 3.0;
if(isfill && fx == 0 && fy == 1) v01 = (v10 + v00 + v11) / 3.0;
if(isfill && fx == 1 && fy == 1) v11 = (v10 + v01 + v00) / 3.0;
return v00 + gp.y * (v01 - v00) + gp.x * ((v10 - v00) + gp.y * (v00 - v01 + v11 - v10));
};
};

11
include/layereddata.h

@ -56,6 +56,7 @@ class LayeredData: public NCFuncs
const NCFileA& Get() const { return nc; }
const std::vector<MDateTime>& Times() const { return times; }
size_t Index(MDateTime tm) const
{
if(tm < Begin() || tm > End()) return 0;
@ -73,11 +74,13 @@ class LayeredData: public NCFuncs
}
return 0;
}
const MString& Url() const { return url; }
};
std::vector<NC> nc;
std::vector<real> depths;
std::vector<MDateTime> times;
struct CoordNames dname;
struct DimNames dname;
real lonb, latb, lone, late;
real lonstep, latstep;
MString title;
@ -143,6 +146,8 @@ class LayeredData: public NCFuncs
real Depth(size_t l) const { return isOk() ? depths[l] : -1000.0; }
real Depth(const BaseParameters* ip) const { return Depth(dynamic_cast<const struct Parameters*>(ip)->layer); }
real Lon(size_t ix) const { return isOk() ? (lonb + ix * lonstep) : -1000.0; }
real Lat(size_t iy) const { return isOk() ? (latb + iy * latstep) : -1000.0; }
@ -157,7 +162,7 @@ class LayeredData: public NCFuncs
return times[i];
}
time_t Timestep() const { return isOk() ? (times[1] - times[0]) : 0; }
time_t Timestep() const { return isOk() ? (times[1] - times[0]).Seconds() : 0; }
MString Title() const { return title; }
@ -172,7 +177,7 @@ class LayeredData: public NCFuncs
// clang-format on
}
bool CheckVar(const MString& vname) const
VarPresence CheckVar(const MString& vname) const
{
return NCFuncs::CheckVar(vname, [this](const MString& vn) { return HaveVar(vn); });
}

207
include/layereddataz.h

@ -0,0 +1,207 @@
#pragma once
#include "gsw.h"
#include "ncfuncs.h"
#include "nczarr.h"
#include "simple2ddata.h"
#include <memory>
using michlib::Ceil;
using michlib::DetGeoDomain;
using michlib::Floor;
using michlib::GPL;
using michlib::int2;
class LayeredDataZ: public NCFuncs
{
public:
using Data = Simple2DData;
private:
class NC: public NCZarr
{
std::vector<MDateTime> times;
public:
Error ReadTimes(const MString& tname)
{
static const MString pref = "LayeredDataZ::NC::ReadTimes";
if(!*this) return Error(pref, "Dataset not open");
std::vector<double> time;
{
auto ret = Read(tname, time);
if(!ret) return ret.Add(pref, "Can't read time");
}
MDateTime refdate;
time_t step = 0;
{
auto units = AttString(tname, "units");
if(!units.Exist()) return Error(pref, "Can't read refdate");
auto [rd, st, suc] = Refdate(units);
if(!suc) return Error(pref, "Can't parse " + units + " to refdate");
if(st == 0) return Error(pref, "Can't get timestep from string " + units);
refdate = rd;
step = st;
}
times.resize(time.size());
for(size_t i = 0; i < time.size(); i++) times[i] = refdate + static_cast<time_t>(time[i] * step);
return Error();
}
MDateTime Begin() const { return times.front(); }
MDateTime End() const { return times.back(); }
const std::vector<MDateTime>& Times() const { return times; }
size_t Index(MDateTime tm) const
{
if(tm < Begin() || tm > End()) return 0;
size_t b = 0, e = times.size() - 1;
if(tm == times[b]) return b + 1;
if(tm == times[e]) return e + 1;
while(e - b > 1)
{
size_t c = (e + b) / 2;
if(tm == times[c]) return c + 1;
if(tm > times[c])
b = c;
else
e = c;
}
return 0;
}
};
std::vector<NC> nc;
std::vector<real> depths;
bool depthinv;
std::vector<MDateTime> times;
struct DimNames dname;
real lonb, latb, lone, late;
real lonstep, latstep;
MString title;
class EnvVar
{
MString name, oldvalue;
bool activated, saved;
public:
EnvVar(): activated(false) {}
~EnvVar() { Deactivate(); }
void Activate(const MString& var, const MString& val)
{
if(activated) Deactivate();
name = var;
char* curval = getenv(name.Buf());
if(nullptr == curval)
saved = false;
else
{
oldvalue = curval;
saved = true;
}
setenv(name.Buf(), val.Buf(), 1);
}
void Deactivate()
{
if(!activated) return;
if(saved)
setenv(name.Buf(), oldvalue.Buf(), 1);
else
unsetenv(name.Buf());
activated = false;
}
};
EnvVar proxy;
protected:
struct Parameters: public BaseParameters
{
size_t xb, yb, xe, ye, layer;
virtual ~Parameters() override = default;
};
// TODO: RetVal
MString Open(const MString& dataset);
void SetTitle(const MString& newtitle) { title = newtitle; }
public:
MString Info() const;
std::pair<const BaseParameters*, MString> Parameters(michlib_internal::ParameterListEx& pars, const CLArgs& args, const struct Region& reg) const;
bool Read(const MString& vname, std::map<MString, Data>& cache, const BaseParameters* ip, size_t i) const;
bool isOk() const { return nc.size() > 0; }
explicit operator bool() const { return nc.size() > 0; }
real Depth(size_t l) const { return isOk() ? depths[l] : -1000.0; }
real Depth(const BaseParameters* ip) const { return Depth(dynamic_cast<const struct Parameters*>(ip)->layer); }
real Lon(size_t ix) const { return isOk() ? (lonb + ix * lonstep) : -1000.0; }
real Lat(size_t iy) const { return isOk() ? (latb + iy * latstep) : -1000.0; }
size_t NDepths() const { return depths.size(); }
size_t NTimes() const { return times.size(); }
MDateTime Time(size_t i) const
{
if(!isOk() || i >= times.size()) return MDateTime();
return times[i];
}
time_t Timestep() const { return isOk() ? (times[1] - times[0]).Seconds() : 0; }
MString Title() const { return title; }
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
}
VarPresence CheckVar(const MString& vname) const
{
return NCFuncs::CheckVar(vname, [this](const MString& vn) { return HaveVar(vn); });
}
private:
Data ReadVarRaw(const NC& f, const MString& name, size_t i, bool nodepth, const struct Parameters* p) const;
bool HaveVar(const MString& vname) const
{
for(size_t i = 0; i < nc.size(); i++)
if(NCFuncs::HaveVar(nc[i], vname)) return true;
return false;
}
std::tuple<MString, size_t, size_t> VarNameLoc(const MString vname, MDateTime tm) const
{
for(size_t i = 0; i < nc.size(); i++)
{
auto tind = nc[i].Index(tm);
if(tind == 0) continue;
for(const auto& v: nc[i].VarNames())
{
auto stname = nc[i].AttString(v, "standard_name");
if(!stname.Exist()) continue;
if(StName2Name(stname) == vname) return {v, i, tind - 1};
}
}
return {"", 0, 0};
}
};

64
include/mirrorfuncs.h

@ -0,0 +1,64 @@
#pragma once
#include "curlfuncs.h"
#include "mdatetime.h"
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <vector>
using michlib::Error;
using michlib::MDateTime;
using michlib::RetVal;
class DIRRAIIDT
{
public:
// TODO: make static
void operator()(DIR* d) { closedir(d); }
};
class DIRRAII: public std::unique_ptr<DIR, DIRRAIIDT>
{
public:
operator DIR*() const { return get(); }
};
struct FileInfo
{
MString url;
MString name;
MDateTime mtime;
size_t size;
};
// Remove last element from path
inline MString DirName(const MString& name)
{
auto p = name.GetPos('/', false);
if(p == 0) return name;
return name.SubStr(1, p - 1);
}
// Get last element from path
inline MString FileName(const MString& name)
{
auto p = name.GetPos('/', false);
if(p == 0) return name;
return name.SubStr(p + 1, name.Len() - p);
}
// Check and, if necessary, create the path to the file
bool MakePath(const MString& dname);
// Get local file list
RetVal<std::vector<struct FileInfo>> ReadLocalFileList(const MString& dir, const bool nofollow = true, const MString& path = "");
// Download file to the local mirror
Error DownloadFile(const CURLRAII& chandle, const struct FileInfo& rinfo, const MString& root);
// Remove file from the local mirror
Error RemoveFile(const struct FileInfo& linfo);
// Updare file in the local mirror
Error UpdateFile(const CURLRAII& chandle, const struct FileInfo& rinfo, const struct FileInfo& linfo, const MString& root);

547
include/ncfilew.h

@ -1,5 +1,6 @@
#pragma once
#include "MString.h"
#include "actiondep.h"
#include "traits.h"
#include <algorithm>
#include <netcdf.h>
@ -8,137 +9,456 @@
using michlib::MString;
class NCFileW
class NCFileWBase
{
enum Type
template<class T, class Dummy> struct NCTypeD
{
UNKNOWN,
G1V1,
G1V2,
G2V2
static constexpr nc_type nc = NC_NAT;
};
template<class Dummy> struct NCTypeD<michlib::int1, Dummy>
{
static constexpr nc_type nc = NC_BYTE;
};
template<class Dummy> struct NCTypeD<michlib::uint1, Dummy>
{
static constexpr nc_type nc = NC_UBYTE;
static int put_var(int nc, int vid, const michlib::uint1* data) { return nc_put_var_ubyte(nc, vid, data); }
static int put_vara(int nc, int vid, const size_t* startp, const size_t* countp, const michlib::uint1* data) { return nc_put_vara_ubyte(nc, vid, startp, countp, data); }
};
template<class Dummy> struct NCTypeD<michlib::int2, Dummy>
{
static constexpr nc_type nc = NC_SHORT;
static int put_var(int nc, int vid, const michlib::int2* data) { return nc_put_var_short(nc, vid, data); }
static int put_vara(int nc, int vid, const size_t* startp, const size_t* countp, const michlib::int2* data) { return nc_put_vara_short(nc, vid, startp, countp, data); }
};
template<class Dummy> struct NCTypeD<michlib::uint2, Dummy>
{
static constexpr nc_type nc = NC_USHORT;
static int put_var(int nc, int vid, const michlib::uint2* data) { return nc_put_var_ushort(nc, vid, data); }
static int put_vara(int nc, int vid, const size_t* startp, const size_t* countp, const michlib::uint2* data) { return nc_put_vara_ushort(nc, vid, startp, countp, data); }
};
template<class Dummy> struct NCTypeD<michlib::int4, Dummy>
{
static constexpr nc_type nc = NC_INT;
static int put_var(int nc, int vid, const michlib::int4* data) { return nc_put_var_int(nc, vid, data); }
static int put_vara(int nc, int vid, const size_t* startp, const size_t* countp, const michlib::int4* data) { return nc_put_vara_int(nc, vid, startp, countp, data); }
};
template<class Dummy> struct NCTypeD<michlib::uint4, Dummy>
{
static constexpr nc_type nc = NC_UINT;
static int put_var(int nc, int vid, const michlib::uint4* data) { return nc_put_var_uint(nc, vid, data); }
static int put_vara(int nc, int vid, const size_t* startp, const size_t* countp, const michlib::uint4* data) { return nc_put_vara_uint(nc, vid, startp, countp, data); }
};
template<class Dummy> struct NCTypeD<michlib::int8, Dummy>
{
static constexpr nc_type nc = NC_INT64;
};
template<class Dummy> struct NCTypeD<michlib::uint8, Dummy>
{
static constexpr nc_type nc = NC_UINT64;
};
template<class Dummy> struct NCTypeD<float, Dummy>
{
static constexpr nc_type nc = NC_FLOAT;
static int put_var(int nc, int vid, const float* data) { return nc_put_var_float(nc, vid, data); }
static int put_vara(int nc, int vid, const size_t* startp, const size_t* countp, const float* data) { return nc_put_vara_float(nc, vid, startp, countp, data); }
};
struct Var
template<class Dummy> struct NCTypeD<double, Dummy>
{
MString name;
int id;
static constexpr nc_type nc = NC_DOUBLE;
static int put_var(int nc, int vid, const double* data) { return nc_put_var_double(nc, vid, data); }
static int put_vara(int nc, int vid, const size_t* startp, const size_t* countp, const double* data) { return nc_put_vara_double(nc, vid, startp, countp, data); }
};
template<class Dummy> struct NCTypeD<MString, Dummy>
{
static constexpr nc_type nc = NC_CHAR;
};
template<class Dummy> struct NCTypeD<char*, Dummy>
{
static constexpr nc_type nc = NC_CHAR;
};
static constexpr auto fill = std::numeric_limits<float>::max();
template<class T> using NCType = NCTypeD<T, void>;
public:
template<class T> static constexpr nc_type Type2NCType = NCType<T>::nc;
// Error class
class Error
{
int err;
public:
constexpr Error(): err(NC_NOERR) {}
constexpr Error(int n_err): err(n_err) {}
explicit operator bool() const { return err == NC_NOERR; }
const char* ErrMessage() const { return nc_strerror(err); }
int Err() const { return err; }
bool IsErr() const { return err != NC_NOERR; }
void Reset() { err = NC_NOERR; }
void Reset(int n_err) { err = n_err; }
void Reset(const Error& e) { err = e.err; }
};
~NCFileWBase() { Close(); }
int Id() const { return ncid; }
int Err() const { return err.Err(); }
const char* ErrMessage() const { return err.ErrMessage(); }
explicit operator bool() const { return Ok(); }
bool Ok() const { return opened && !err.IsErr(); }
operator int() const { return ncid; }
void Close()
{
if(opened) nc_close(ncid);
err.Reset();
opened = false;
}
void Open(const MString& path)
{
if(opened) return;
err.Reset(nc_create(path.Buf(), NC_CLOBBER | NC_NETCDF4, &ncid));
opened = !err.IsErr();
}
void AddDim(const MString& name, size_t len)
{
if(err.IsErr()) return;
static int dimid;
err.Reset(nc_def_dim(ncid, name.Buf(), len, &dimid));
}
MString AddAtts(const michlib_internal::ParameterListEx& pars)
{
for(const auto& p: pars.GetParameterList())
{
MString ret;
if(p.first.Prefix() != "") continue; // Only parameters with empty prefix considered for now
switch(p.second.valtype)
{
case michlib_internal::Parameter::UINT1:
{
ret = AddAtt(p.first.Name(), p.second.value.u1);
break;
}
case michlib_internal::Parameter::UINT2:
{
ret = AddAtt(p.first.Name(), p.second.value.u2);
break;
}
case michlib_internal::Parameter::UINT4:
{
ret = AddAtt(p.first.Name(), p.second.value.u4);
break;
}
case michlib_internal::Parameter::UINT8:
{
ret = AddAtt(p.first.Name(), p.second.value.u8);
break;
}
case michlib_internal::Parameter::INT1:
{
ret = AddAtt(p.first.Name(), p.second.value.i1);
break;
}
case michlib_internal::Parameter::INT2:
{
ret = AddAtt(p.first.Name(), p.second.value.i2);
break;
}
case michlib_internal::Parameter::INT4:
{
ret = AddAtt(p.first.Name(), p.second.value.i4);
break;
}
case michlib_internal::Parameter::INT8:
{
ret = AddAtt(p.first.Name(), p.second.value.i8);
break;
}
case michlib_internal::Parameter::FLOAT:
{
ret = AddAtt(p.first.Name(), p.second.value.r4);
break;
}
case michlib_internal::Parameter::DOUBLE:
{
ret = AddAtt(p.first.Name(), p.second.value.r8);
break;
}
case michlib_internal::Parameter::LDOUBLE:
{
ret = AddAtt(p.first.Name(), static_cast<double>(p.second.value.r10));
break;
}
case michlib_internal::Parameter::BOOL:
{
ret = AddAtt(p.first.Name(), p.second.value.b ? 1 : 0);
break;
}
case michlib_internal::Parameter::STRING:
{
ret = AddAtt(p.first.Name(), p.second.svalue);
break;
}
case michlib_internal::Parameter::INVALID: break; // Silently skip
}
if(ret.Exist()) return ret;
}
return "";
}
template<class T>
requires(Type2NCType<T> != NC_NAT)
MString AddAtt(const MString& name, const T& val)
{
if(err.IsErr()) return "Can't write attribute " + name + " due to previous errors";
static constexpr auto nct = Type2NCType<T>;
static constexpr bool ischar = std::is_same_v<T, char*>;
if constexpr(nct != NC_CHAR)
err.Reset(nc_put_att(ncid, NC_GLOBAL, name.Buf(), nct, 1, &val));
else if constexpr(ischar)
err.Reset(nc_put_att_text(ncid, NC_GLOBAL, name.Buf(), strlen(val) + 1, val));
else
err.Reset(nc_put_att_text(ncid, NC_GLOBAL, name.Buf(), val.Len() + 1, val.Buf()));
return err.IsErr() ? "Can't write attribute " + name + ": " + ErrMessage() : "";
}
template<class T>
requires(Type2NCType<T> != NC_NAT)
MString AddAtt(const MString& vname, const MString& name, const T& val)
{
if(err.IsErr()) return "Can't write attribute " + name + " to the variable " + vname + " due to previous errors";
static constexpr auto nct = Type2NCType<T>;
static constexpr bool ischar = std::is_same_v<T, char*>;
int varid;
err.Reset(nc_inq_varid(ncid, vname.Buf(), &varid));
if(err.IsErr()) return "Can't find variable " + vname + ": " + ErrMessage();
if constexpr(nct != NC_CHAR)
err.Reset(nc_put_att(ncid, varid, name.Buf(), nct, 1, &val));
else if constexpr(ischar)
err.Reset(nc_put_att_text(ncid, varid, name.Buf(), strlen(val) + 1, val));
else
err.Reset(nc_put_att_text(ncid, varid, name.Buf(), val.Len() + 1, val.Buf()));
return err.IsErr() ? "Can't write attribute " + name + " to the variable " + vname + ": " + ErrMessage() : "";
}
MString AddAtt(const MString& vname, const MString& name, const char* val)
{
if(err.IsErr()) return "Can't write attribute " + name + " to the variable " + vname + " due to previous errors";
int varid;
err.Reset(nc_inq_varid(ncid, vname.Buf(), &varid));
if(err.IsErr()) return "Can't find variable " + vname + ": " + ErrMessage();
err.Reset(nc_put_att_text(ncid, varid, name.Buf(), strlen(val) + 1, val));
return err.IsErr() ? "Can't write attribute " + name + " to the variable " + vname + ": " + ErrMessage() : "";
}
template<class... T> void AddVar(const MString& vname, nc_type type, const T&... dnames) { AddVar(vname, type, std::vector<MString>({dnames...})); }
void EndDef() const { nc_enddef(ncid); }
void SetComp(const MString& vname, int comp)
{
if(err.IsErr()) return;
int varid;
err.Reset(nc_inq_varid(ncid, vname.Buf(), &varid));
if(err.IsErr()) return;
err.Reset(nc_def_var_deflate(ncid, varid, 1, 1, comp));
}
template<class T>
requires requires(int nc, int vid, const T* d) {
{ NCTypeD<T, void>::put_var(nc, vid, d) } -> std::same_as<int>;
}
void WriteVar(const MString& vname, const T* data)
{
if(err.IsErr()) return;
int varid;
err.Reset(nc_inq_varid(ncid, vname.Buf(), &varid));
if(err.IsErr()) return;
err.Reset(NCTypeD<T, void>::put_var(ncid, varid, data));
}
template<class T>
requires requires(int nc, int vid, const size_t* start, const size_t* count, const T* d) {
{ NCTypeD<T, void>::put_vara(nc, vid, start, count, d) } -> std::same_as<int>;
}
void WriteVar(const MString& vname, size_t ind, const T* data)
{
if(err.IsErr()) return;
int varid;
err.Reset(nc_inq_varid(ncid, vname.Buf(), &varid));
if(err.IsErr()) return;
int ndim;
err.Reset(nc_inq_var(ncid, varid, nullptr, nullptr, &ndim, nullptr, nullptr));
if(err.IsErr()) return;
std::vector<int> dimids(ndim);
err.Reset(nc_inq_var(ncid, varid, nullptr, nullptr, nullptr, dimids.data(), nullptr));
if(err.IsErr()) return;
std::vector<size_t> start(ndim), count(ndim);
start[0] = ind;
count[0] = 1;
for(size_t i = 1; i < dimids.size(); i++)
{
start[i] = 0;
err.Reset(nc_inq_dim(ncid, dimids[i], nullptr, &count[i]));
if(err.IsErr()) return;
}
err.Reset(NCTypeD<T, void>::put_vara(ncid, varid, start.data(), count.data(), data));
}
private:
// Members
int ncid;
Type type = UNKNOWN;
union
Error err = NC_NOERR;
bool opened = false;
void AddVar(const MString& vname, nc_type type, std::vector<MString>&& dnames)
{
struct
if(err.IsErr()) return;
std::vector<int> dimids(dnames.size());
int varid;
for(size_t i = 0; i < dnames.size(); i++)
{
int xdimid, ydimid;
};
int dimid[2];
err.Reset(nc_inq_dimid(ncid, dnames[i].Buf(), &dimids[i]));
if(err.IsErr()) return;
}
err.Reset(nc_def_var(ncid, vname.Buf(), type, dimids.size(), dimids.data(), &varid));
}
};
class NCFileW: public NCFileWBase
{
enum Type
{
UNKNOWN,
PSET,
GPSET,
GRID,
GGRID,
RGRID,
GRGRID
};
int xid, yid;
static constexpr auto fill = std::numeric_limits<float>::max();
Type type = UNKNOWN;
int compress;
std::vector<struct Var> vars;
bool tdep = false;
template<class D> static constexpr Type DetType()
{
if constexpr(ReadIs2DGeoRectArray<D>) return G1V2;
if constexpr(ReadIs2DGeoArray<D>) return G2V2;
if constexpr(ReadIs1DGeoArray<D>) return G1V1;
if constexpr(ReadIs2DGeoRectArray<D>) return GRGRID;
if constexpr(ReadIs2DGeoArray<D>) return GGRID;
if constexpr(ReadIs1DGeoArray<D>) return GPSET;
if constexpr(ReadIs2DXYRectArray<D>) return RGRID;
if constexpr(ReadIs2DXYArray<D>) return GRID;
if constexpr(ReadIs1DArray<D>) return PSET;
return UNKNOWN;
};
template<class D> static constexpr bool Is1DType() { return Is1DType(DetType<D>()); }
static constexpr bool Is1DType(Type t) { return t == G1V1; }
static constexpr bool IsGeoType(Type t) { return t == GPSET || t == GGRID || t == GRGRID; }
static constexpr bool Is1DType(Type t) { return t == PSET || t == GPSET; }
MString CreateFile(Type stype, const MString& name, const MString& history, int compression, size_t nx, size_t ny);
MString CreateFile(Type stype, const MString& name, int compression, size_t nx, size_t ny);
public:
static constexpr auto Fill() { return fill; }
~NCFileW() { Close(); }
template<class D> MString Create(const D& data, const MString& name, const MString& history, int compression)
template<class D> MString Create(const D& data, const MString& name, int compression)
{
if(type != UNKNOWN) return "File already created";
if constexpr(Is1DType<D>())
return CreateFile(DetType<D>(), name, history, compression, data.N(), 0);
return CreateFile(DetType<D>(), name, compression, data.N(), 0);
else
return CreateFile(DetType<D>(), name, history, compression, data.Nx(), data.Ny());
}
size_t VarId(const MString& name) const
{
for(size_t i = 0; i < vars.size(); i++)
if(vars[i].name == name) return i + 1;
return 0;
return CreateFile(DetType<D>(), name, compression, data.Nx(), data.Ny());
}
bool HaveVar(const MString& name) const { return VarId(name) != 0; }
void EndDef() const
{
if(type != UNKNOWN) nc_enddef(ncid);
}
void Close()
{
if(type != UNKNOWN) nc_close(ncid);
NCFileWBase::Close();
type = UNKNOWN;
}
MString AddTimeData(const TimeData& tdata, bool tisindex);
MString AddVariable(const MString& name, const MString& stname, const MString& lname, const MString& units, const MString& comment);
template<class D, class Op> MString WriteVariable(const D& data, size_t varid, Op op) const
template<class D, class Op>
requires(std::is_invocable_v<Op, size_t> || std::is_invocable_v<Op, size_t, size_t>)
MString WriteVariable(const D& data, const MString& name, Op op, size_t tind)
{
static constexpr auto dtype = DetType<D>();
if(type == UNKNOWN) return "File not open";
if(type != dtype) return "Incompatible data type";
if(varid == 0) return "Incorrect variable";
if constexpr(dtype == UNKNOWN) return "Unknown data type";
size_t v = varid - 1;
EndDef();
int ret;
if constexpr(Is1DType(dtype))
{
const size_t i = 0, c = data.N();
float buf[c];
const size_t c = data.N();
auto buf = std::make_unique<float[]>(c);
for(size_t ix = 0; ix < c; ix++) buf[ix] = data.IsFill(ix) ? fill : op(ix);
ret = nc_put_vara_float(ncid, vars[v].id, &i, &c, buf);
if(ret != NC_NOERR) return "Can't write " + vars[v].name + " variable in the netcdf file";
if(tdep)
WriteVar(name, tind, buf.get());
else
WriteVar(name, buf.get());
}
else
{
const size_t i[2] = {0, 0};
const size_t c[2] = {data.Nx(), data.Ny()};
float buf[c[0] * c[1]];
const size_t c[2] = {data.Ny(), data.Nx()};
auto buf = std::make_unique<float[]>(c[0] * c[1]);
for(size_t iy = 0; iy < c[0]; iy++)
for(size_t ix = 0; ix < c[1]; ix++) buf[iy * c[1] + ix] = data.IsFill(ix, iy) ? fill : op(ix, iy);
if(tdep)
WriteVar(name, tind, buf.get());
else
WriteVar(name, buf.get());
}
for(size_t ix = 0; ix < c[0]; ix++)
for(size_t iy = 0; iy < c[1]; iy++) buf[ix * c[1] + iy] = data.IsFill(ix, iy) ? fill : op(ix, iy);
if(!*this) return "Can't write variable " + name + ": " + ErrMessage();
ret = nc_put_vara_float(ncid, vars[v].id, i, c, buf);
if(ret != NC_NOERR) return "Can't write " + vars[v].name + " variable in the netcdf file";
}
return "";
}
template<class D, class Op> MString WriteVariable(const D& data, const MString& name, Op op) const { return WriteVariable(data, VarId(name), op); }
template<class D> MString WriteVariable(const D& data, size_t varid) const
template<class D> MString WriteVariable(const D& data, const MString& name, size_t tind)
{
if constexpr(Is1DType(DetType<D>()))
return WriteVariable(data, varid, [&data = std::as_const(data)](size_t i) { return data(i); });
return WriteVariable(data, name, [&data = std::as_const(data)](size_t i) { return data(i); }, tind);
else
return WriteVariable(data, varid, [&data = std::as_const(data)](size_t i, size_t j) { return data(i, j); });
return WriteVariable(data, name, [&data = std::as_const(data)](size_t i, size_t j) { return data(i, j); }, tind);
}
template<class D> MString WriteVariable(const D& data, const MString& name) const { return WriteVariable(data, VarId(name)); }
template<class D, class Op>
requires(std::is_invocable_v<Op, size_t> || std::is_invocable_v<Op, size_t, size_t>)
MString WriteVariable(const D& data, const MString& name, Op op)
{
return WriteVariable(data, name, op, 0);
}
template<class D> MString WriteVariable(const D& data, const MString& name) { return WriteVariable(data, name, 0); }
template<class D> MString WriteGrid(const D& data) const
template<class D> MString WriteGrid(const D& data)
{
static constexpr auto dtype = DetType<D>();
if(type == UNKNOWN) return "File not open";
@ -146,56 +466,87 @@ class NCFileW
EndDef();
int ret;
if constexpr(dtype == G1V1)
if constexpr(dtype == UNKNOWN)
return "Unknown data type";
else if constexpr(dtype == PSET)
{
const size_t i = 0, c = data.N();
const size_t c = data.N();
float bufx[c];
float bufy[c];
for(size_t ix = 0; ix < c; ix++)
{
bufx[ix] = data.X(ix);
bufy[ix] = data.Y(ix);
}
WriteVar("x", bufx);
WriteVar("y", bufy);
}
else if constexpr(dtype == GPSET)
{
const size_t c = data.N();
auto bufx = std::make_unique<float[]>(c);
auto bufy = std::make_unique<float[]>(c);
for(size_t ix = 0; ix < c; ix++)
{
bufx[ix] = data.Lon(ix);
bufy[ix] = data.Lat(ix);
}
ret = nc_put_vara_float(ncid, xid, &i, &c, bufx);
if(ret != NC_NOERR) return "Can't write longitude variable in the netcdf file";
ret = nc_put_vara_float(ncid, yid, &i, &c, bufy);
if(ret != NC_NOERR) return "Can't write latitude variable in the netcdf file";
WriteVar("longitude", bufx.get());
WriteVar("latitude", bufy.get());
}
else if constexpr(dtype == G1V2)
else if constexpr(dtype == RGRID)
{
const size_t i = 0, cx = data.Nx(), cy = data.Ny();
float bufx[cx];
float bufy[cy];
const size_t cx = data.Nx(), cy = data.Ny();
auto bufx = std::make_unique<float[]>(cx);
auto bufy = std::make_unique<float[]>(cy);
for(size_t ix = 0; ix < cx; ix++) bufx[ix] = data.Ix2X(ix);
for(size_t iy = 0; iy < cy; iy++) bufy[iy] = data.Iy2Y(iy);
WriteVar("x", bufx.get());
WriteVar("y", bufy.get());
}
else if constexpr(dtype == GRGRID)
{
const size_t cx = data.Nx(), cy = data.Ny();
auto bufx = std::make_unique<float[]>(cx);
auto bufy = std::make_unique<float[]>(cy);
for(size_t ix = 0; ix < cx; ix++) bufx[ix] = data.Ix2Lon(ix);
for(size_t iy = 0; iy < cy; iy++) bufy[iy] = data.Iy2Lat(iy);
ret = nc_put_vara_float(ncid, xid, &i, &cx, bufx);
if(ret != NC_NOERR) return "Can't write longitude variable in the netcdf file";
ret = nc_put_vara_float(ncid, yid, &i, &cy, bufy);
if(ret != NC_NOERR) return "Can't write latitude variable in the netcdf file";
WriteVar("longitude", bufx.get());
WriteVar("latitude", bufy.get());
}
else if constexpr(dtype == G2V2)
else if constexpr(dtype == GRID)
{
const size_t i[2] = {0, 0};
const size_t c[2] = {data.Nx(), data.Ny()};
float bufx[c[0] * c[1]];
float bufy[c[0] * c[1]];
for(size_t ix = 0; ix < c[0]; ix++)
for(size_t iy = 0; iy < c[1]; iy++)
const size_t c[2] = {data.Ny(), data.Nx()};
auto bufx = std::make_unique<float[]>(c[0] * c[1]);
auto bufy = std::make_unique<float[]>(c[0] * c[1]);
for(size_t iy = 0; iy < c[0]; iy++)
for(size_t ix = 0; ix < c[1]; ix++)
{
bufx[ix * c[1] + iy] = data.Lon(ix, iy);
bufy[ix * c[1] + iy] = data.Lat(ix, iy);
bufx[iy * c[1] + ix] = data.X(ix, iy);
bufy[iy * c[1] + ix] = data.Y(ix, iy);
}
ret = nc_put_vara_float(ncid, xid, i, c, bufx);
if(ret != NC_NOERR) return "Can't write longitude variable in the netcdf file";
ret = nc_put_vara_float(ncid, yid, i, c, bufy);
if(ret != NC_NOERR) return "Can't write latitude variable in the netcdf file";
WriteVar("x", bufx.get());
WriteVar("y", bufy.get());
}
else if constexpr(dtype == GGRID)
{
const size_t c[2] = {data.Ny(), data.Nx()};
auto bufx = std::make_unique<float[]>(c[0] * c[1]);
auto bufy = std::make_unique<float[]>(c[0] * c[1]);
for(size_t iy = 0; iy < c[0]; iy++)
for(size_t ix = 0; ix < c[1]; ix++)
{
bufx[iy * c[1] + ix] = data.Lon(ix, iy);
bufy[iy * c[1] + ix] = data.Lat(ix, iy);
}
WriteVar("longitude", bufx.get());
WriteVar("latitude", bufy.get());
}
else
return "Unknown data type";
if(!*this) return MString("Can't write grid: ") + ErrMessage();
return "";
}
};

207
include/ncfuncs.h

@ -1,6 +1,9 @@
#pragma once
#include "DataAdapters/ncfilealt.h"
#include "basedata.h"
#include "mdatetime.h"
#include "nczarr.h"
#include <map>
#include <set>
#include <tuple>
@ -12,6 +15,10 @@ class NCFuncs
{
public:
struct CoordNames
{
MString lonname, latname, depthname, timename;
};
struct DimNames
{
MString lonname, latname, depthname, timename;
size_t nx, ny, nz, nt;
@ -23,19 +30,203 @@ class NCFuncs
static std::tuple<MDateTime, time_t, bool> Refdate(const MString& refdate);
static void GetVars(const NCFileA& nc, std::set<MString>& vars);
static CoordNames GetCNames(const NCFileA& nc);
static CoordNames GetDNames(const NCFileA& nc);
static DimNames GetDNames(const NCFileA& nc);
static bool HaveVar(const NCFileA& nc, const MString& vname);
template<class HV> static bool CheckVar(const MString& vname, HV hv)
static void GetVars(const NCZarr& nc, std::set<MString>& vars);
static CoordNames GetCNames(const NCZarr& nc);
static DimNames GetDNames(const NCZarr& nc);
static bool HaveVar(const NCZarr& nc, const MString& vname);
template<class HV> static VarPresence CheckVar(const MString& vname, HV hv)
{
if(!hv(vname))
{
bool varexist = false;
if(vname == "temp" && hv("ptemp") && hv("sal")) varexist = true;
if(vname == "ptemp" && hv("temp") && hv("sal")) varexist = true;
if(vname == "pdens" && (hv("ptemp") || hv("temp")) && hv("sal")) varexist = true;
if((vname == "U" || vname == "U2") && hv("u") && hv("v")) varexist = true;
if(!varexist) return false;
auto varexist = VarPresence::NONE;
if(vname == "temp" && hv("ptemp") && hv("sal")) varexist = VarPresence::DERIVED;
if(vname == "ptemp" && hv("temp") && hv("sal")) varexist = VarPresence::DERIVED;
if(vname == "pdens" && (hv("ptemp") || hv("temp")) && hv("sal")) varexist = VarPresence::DERIVED;
if((vname == "U" || vname == "U2") && hv("u") && hv("v")) varexist = VarPresence::DERIVED;
if((vname == "ugeo" || vname == "vgeo") && hv("ssh")) varexist = VarPresence::DERIVED;
return varexist;
}
return VarPresence::INTERNAL;
}
template<class D> static bool TransformationRead(const D* data, const MString& vname, std::map<MString, typename D::Data>& cache, const BaseParameters* ip, size_t i)
{
if constexpr(requires(const D* d, const BaseParameters* p) {
{ d->Depth(p) } -> std::convertible_to<real>;
})
{
real depth = data->Depth(ip);
// ptemp from temp and sal
if(vname == "ptemp")
{
if(!(data->Read("temp", cache, ip, i) && data->Read("sal", cache, ip, i))) return false;
cache[vname] = cache.at("temp").CopyGrid();
auto& ptemp = cache.at(vname);
const auto& temp = cache.at("temp");
const auto& sal = cache.at("sal");
ptemp.SetUnit("degrees_C");
for(size_t ind = 0; ind < ptemp.N(); ind++)
{
if(temp.IsFill(ind) || sal.IsFill(ind))
ptemp.V(ind) = ptemp.Fillval();
else
ptemp.V(ind) = Temp2PTemp(temp.V(ind), sal.V(ind), depth, ptemp.Lon(ind), ptemp.Lat(ind));
}
return true;
}
// temp from ptemp and sal
if(vname == "temp")
{
if(!(data->Read("ptemp", cache, ip, i) && data->Read("sal", cache, ip, i))) return false;
cache[vname] = cache.at("ptemp").CopyGrid();
auto& temp = cache.at(vname);
const auto& ptemp = cache.at("ptemp");
const auto& sal = cache.at("sal");
temp.SetUnit("degrees_C");
for(size_t ind = 0; ind < temp.N(); ind++)
{
if(ptemp.IsFill(ind) || sal.IsFill(ind))
temp.V(ind) = temp.Fillval();
else
temp.V(ind) = PTemp2Temp(ptemp.V(ind), sal.V(ind), depth, temp.Lon(ind), temp.Lat(ind));
}
return true;
}
// pdens from temp and sal
if(vname == "pdens")
{
bool tempispot = data->CheckVar("ptemp") == VarPresence::INTERNAL;
if(!(data->Read(tempispot ? "ptemp" : "temp", cache, ip, i) && data->Read("sal", cache, ip, i))) return false;
cache[vname] = cache.at("sal").CopyGrid();
auto& pdens = cache.at(vname);
const auto& temp = cache.at(tempispot ? "ptemp" : "temp");
const auto& sal = cache.at("sal");
pdens.SetUnit("kg m-3");
for(size_t ind = 0; ind < pdens.N(); ind++)
{
if(temp.IsFill(ind) || sal.IsFill(ind))
pdens.V(ind) = pdens.Fillval();
else
pdens.V(ind) =
tempispot ? PTemp2PDens(temp.V(ind), sal.V(ind), depth, pdens.Lon(ind), pdens.Lat(ind)) : Temp2PDens(temp.V(ind), sal.V(ind), depth, pdens.Lon(ind), pdens.Lat(ind));
}
return true;
}
}
// U and U2 from u and v
if(vname == "U" || vname == "U2")
{
bool square = vname == "U2";
if(!(data->Read("u", cache, ip, i) && data->Read("v", cache, ip, i))) return false;
cache[vname] = cache.at("u").CopyGrid();
auto& U = cache.at(vname);
const auto& u = cache.at("u");
const auto& v = cache.at("v");
if(u.Unit().Exist()) U.SetUnit(square ? ("(" + u.Unit() + ")2") : u.Unit());
for(size_t ind = 0; ind < U.N(); ind++)
{
if(u.IsFill(ind) || v.IsFill(ind))
U.V(ind) = U.Fillval();
else
U.V(ind) = square ? (u(ind) * u(ind) + v(ind) * v(ind)) : michlib::Hypot(u(ind), v(ind));
}
return true;
}
// ugeo
if(vname == "ugeo")
{
if(!data->Read("ssh", cache, ip, i)) return false;
const auto& ssh = cache.at("ssh");
const real mul = (ssh.Unit() == "cm") ? 1.0 : ((ssh.Unit() == "m") ? 100.0 : -1.0);
if(mul < 0.0) return false; // Unknown units
cache[vname] = cache.at("ssh").CopyGrid();
auto& ugeo = cache.at(vname);
ugeo.SetUnit("cm/s");
for(size_t iy = 0; iy < ssh.Ny(); iy++)
{
const real phi = michlib::M_PI * ssh.Iy2Lat(iy) / 180.0;
const real sphi = michlib::Sin(phi);
const real s2phi = michlib::Sin(2.0 * phi);
const real g = 978.0318 * (1.0 + 0.005302 * sphi * sphi - 0.000006 * s2phi * s2phi); // g in cm/s
const real f = 2.0 * 7.29211 * sphi; // Coriolis parameter multiplied by 100'000
const real db = (iy == 0) ? 0.0 : (michlib::M_PI * 6371.0 * (ssh.Iy2Lat(iy) - ssh.Iy2Lat(iy - 1)) / 180.0); // Distance in km, compensated by multiplier in Coriolis parameter
const real du =
(iy == ssh.Ny() - 1) ? 0.0 : (michlib::M_PI * 6371.0 * (ssh.Iy2Lat(iy + 1) - ssh.Iy2Lat(iy)) / 180.0); // Distance in km, compensated by multiplier in Coriolis parameter
for(size_t ix = 0; ix < ssh.Nx(); ix++)
{
const bool bok = iy != 0 && !ssh.IsFill(ix, iy - 1) && !ssh.IsFill(ix, iy);
const bool uok = iy != ssh.Ny() - 1 && !ssh.IsFill(ix, iy + 1) && !ssh.IsFill(ix, iy);
if(!(bok || uok))
ugeo(ix, iy) = ugeo.Fillval();
else if(!bok)
ugeo(ix, iy) = -g * mul * (ssh(ix, iy + 1) - ssh(ix, iy)) / (f * du);
else if(!uok)
ugeo(ix, iy) = -g * mul * (ssh(ix, iy) - ssh(ix, iy - 1)) / (f * db);
else
{
const real bder = -g * mul * (ssh(ix, iy) - ssh(ix, iy - 1)) / (f * db);
const real uder = -g * mul * (ssh(ix, iy + 1) - ssh(ix, iy)) / (f * du);
ugeo(ix, iy) = 0.5 * (bder + uder);
}
}
}
return true;
}
// vgeo
if(vname == "vgeo")
{
if(!data->Read("ssh", cache, ip, i)) return false;
const auto& ssh = cache.at("ssh");
const real mul = (ssh.Unit() == "cm") ? 1.0 : ((ssh.Unit() == "m") ? 100.0 : -1.0);
if(mul < 0.0) return false; // Unknown units
cache[vname] = cache.at("ssh").CopyGrid();
auto& vgeo = cache.at(vname);
vgeo.SetUnit("cm/s");
for(size_t iy = 0; iy < ssh.Ny(); iy++)
{
const real phi = michlib::M_PI * ssh.Iy2Lat(iy) / 180.0;
const real sphi = michlib::Sin(phi);
const real cphi = michlib::Cos(phi);
const real s2phi = michlib::Sin(2.0 * phi);
const real g = 978.0318 * (1.0 + 0.005302 * sphi * sphi - 0.000006 * s2phi * s2phi); // g in cm/s
const real f = 2.0 * 7.29211 * sphi; // Coriolis parameter multiplied by 100'000
for(size_t ix = 0; ix < ssh.Nx(); ix++)
{
const real dl =
(ix == 0) ? 0.0 : (michlib::M_PI * 6371.0 * (ssh.Ix2Lon(ix) - ssh.Ix2Lon(ix - 1)) / 180.0) * cphi; // Distance in km, compensated by multiplier in Coriolis parameter
const real dr = (ix == ssh.Nx() - 1)
? 0.0
: (michlib::M_PI * 6371.0 * (ssh.Ix2Lon(ix + 1) - ssh.Ix2Lon(ix)) / 180.0) * cphi; // Distance in km, compensated by multiplier in Coriolis parameter
const bool lok = ix != 0 && !ssh.IsFill(ix - 1, iy) && !ssh.IsFill(ix, iy);
const bool rok = ix != ssh.Nx() - 1 && !ssh.IsFill(ix + 1, iy) && !ssh.IsFill(ix, iy);
if(!(lok || rok))
vgeo(ix, iy) = vgeo.Fillval();
else if(!lok)
vgeo(ix, iy) = g * mul * (ssh(ix + 1, iy) - ssh(ix, iy)) / (f * dr);
else if(!rok)
vgeo(ix, iy) = g * mul * (ssh(ix, iy) - ssh(ix - 1, iy)) / (f * dl);
else
{
const real lder = g * mul * (ssh(ix, iy) - ssh(ix - 1, iy)) / (f * dl);
const real rder = g * mul * (ssh(ix + 1, iy) - ssh(ix, iy)) / (f * dr);
vgeo(ix, iy) = 0.5 * (lder + rder);
}
}
}
return true;
}
return false;
}
};

87
include/ncsimple.h

@ -0,0 +1,87 @@
#pragma once
#include "nczarrcommon.h"
#include <netcdf.h>
#include <variant>
class NCSimpleTypes: public NcZarrTypes
{
protected:
template<class VType> class ReadedData
{
using Vec = std::vector<size_t>;
private:
std::unique_ptr<VType[]> data;
public:
ReadedData() = default;
ReadedData(std::unique_ptr<VType[]>&& d): data(std::move(d)) {}
VType operator()(size_t lini) const { return data[lini]; }
};
};
class NCSimpleFunctions: public NCSimpleTypes
{
int ncid;
RetVal<std::vector<Attribute>> ReadAtts(int vid) const;
protected:
NCSimpleFunctions(): ncid(0) {}
template<class VType> RetVal<ReadedData<VType>> Read(const MString& var, const size_t* start, const size_t* count) const
{
static const MString pref = "NCSimpleFunctions::Read";
size_t ind = FindInd(var, vars);
const size_t N = vars[ind].NDim();
std::unique_ptr<VType[]> cdata;
size_t dsize = 1;
for(size_t i = 0; i < N; i++) dsize *= count[i];
cdata.reset(new VType[dsize]);
int vid;
int res = nc_inq_varid(ncid, var.Buf(), &vid);
if(res != NC_NOERR) return Error(pref, MString("nc_inq_varid error: ") + nc_strerror(res));
if constexpr(std::is_same_v<VType, float>)
res = nc_get_vara_float(ncid, vid, start, count, cdata.get());
else if constexpr(std::is_same_v<VType, double>)
res = nc_get_vara_double(ncid, vid, start, count, cdata.get());
else if constexpr(std::is_same_v<VType, int>)
res = nc_get_vara_int(ncid, vid, start, count, cdata.get());
else if constexpr(std::is_same_v<VType, long>)
res = nc_get_vara_long(ncid, vid, start, count, cdata.get());
else if constexpr(std::is_same_v<VType, long long>)
res = nc_get_vara_longlong(ncid, vid, start, count, cdata.get());
else if constexpr(std::is_same_v<VType, short>)
res = nc_get_vara_short(ncid, vid, start, count, cdata.get());
else if constexpr(std::is_same_v<VType, signed char>)
res = nc_get_vara_schar(ncid, vid, start, count, cdata.get());
else if constexpr(std::is_same_v<VType, unsigned int>)
res = nc_get_vara_uint(ncid, vid, start, count, cdata.get());
else if constexpr(std::is_same_v<VType, unsigned long long>)
res = nc_get_vara_ulonglong(ncid, vid, start, count, cdata.get());
else if constexpr(std::is_same_v<VType, unsigned short>)
res = nc_get_vara_ushort(ncid, vid, start, count, cdata.get());
else if constexpr(std::is_same_v<VType, unsigned char>)
res = nc_get_vara_ubyte(ncid, vid, start, count, cdata.get());
else
return Error(pref, "Unsupported variable type");
if(res != NC_NOERR) return Error(pref, MString("nc_get_vara error: ") + nc_strerror(res));
return ReadedData<VType>(std::move(cdata));
}
public:
~NCSimpleFunctions() { nc_close(ncid); }
Error Open(const MString& filename);
};
using NCSimple = NcZarrRead<NCSimpleFunctions>;

166
include/nczarr.h

@ -0,0 +1,166 @@
#pragma once
#include "nczarrmulti.h"
#include <variant>
using NCZarrBase = std::variant<Zarr, NCSimple, ZarrMulti, NCMulti>;
#define DEFFUNC(NAME) \
auto NAME() const \
{ \
return V([](const auto& arg) { return arg.NAME(); }); \
}
#define DEFFUNC1(NAME) \
auto NAME(const MString& v) const \
{ \
return V([&v = std::as_const(v)](const auto& arg) { return arg.NAME(v); }); \
}
#define DEFFUNC2(NAME) \
auto NAME(const MString& v1, const MString& v2) const \
{ \
return V([&v1 = std::as_const(v1), &v2 = std::as_const(v2)](const auto& arg) { return arg.NAME(v1, v2); }); \
}
class NCZarr: private NCZarrBase, private DimReqDef
{
template<class Visitor> auto V(Visitor&& visitor) const { return std::visit(std::forward<Visitor>(visitor), *static_cast<const NCZarrBase*>(this)); }
public:
explicit operator bool() const
{
return V([](const auto& arg) { return static_cast<bool>(arg); });
}
DEFFUNC1(NDim)
DEFFUNC(NAtt)
DEFFUNC1(NAtt)
DEFFUNC(AttNames)
DEFFUNC1(AttNames)
DEFFUNC(VarNames)
DEFFUNC1(VarT)
DEFFUNC1(VarFill)
DEFFUNC1(DimNames)
DEFFUNC2(DimSize)
DEFFUNC2(AttT)
DEFFUNC2(AttInt)
DEFFUNC2(AttUInt)
DEFFUNC2(AttReal)
DEFFUNC2(AttString)
DEFFUNC2(AttBool)
DEFFUNC1(AttT)
DEFFUNC1(AttInt)
DEFFUNC1(AttUInt)
DEFFUNC1(AttReal)
DEFFUNC1(AttString)
DEFFUNC1(AttBool)
DEFFUNC2(HasDim)
DEFFUNC1(HasVar)
DEFFUNC1(HasAtt)
DEFFUNC2(HasAtt)
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, const char* request) const
{
return V([&vname = std::as_const(vname), &data = data, &transform = transform, request = request](const auto& arg) { return arg.Read(vname, data, transform, request); });
}
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1) const
{
return V([&vname = std::as_const(vname), &data = data, &transform = transform, &req1 = req1](const auto& arg) { return arg.Read(vname, data, transform, std::move(req1)); });
}
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1, DimReq&& req2) const
{
return V([&vname = std::as_const(vname), &data = data, &transform = transform, &req1 = req1, &req2 = req2](const auto& arg)
{ return arg.Read(vname, data, transform, std::move(req1), std::move(req2)); });
}
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1, DimReq&& req2, DimReq&& req3) const
{
return V([&vname = std::as_const(vname), &data = data, &transform = transform, &req1 = req1, &req2 = req2, &req3 = req3](const auto& arg)
{ return arg.Read(vname, data, transform, std::move(req1), std::move(req2), std::move(req3)); });
}
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1, DimReq&& req2, DimReq&& req3, DimReq&& req4) const
{
return V([&vname = std::as_const(vname), &data = data, &transform = transform, &req1 = req1, &req2 = req2, &req3 = req3, &req4 = req4](const auto& arg)
{ return arg.Read(vname, data, transform, std::move(req1), std::move(req2), std::move(req3), std::move(req4)); });
}
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform) const
{
return V([&vname = std::as_const(vname), &data = data, &transform = transform](const auto& arg) { return arg.Read(vname, data, transform); });
}
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, const std::vector<DimReqDef::DimReq>& reqs) const
{
return V([&vname = std::as_const(vname), &data = data, &transform = transform, &reqs = std::as_const(reqs)](const auto& arg) { return arg.Read(vname, data, transform, reqs); });
}
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, const MString& request) const
{
return V([&vname = std::as_const(vname), &data = data, &transform = transform, &request = std::as_const(request)](const auto& arg)
{ return arg.Read(vname, data, transform, request); });
}
template<class Type> Error Read(const MString& vname, std::vector<Type>& out) const
{
return V([&vname = std::as_const(vname), &out = out](const auto& arg) { return arg.Read(vname, out); });
}
Error OpenNC(const MString& filename)
{
auto pv = static_cast<NCZarrBase*>(this);
*pv = NCZarrBase(NCSimple());
auto p = std::get_if<NCSimple>(pv);
if(p == nullptr) return Error("NCZarr::OpenNC", "Impossible error!");
return p->Open(filename);
}
Error OpenZarr(const MString& product, const MString& dataset, bool time = true)
{
auto pv = static_cast<NCZarrBase*>(this);
*pv = NCZarrBase(Zarr());
auto p = std::get_if<Zarr>(pv);
if(p == nullptr) return Error("NCZarr::OpenNC", "Impossible error!");
return p->Open(product, dataset, time);
}
Error OpenMultiNC(const std::vector<MString>& names)
{
if(names.size() == 0) return Error("NCZarr::OpenMultiNC", "no names");
if(names.size() == 1) return OpenNC(names[0]);
auto pv = static_cast<NCZarrBase*>(this);
*pv = NCZarrBase(NCMulti());
auto p = std::get_if<NCMulti>(pv);
if(p == nullptr) return Error("NCZarr::OpenMultiNC", "Impossible error!");
return p->Open(names);
}
Error OpenMultiZarr(const MString& product, const std::vector<MString>& dsets, bool time = true)
{
if(dsets.size() == 0) return Error("NCZarr::OpenMultiZarr", "no datasets");
if(dsets.size() == 1) return OpenZarr(product, dsets[0], time);
auto pv = static_cast<NCZarrBase*>(this);
*pv = NCZarrBase(ZarrMulti());
auto p = std::get_if<ZarrMulti>(pv);
if(p == nullptr) return Error("NCZarr::OpenMultiZarr", "Impossible error!");
return p->Open(product, dsets, time);
}
};
#undef DEFFUNC
#undef DEFFUNC1
#undef DEFFUNC2

826
include/nczarrcommon.h

@ -0,0 +1,826 @@
#pragma once
#include "merrors.h"
#include <utility>
#include <variant>
using michlib::Error;
using michlib::int1;
using michlib::int2;
using michlib::int4;
using michlib::int8;
using michlib::int_cast;
using michlib::MString;
using michlib::RetVal;
using michlib::uint1;
using michlib::uint8;
class NcZarrTypes
{
protected:
using AttVT = std::variant<std::monostate, int8, uint8, double, MString, bool>;
class ArrCounter
{
using VT = std::vector<size_t>;
const VT count;
VT ind;
bool end;
public:
static size_t Index(const VT& i, const VT& c)
{
size_t out = 0;
size_t mul = 1;
for(size_t ii = i.size(); ii != 0; ii--)
{
out += mul * i[ii - 1];
mul *= c[ii - 1];
}
return out;
}
static VT Index(size_t lind, const VT& c)
{
VT out(c.size());
size_t j = lind;
for(auto i = c.size(); i > 0; i--)
{
out[i - 1] = j % c[i - 1];
j = j / c[i - 1];
}
return out;
}
ArrCounter() = delete;
ArrCounter(const VT& cnt): count(cnt), ind(cnt.size(), 0), end(false) {}
size_t operator[](size_t i) const { return ind[i]; }
ArrCounter& operator++()
{
size_t curind = count.size();
while(curind != 0)
{
ind[curind - 1]++;
if(ind[curind - 1] >= count[curind - 1])
{
ind[curind - 1] = 0;
curind--;
}
else
return *this;
}
ind = count;
end = true;
return *this;
}
explicit operator bool() const { return !end; }
size_t Index() const { return Index(ind, count); }
size_t Index(const VT& i) const { return Index(i, count); }
VT Index(size_t lind) const { return Index(lind, count); }
size_t Count(size_t i) const { return count[i]; }
const VT& VIndex() const { return ind; }
VT VIndex(const VT& start) const
{
VT out(ind.size());
for(size_t i = 0; i < ind.size(); i++) out[i] = ind[i] + start[i];
return out;
}
const auto& Count() const { return count; }
size_t N() const
{
size_t out = 1;
for(size_t i = 0; i < count.size(); i++) out *= count[i];
return out;
}
};
public:
enum class AttType
{
UNDEF,
INT,
UINT,
REAL,
STRING,
BOOL
};
enum class VarType
{
UNDEF,
FLOAT,
DOUBLE,
INT1,
INT2,
INT4,
INT8,
UINT1
};
protected:
template<VarType VT, class Dummy = void> struct VarType2Type;
template<class Dummy> struct VarType2Type<VarType::FLOAT, Dummy>
{
using type = float;
};
template<class Dummy> struct VarType2Type<VarType::DOUBLE, Dummy>
{
using type = double;
};
template<class Dummy> struct VarType2Type<VarType::INT1, Dummy>
{
using type = int1;
};
template<class Dummy> struct VarType2Type<VarType::INT2, Dummy>
{
using type = int2;
};
template<class Dummy> struct VarType2Type<VarType::INT4, Dummy>
{
using type = int4;
};
template<class Dummy> struct VarType2Type<VarType::INT8, Dummy>
{
using type = int8;
};
template<class Dummy> struct VarType2Type<VarType::UINT1, Dummy>
{
using type = uint1;
};
template<VarType VT> using Type = VarType2Type<VT>::type;
static constexpr size_t SizeOf(VarType vt)
{
switch(vt)
{
case(VarType::UNDEF): return 0;
case(VarType::FLOAT): return sizeof(Type<VarType::FLOAT>);
case(VarType::DOUBLE): return sizeof(Type<VarType::DOUBLE>);
case(VarType::INT1): return sizeof(Type<VarType::INT1>);
case(VarType::INT2): return sizeof(Type<VarType::INT2>);
case(VarType::INT4): return sizeof(Type<VarType::INT4>);
case(VarType::INT8): return sizeof(Type<VarType::INT8>);
case(VarType::UINT1): return sizeof(Type<VarType::UINT1>);
}
return 0;
}
template<class T> static size_t FindInd(const MString& name, const std::vector<T>& arr)
{
for(size_t i = 0; i < arr.size(); i++)
if(arr[i].Name() == name) return i;
return arr.size();
}
class Attribute: public AttVT
{
MString name;
public:
Attribute(const MString& n, AttVT&& v): AttVT(std::move(v)), name(n) {}
Attribute(const std::string& n, AttVT&& v): AttVT(std::move(v)), name(n.c_str(), n.size()) {}
const MString& Name() const { return name; }
AttType Type() const
{
if(std::holds_alternative<int8>(*this))
return AttType::INT;
else if(std::holds_alternative<uint8>(*this))
return AttType::UINT;
else if(std::holds_alternative<double>(*this))
return AttType::REAL;
else if(std::holds_alternative<MString>(*this))
return AttType::STRING;
else if(std::holds_alternative<bool>(*this))
return AttType::BOOL;
return AttType::UNDEF;
}
int8 I() const
{
if(std::holds_alternative<int8>(*this))
return std::get<int8>(*this);
else if(std::holds_alternative<uint8>(*this))
return int_cast<int8>(std::get<uint8>(*this));
else if(std::holds_alternative<double>(*this))
return static_cast<int8>(std::get<double>(*this));
else if(std::holds_alternative<MString>(*this))
return std::get<MString>(*this).ToInteger<int8>();
else if(std::holds_alternative<bool>(*this))
return std::get<bool>(*this) ? 1 : 0;
return 0;
}
uint8 U() const
{
if(std::holds_alternative<int8>(*this))
return int_cast<uint8>(std::get<int8>(*this));
else if(std::holds_alternative<uint8>(*this))
return std::get<uint8>(*this);
else if(std::holds_alternative<double>(*this))
return static_cast<uint8>(std::get<double>(*this));
else if(std::holds_alternative<MString>(*this))
return std::get<MString>(*this).ToInteger<uint8>();
else if(std::holds_alternative<bool>(*this))
return std::get<bool>(*this) ? 1 : 0;
return 0;
}
double D() const
{
if(std::holds_alternative<int8>(*this))
return std::get<int8>(*this);
else if(std::holds_alternative<uint8>(*this))
return std::get<uint8>(*this);
else if(std::holds_alternative<double>(*this))
return std::get<double>(*this);
else if(std::holds_alternative<MString>(*this))
return michlib_internal::RealType<sizeof(double)>::String2Real(std::get<MString>(*this).Buf());
else if(std::holds_alternative<bool>(*this))
return std::get<bool>(*this) ? 1 : 0;
return 0;
}
MString S() const
{
if(std::holds_alternative<int8>(*this))
return MString().FromInt(std::get<int8>(*this));
else if(std::holds_alternative<uint8>(*this))
return MString().FromUInt(std::get<uint8>(*this));
else if(std::holds_alternative<double>(*this))
return MString().FromReal(std::get<double>(*this));
else if(std::holds_alternative<MString>(*this))
return std::get<MString>(*this);
else if(std::holds_alternative<bool>(*this))
return MString().FromBool(std::get<bool>(*this));
return "";
}
bool B() const
{
if(std::holds_alternative<int8>(*this))
return std::get<int8>(*this) != 0;
else if(std::holds_alternative<uint8>(*this))
return std::get<uint8>(*this) != 0;
else if(std::holds_alternative<double>(*this))
return std::get<double>(*this) != 0.0;
else if(std::holds_alternative<MString>(*this))
return std::get<MString>(*this).ToBool();
else if(std::holds_alternative<bool>(*this))
return std::get<bool>(*this);
return false;
}
};
class Dimension
{
MString name;
size_t size;
public:
Dimension(const MString& str, size_t num): name(str), size(num) {}
const MString& Name() const { return name; }
size_t Size() const { return size; }
};
class Variable
{
public:
using FillType = std::variant<std::monostate, int8, uint8, double>;
private:
MString name;
VarType type = VarType::UNDEF;
std::vector<size_t> dims;
std::vector<Attribute> atts;
FillType fill;
public:
Variable(const MString& name_, VarType type_, std::vector<size_t>&& dims_, std::vector<Attribute>&& atts_, FillType fill_ = 0):
name(name_), type(type_), dims(std::move(dims_)), atts(std::move(atts_)), fill(fill_)
{
}
explicit operator bool() const { return type != VarType::UNDEF; }
const auto& Dims() const { return dims; }
size_t NDim() const { return dims.size(); }
size_t NAtt() const { return atts.size(); }
auto AttNames() const
{
std::vector<MString> out;
std::transform(atts.cbegin(), atts.cend(), std::back_inserter(out), [](const Attribute& a) { return a.Name(); });
return out;
}
AttType AttT(const MString& name) const
{
size_t ind = FindInd(name, atts);
return ind < atts.size() ? atts[ind].Type() : AttType::UNDEF;
}
int8 AttInt(const MString& name) const
{
size_t ind = FindInd(name, atts);
return ind < atts.size() ? atts[ind].I() : 0;
}
uint8 AttUInt(const MString& name) const
{
size_t ind = FindInd(name, atts);
return ind < atts.size() ? atts[ind].U() : 0;
}
double AttReal(const MString& name) const
{
size_t ind = FindInd(name, atts);
return ind < atts.size() ? atts[ind].D() : 0.0;
}
MString AttString(const MString& name) const
{
size_t ind = FindInd(name, atts);
return ind < atts.size() ? atts[ind].S() : MString();
}
bool AttBool(const MString& name) const
{
size_t ind = FindInd(name, atts);
return ind < atts.size() ? atts[ind].B() : false;
}
const MString& Name() const { return name; }
auto Type() const { return type; }
const auto& Fill() const { return fill; }
};
protected:
std::vector<Attribute> gats;
std::vector<Dimension> dims;
std::vector<Variable> vars;
public:
explicit operator bool() const { return !vars.empty(); }
size_t NDim(const MString& var) const
{
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].NDim() : 0;
}
size_t NAtt() const { return gats.size(); }
auto AttNames() const
{
std::vector<MString> out;
std::transform(gats.cbegin(), gats.cend(), std::back_inserter(out), [](const Attribute& a) { return a.Name(); });
return out;
}
size_t NAtt(const MString& var) const
{
if(!var.Exist()) return NAtt();
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].NAtt() : 0;
}
auto AttNames(const MString& var) const
{
if(!var.Exist()) return AttNames();
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].AttNames() : decltype(AttNames())();
}
auto VarNames() const
{
std::vector<MString> out;
std::transform(vars.cbegin(), vars.cend(), std::back_inserter(out), [](const Variable& v) { return v.Name(); });
return out;
}
VarType VarT(const MString& var) const
{
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].Type() : VarType::UNDEF;
}
auto VarFill(const MString& var) const
{
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].Fill() : Variable::FillType();
}
auto DimNames(const MString& var) const
{
size_t ind = FindInd(var, vars);
std::vector<MString> out;
if(ind >= vars.size()) return out;
auto vdims = vars[ind].Dims();
std::transform(vdims.cbegin(), vdims.cend(), std::back_inserter(out), [&dims = std::as_const(dims)](const size_t& i) { return dims[i].Name(); });
return out;
}
size_t DimSize(const MString& var, const MString& dim) const
{
if(!HasDim(var, dim)) return 0;
size_t ind = FindInd(dim, dims);
return ind < dims.size() ? dims[ind].Size() : 0;
}
AttType AttT(const MString& var, const MString& name) const
{
if(!var.Exist())
{
size_t ind = FindInd(name, gats);
return ind < gats.size() ? gats[ind].Type() : AttType::UNDEF;
}
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].AttT(name) : AttType::UNDEF;
}
int8 AttInt(const MString& var, const MString& name) const
{
if(!var.Exist())
{
size_t ind = FindInd(name, gats);
return ind < gats.size() ? gats[ind].I() : 0;
}
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].AttInt(name) : 0;
}
uint8 AttUInt(const MString& var, const MString& name) const
{
if(!var.Exist())
{
size_t ind = FindInd(name, gats);
return ind < gats.size() ? gats[ind].U() : 0;
}
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].AttUInt(name) : 0;
}
double AttReal(const MString& var, const MString& name) const
{
if(!var.Exist())
{
size_t ind = FindInd(name, gats);
return ind < gats.size() ? gats[ind].D() : 0.0;
}
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].AttReal(name) : 0.0;
}
MString AttString(const MString& var, const MString& name) const
{
if(!var.Exist())
{
size_t ind = FindInd(name, gats);
return ind < gats.size() ? gats[ind].S() : MString();
}
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].AttString(name) : MString();
}
bool AttBool(const MString& var, const MString& name) const
{
if(!var.Exist())
{
size_t ind = FindInd(name, gats);
return ind < gats.size() ? gats[ind].B() : false;
}
size_t ind = FindInd(var, vars);
return ind < vars.size() ? vars[ind].AttBool(name) : false;
}
auto AttT(const MString& name) const { return AttT("", name); }
auto AttInt(const MString& name) const { return AttInt("", name); }
auto AttUInt(const MString& name) const { return AttUInt("", name); }
auto AttReal(const MString& name) const { return AttReal("", name); }
auto AttString(const MString& name) const { return AttString("", name); }
auto AttBool(const MString& name) const { return AttBool("", name); }
bool HasDim(const MString& var, const MString& name) const
{
size_t vind = FindInd(var, vars);
if(vind >= vars.size()) return false;
for(const auto dind: vars[vind].Dims())
if(dims[dind].Name() == name) return true;
return false;
}
bool HasVar(const MString& name) const { return FindInd(name, vars) < vars.size(); }
bool HasAtt(const MString& vname, const MString& aname) const { return AttT(vname, aname) != AttType::UNDEF; }
bool HasAtt(const MString& aname) const { return AttT(aname) != AttType::UNDEF; }
};
class DimReqDef
{
protected:
struct DimReq
{
static const auto fill = std::numeric_limits<size_t>::max();
MString name;
size_t beg, count;
DimReq(): name(MString()), beg(fill), count(fill) {}
DimReq(const char* n): name(n), beg(fill), count(fill) {}
DimReq(const MString& n): name(n), beg(fill), count(fill) {}
DimReq(MString&& n): name(std::move(n)), beg(fill), count(fill) {}
DimReq(const char* n, size_t s): name(n), beg(s), count(fill) {}
DimReq(const MString& n, size_t s): name(n), beg(s), count(fill) {}
DimReq(MString&& n, size_t s): name(std::move(n)), beg(s), count(fill) {}
DimReq(const char* n, size_t s, size_t c): name(n), beg(s), count(c) {}
DimReq(const MString& n, size_t s, size_t c): name(n), beg(s), count(c) {}
DimReq(MString&& n, size_t s, size_t c): name(std::move(n)), beg(s), count(c) {}
const MString& Name() const { return name; }
};
};
template<class C> class NcZarrRead: public C, public DimReqDef
{
template<class Data> static constexpr size_t Dimensionity()
{
if constexpr(requires(Data& d) { d(0, 0, 0, 0); }) return 4;
if constexpr(requires(Data& d) { d(0, 0, 0); }) return 3;
if constexpr(requires(Data& d) { d(0, 0); }) return 2;
if constexpr(requires(Data& d) { d(0); }) return 1;
return 0;
}
template<class Data, size_t D, class Dummy = void> struct DataTypeExtractorS;
template<class Data, class Dummy> struct DataTypeExtractorS<Data, 1, Dummy>
{
using type = std::decay_t<decltype(std::declval<Data>()(0))>;
};
template<class Data, class Dummy> struct DataTypeExtractorS<Data, 2, Dummy>
{
using type = std::decay_t<decltype(std::declval<Data>()(0, 0))>;
};
template<class Data, class Dummy> struct DataTypeExtractorS<Data, 3, Dummy>
{
using type = std::decay_t<decltype(std::declval<Data>()(0, 0, 0))>;
};
template<class Data, class Dummy> struct DataTypeExtractorS<Data, 4, Dummy>
{
using type = std::decay_t<decltype(std::declval<Data>()(0, 0, 0, 0))>;
};
template<class Data> using DataTypeExtractor = DataTypeExtractorS<Data, Dimensionity<Data>()>::type;
template<class VType, class Data, class Transform>
Error Read(const MString& vname, const std::vector<size_t>& transindex, Data& data, Transform transform, std::vector<DimReq> reqs) const
{
const size_t indim = reqs.size();
constexpr size_t outdim = Dimensionity<Data>();
std::vector<size_t> start;
std::vector<size_t> count;
start.resize(indim);
count.resize(indim);
for(size_t i = 0; i < indim; i++)
{
start[i] = reqs[i].beg;
count[i] = reqs[i].count;
}
using DataType = DataTypeExtractor<Data>;
DataType fillout;
bool havefill = C::VarFill(vname).index() > 0;
VType fillin = std::visit(
[](auto v)
{
if constexpr(std::is_convertible_v<decltype(v), VType>)
return static_cast<VType>(v);
else
return std::numeric_limits<VType>::max();
},
C::VarFill(vname));
if constexpr(requires(Data& d) { // Data have own fillvalue
{ d.Fillval() } -> std::convertible_to<DataType>;
})
fillout = data.Fillval();
else // Data does'nt have own fillvalue, using variable fillvalue
fillout = static_cast<DataType>(fillin);
auto ret = C::template Read<VType>(vname, start.data(), count.data());
if(!ret) return ret;
const auto& rawdata = ret.Value();
std::vector<size_t> mul(indim, 1);
for(size_t i = indim - 1; i > 0; i--) mul[i - 1] = mul[i] * count[i];
size_t inind = 0;
for(typename C::ArrCounter i(count); i; ++i)
{
// TODO: Remove this testing block
size_t cind = 0;
for(size_t j = 0; j < indim; j++) cind += i[j] * mul[j];
if(cind != inind) return {"NcZarrRead::Read", "Internal error"};
if(i.Index() != inind) return {"NcZarrRead::Read", "Internal error"};
if(inind != i.Index(i.Index(inind, count), count)) return {"NcZarrRead::Read", "Internal error"};
DataType out;
const VType& in = rawdata(inind);
if(havefill && in == fillin)
out = fillout;
else
out = transform(in);
if constexpr(outdim == 1)
data(i[transindex[0]]) = out;
else if constexpr(outdim == 2)
data(i[transindex[0]], i[transindex[1]]) = out;
else if constexpr(outdim == 3)
data(i[transindex[0]], i[transindex[1]], i[transindex[2]]) = out;
else if constexpr(outdim == 4)
data(i[transindex[0]], i[transindex[1]], i[transindex[2]], i[transindex[3]]) = out;
inind++;
}
return Error();
}
public:
// Request is string
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, const char* request) const
{
return Read(vname, data, transform, MString(request));
}
// Request by one dimension
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1) const
{
return Read(vname, data, transform, std::vector<DimReq>{std::move(req1)});
}
// Request by two dimension
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1, DimReq&& req2) const
{
return Read(vname, data, transform, std::vector<DimReq>{std::move(req1), std::move(req2)});
}
// Request by three dimension
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1, DimReq&& req2, DimReq&& req3) const
{
return Read(vname, data, transform, std::vector<DimReq>{std::move(req1), std::move(req2), std::move(req3)});
}
// Request by four dimension
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1, DimReq&& req2, DimReq&& req3, DimReq&& req4) const
{
return Read(vname, data, transform, std::vector<DimReq>{std::move(req1), std::move(req2), std::move(req3), std::move(req4)});
}
// Request full variable
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform) const
{
static const MString pref = "NcZarrRead::Read";
if(!C::HasVar(vname)) return {pref, "Variable " + vname + " not found"};
std::vector<struct DimReq> pdims;
const auto vdims = C::DimNames(vname);
std::transform(vdims.cbegin(), vdims.cend(), std::back_inserter(pdims),
[this, &vname = std::as_const(vname)](const MString& n) -> struct DimReq { return {n, 0, C::DimSize(vname, n)}; });
return Read(vname, data, transform, pdims);
}
// Base function for all Read's
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, const std::vector<DimReq>& reqs) const
{
static const MString pref = "NcZarrRead::Read";
if(!C::HasVar(vname)) return {pref, "Variable " + vname + " not found"};
std::vector<struct DimReq> pdims;
{
const auto vdims = C::DimNames(vname);
std::transform(vdims.cbegin(), vdims.cend(), std::back_inserter(pdims), [](const MString& n) -> struct DimReq { return {n, 0, 1}; });
}
std::vector<size_t> transindex;
// Parse request
if(reqs.size() == 0) return {pref, "Empty request"};
for(const auto& req: reqs)
{
size_t ind = C::FindInd(req.name, pdims);
if(ind >= pdims.size()) return {pref, "Variable " + vname + " has no dimension " + req.name};
for(size_t i = 0; i < transindex.size(); i++)
if(transindex[i] == ind) return {pref, "Parameters for dimension " + req.name + " already defined"};
transindex.push_back(ind);
size_t dlen = C::DimSize(vname, pdims[ind].name);
if(req.beg == req.fill && req.count == req.fill) // Only name, so, we request full length
{
pdims[ind].beg = 0;
pdims[ind].count = dlen;
}
else if(req.count == req.fill) // Name and first index
{
pdims[ind].beg = req.beg;
pdims[ind].count = 1;
}
else // Name, first index, count
{
pdims[ind].beg = req.beg;
pdims[ind].count = req.count;
}
// Sanity checks
if(pdims[ind].count <= 0) return {pref, "Error parsing request: count must be greter then zero"};
if(pdims[ind].beg >= dlen) return {pref, MString("Error parsing request: start index ") + pdims[ind].beg + " must be lesser then " + pdims[ind].name + " size " + dlen};
if(pdims[ind].beg + pdims[ind].count > dlen)
return {pref, MString("Error parsing request: start index ") + pdims[ind].beg + " with count " + pdims[ind].count + " exceeds " + pdims[ind].name + " size " + dlen};
// Ignore hyperplanes in requests for calculation of data dimensionality
if(pdims[transindex.back()].count == 1) transindex.pop_back();
}
if(transindex.size() != Dimensionity<Data>())
return {pref, MString("Output data dimensions (") + Dimensionity<Data>() + ") not corresponding request dimensions (" + transindex.size() + ")"};
switch(C::VarT(vname))
{
case(C::VarType::UNDEF): return {pref, "No variable with name " + vname + " (impossible)"};
case(C::VarType::FLOAT): return Read<typename C::template Type<C::VarType::FLOAT>>(vname, transindex, data, transform, pdims);
case(C::VarType::DOUBLE): return Read<typename C::template Type<C::VarType::DOUBLE>>(vname, transindex, data, transform, pdims);
case(C::VarType::INT1): return Read<typename C::template Type<C::VarType::INT1>>(vname, transindex, data, transform, pdims);
case(C::VarType::INT2): return Read<typename C::template Type<C::VarType::INT2>>(vname, transindex, data, transform, pdims);
case(C::VarType::INT4): return Read<typename C::template Type<C::VarType::INT4>>(vname, transindex, data, transform, pdims);
case(C::VarType::INT8): return Read<typename C::template Type<C::VarType::INT8>>(vname, transindex, data, transform, pdims);
case(C::VarType::UINT1): return Read<typename C::template Type<C::VarType::INT1>>(vname, transindex, data, transform, pdims);
}
return {pref, "Internal error (impossible)"};
}
// Request by string argument
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, const MString& request) const
{
static const MString pref = "NcZarrRead::Read";
std::vector<struct DimReq> pdims;
// Parse request
const auto dimdesc = request.Split(";, \t");
if(dimdesc.size() == 0) return {pref, "Empty request"};
for(const auto& dd: dimdesc)
{
const auto dimpar = dd.Split(":", true);
if(dimpar.size() == 1) // Only name, so, we request full length
pdims.emplace_back(dimpar[0]);
else if(dimpar.size() == 2) // Name and first index
pdims.emplace_back(dimpar[0], dimpar[1].ToInteger<size_t>());
else if(dimpar.size() == 3) // Name, first index, count
pdims.emplace_back(dimpar[0], dimpar[1].ToInteger<size_t>(), dimpar[2].ToInteger<size_t>());
else
return {pref, "Can't parse expression " + dd};
}
return Read(vname, data, transform, pdims);
}
// Request full one-dimensional variable
template<class Type> Error Read(const MString& vname, std::vector<Type>& out) const
{
const auto& dnames = C::DimNames(vname);
if(dnames.size() > 0) out.resize(C::DimSize(vname, dnames[0]));
auto data = [&vec = out](size_t i) -> Type& { return vec[i]; };
return Read(vname, data, std::identity());
}
};

206
include/nczarrmulti.h

@ -0,0 +1,206 @@
#pragma once
#include "ncsimple.h"
#include "zarr.h"
#include <set>
#define DEFFUNC(func, def) \
auto func(const MString& var) const \
{ \
const auto ind = FindVar(var); \
return ind < sources.size() ? sources[ind].func(var) : (def); \
}
#define DEFFUNCA(func, def) \
auto func(const MString& att) const \
{ \
const auto ind = FindAtt(att); \
return ind < sources.size() ? sources[ind].func(att) : (def); \
}
#define DEFFUNC2(func, def) \
auto func(const MString& var, const MString& name) const \
{ \
const auto ind = FindVar(var); \
return ind < sources.size() ? sources[ind].func(var, name) : (def); \
}
#define DEFFUNCNAMES(func) \
auto func() const \
{ \
std::set<MString> names; \
for(const auto& s: sources) \
{ \
auto snames = s.func(); \
for(const auto& name: snames) names.insert(name); \
} \
std::vector<MString> out; \
for(const auto& name: names) out.push_back(name); \
return out; \
}
template<class T> class NCZarrMultiCommon: private DimReqDef
{
protected:
std::vector<T> sources;
private:
size_t FindVar(const MString& var) const
{
for(size_t i = 0; i < sources.size(); i++)
if(sources[i].HasVar(var)) return i;
return sources.size();
}
size_t FindAtt(const MString& att) const
{
for(size_t i = 0; i < sources.size(); i++)
if(sources[i].HasAtt(att)) return i;
return sources.size();
}
public:
explicit operator bool() const
{
if(sources.empty()) return false;
for(const auto& s: sources)
if(!s) return false;
return true;
}
size_t NDim() const { return DimNames().size(); }
DEFFUNC(NDim, 0)
size_t NAtt() const { return AttNames().size(); }
DEFFUNC(NAtt, 0)
DEFFUNCNAMES(AttNames)
DEFFUNC(AttNames, decltype(AttNames())())
DEFFUNCNAMES(VarNames)
DEFFUNC(VarT, T::VarType::UNDEF)
DEFFUNC(VarFill, decltype(T().VarFill(MString()))())
DEFFUNC(DimNames, decltype(T().DimNames(MString()))())
DEFFUNC2(DimSize, 0)
DEFFUNC2(AttT, T::AttType::UNDEF)
DEFFUNC2(AttInt, 0)
DEFFUNC2(AttUInt, 0)
DEFFUNC2(AttReal, 0.0)
DEFFUNC2(AttString, MString())
DEFFUNC2(AttBool, false)
DEFFUNCA(AttT, T::AttType::UNDEF)
DEFFUNCA(AttInt, 0)
DEFFUNCA(AttUInt, 0)
DEFFUNCA(AttReal, 0.0)
DEFFUNCA(AttString, MString())
DEFFUNCA(AttBool, false)
DEFFUNC2(HasDim, false)
DEFFUNC(HasVar, false)
DEFFUNCA(HasAtt, false)
DEFFUNC2(HasAtt, false)
// Request is string
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, const char* request) const
{
return Read(vname, data, transform, MString(request));
}
// Request by one dimension
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1) const
{
return Read(vname, data, transform, std::vector<DimReq>{std::move(req1)});
}
// Request by two dimension
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1, DimReq&& req2) const
{
return Read(vname, data, transform, std::vector<DimReq>{std::move(req1), std::move(req2)});
}
// Request by three dimension
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1, DimReq&& req2, DimReq&& req3) const
{
return Read(vname, data, transform, std::vector<DimReq>{std::move(req1), std::move(req2), std::move(req3)});
}
// Request by four dimension
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, DimReq&& req1, DimReq&& req2, DimReq&& req3, DimReq&& req4) const
{
return Read(vname, data, transform, std::vector<DimReq>{std::move(req1), std::move(req2), std::move(req3), std::move(req4)});
}
// Request full variable
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform) const
{
auto ind = FindVar(vname);
if(ind < sources.size()) return sources[ind].Read(vname, data, transform);
return Error("NCZarrMultiCommon::Read", "Variable " + vname + " not found");
}
// Base function for all Read's
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, const std::vector<DimReqDef::DimReq>& reqs) const
{
auto ind = FindVar(vname);
if(ind < sources.size()) return sources[ind].Read(vname, data, transform, reqs);
return Error("NCZarrMultiCommon::Read", "Variable " + vname + " not found");
}
// Request by string argument
template<class Data, class Transform> Error Read(const MString& vname, Data& data, Transform transform, const MString& request) const
{
auto ind = FindVar(vname);
if(ind < sources.size()) return sources[ind].Read(vname, data, transform, request);
return Error("NCZarrMultiCommon::Read", "Variable " + vname + " not found");
}
// Request full one-dimensional variable
template<class Type> Error Read(const MString& vname, std::vector<Type>& out) const
{
auto ind = FindVar(vname);
if(ind < sources.size()) return sources[ind].Read(vname, out);
return Error("NCZarrMultiCommon::Read", "Variable " + vname + " not found");
}
};
#undef DEFFUNC
#undef DEFFUNCA
#undef DEFFUNC2
#undef DEFFUNCNAME
class NCMulti: public NCZarrMultiCommon<NCSimple>
{
public:
Error Open(const std::vector<MString>& names)
{
sources.clear();
decltype(sources) newsources;
static const MString pref = "NcMulti::Open";
if(names.size() == 0) return Error(pref, "empty file list");
for(const auto& name: names)
{
newsources.emplace_back();
auto ret = newsources.back().Open(name);
if(!ret) return ret.Add(pref, "Can't open file " + name);
}
sources = std::move(newsources);
return Error();
}
};
class ZarrMulti: public NCZarrMultiCommon<Zarr>
{
public:
Error Open(const MString& product, const std::vector<MString>& dsets, bool time = true)
{
sources.clear();
decltype(sources) newsources;
static const MString pref = "ZarrMulti::Open";
if(dsets.size() == 0) return Error(pref, "empty datasets list");
for(const auto& dset: dsets)
{
newsources.emplace_back();
auto ret = newsources.back().Open(product, dset, time);
if(!ret) return ret.Add(pref, "Can't open dataset " + dset);
}
sources = std::move(newsources);
return Error();
}
};

132
include/simple2ddata.h

@ -1,7 +1,8 @@
#pragma once
#include "basedata.h"
#include "geohelpers.h"
using michlib::Floor;
using michlib::M_PI;
class Simple2DData: public BaseData
{
@ -9,6 +10,8 @@ class Simple2DData: public BaseData
size_t nx = 0, ny = 0;
real xstep = 0.0, ystep = 0.0;
static real D(real lon1, real lat1, real lon2, real lat2) { return michlib::GCD(lon1 * M_PI / 180.0, lat1 * M_PI / 180.0, lon2 * M_PI / 180.0, lat2 * M_PI / 180.0) * 6371.0; }
public:
using BaseData::IsFill;
using BaseData::V;
@ -45,21 +48,82 @@ class Simple2DData: public BaseData
real XStep() const { return xstep; }
real YStep() const { return ystep; }
auto GridPoint(real lon, real lat) const
struct GridPoint GridPos(real x, real y) const
{
struct GridPoint out;
if(!*this) return out;
if(x < x0 || x > x0 + (nx - 1) * xstep || y < y0 || y > y0 + (ny - 1) * ystep) return out;
real rx = x - x0;
real ry = y - y0;
out.ix = static_cast<decltype(out.ix)>(michlib::Floor(rx / xstep));
out.iy = static_cast<decltype(out.ix)>(michlib::Floor(ry / ystep));
out.x = rx - out.ix * xstep;
out.y = ry - out.iy * ystep;
return out;
}
real dVdX(size_t ix, size_t iy) const
{
if(IsFill(ix, iy)) return Fillval();
if(ix == 0 || IsFill(ix - 1, iy))
{
if(IsFill(ix + 1, iy))
return Fillval();
else
return (V(ix + 1, iy) - V(ix, iy)) / D(Lon(ix, iy), Lat(ix, iy), Lon(ix + 1, iy), Lat(ix + 1, iy));
}
if(ix == nx - 1 || IsFill(ix + 1, iy))
{
if(IsFill(ix - 1, iy))
return Fillval();
else
return (V(ix, iy) - V(ix - 1, iy)) / D(Lon(ix - 1, iy), Lat(ix - 1, iy), Lon(ix, iy), Lat(ix, iy));
}
return 0.5 * ((V(ix + 1, iy) - V(ix, iy)) / D(Lon(ix, iy), Lat(ix, iy), Lon(ix + 1, iy), Lat(ix + 1, iy)) +
(V(ix, iy) - V(ix - 1, iy)) / D(Lon(ix - 1, iy), Lat(ix - 1, iy), Lon(ix, iy), Lat(ix, iy)));
}
real dVdY(size_t ix, size_t iy) const
{
if(IsFill(ix, iy)) return Fillval();
if(iy == 0 || IsFill(ix, iy - 1))
{
if(IsFill(ix, iy + 1))
return Fillval();
else
return (V(ix, iy + 1) - V(ix, iy)) / D(Lon(ix, iy), Lat(ix, iy), Lon(ix, iy + 1), Lat(ix, iy + 1));
}
if(iy == ny - 1 || IsFill(ix, iy + 1))
{
if(IsFill(ix, iy - 1))
return Fillval();
else
return (V(ix, iy) - V(ix, iy - 1)) / D(Lon(ix, iy - 1), Lat(ix, iy - 1), Lon(ix, iy), Lat(ix, iy));
}
return 0.5 * ((V(ix, iy + 1) - V(ix, iy)) / D(Lon(ix, iy), Lat(ix, iy), Lon(ix, iy + 1), Lat(ix, iy + 1)) +
(V(ix, iy) - V(ix, iy - 1)) / D(Lon(ix, iy - 1), Lat(ix, iy - 1), Lon(ix, iy), Lat(ix, iy)));
}
real Grad(size_t ix, size_t iy) const
{
struct GridPointLocation loc;
loc.ix = static_cast<size_t>(Floor((lon - x0) / xstep));
loc.iy = static_cast<size_t>(Floor((lat - y0) / ystep));
loc.x = lon - x0 - loc.ix * xstep;
loc.y = lat - y0 - loc.iy * ystep;
return loc;
real grx = dVdX(ix, iy);
real gry = dVdY(ix, iy);
if(grx == Fillval() || gry == Fillval()) return Fillval();
return michlib::Hypot(grx, gry);
}
real Grad(size_t i) const { return Grad(i % Nx(), i / Nx()); }
};
class Rect2DData: public BaseData
{
std::vector<real> lon, lat;
static real D(real lon1, real lat1, real lon2, real lat2) { return michlib::GCD(lon1 * M_PI / 180.0, lat1 * M_PI / 180.0, lon2 * M_PI / 180.0, lat2 * M_PI / 180.0) * 6371.0; }
public:
using BaseData::IsFill;
using BaseData::V;
@ -87,4 +151,56 @@ class Rect2DData: public BaseData
real Ix2Lon(size_t ix) const { return lon[ix]; }
real Iy2Lat(size_t iy) const { return lat[iy]; }
real dVdX(size_t ix, size_t iy) const
{
if(IsFill(ix, iy)) return Fillval();
if(ix == 0 || IsFill(ix - 1, iy))
{
if(IsFill(ix + 1, iy))
return Fillval();
else
return (V(ix + 1, iy) - V(ix, iy)) / D(Lon(ix, iy), Lat(ix, iy), Lon(ix + 1, iy), Lat(ix + 1, iy));
}
if(ix == Nx() - 1 || IsFill(ix + 1, iy))
{
if(IsFill(ix - 1, iy))
return Fillval();
else
return (V(ix, iy) - V(ix - 1, iy)) / D(Lon(ix - 1, iy), Lat(ix - 1, iy), Lon(ix, iy), Lat(ix, iy));
}
return 0.5 * ((V(ix + 1, iy) - V(ix, iy)) / D(Lon(ix, iy), Lat(ix, iy), Lon(ix + 1, iy), Lat(ix + 1, iy)) +
(V(ix, iy) - V(ix - 1, iy)) / D(Lon(ix - 1, iy), Lat(ix - 1, iy), Lon(ix, iy), Lat(ix, iy)));
}
real dVdY(size_t ix, size_t iy) const
{
if(IsFill(ix, iy)) return Fillval();
if(iy == 0 || IsFill(ix, iy - 1))
{
if(IsFill(ix, iy + 1))
return Fillval();
else
return (V(ix, iy + 1) - V(ix, iy)) / D(Lon(ix, iy), Lat(ix, iy), Lon(ix, iy + 1), Lat(ix, iy + 1));
}
if(iy == Ny() - 1 || IsFill(ix, iy + 1))
{
if(IsFill(ix, iy - 1))
return Fillval();
else
return (V(ix, iy) - V(ix, iy - 1)) / D(Lon(ix, iy - 1), Lat(ix, iy - 1), Lon(ix, iy), Lat(ix, iy));
}
return 0.5 * ((V(ix, iy + 1) - V(ix, iy)) / D(Lon(ix, iy), Lat(ix, iy), Lon(ix, iy + 1), Lat(ix, iy + 1)) +
(V(ix, iy) - V(ix, iy - 1)) / D(Lon(ix, iy - 1), Lat(ix, iy - 1), Lon(ix, iy), Lat(ix, iy)));
}
real Grad(size_t ix, size_t iy) const
{
real grx = dVdX(ix, iy);
real gry = dVdY(ix, iy);
if(grx == Fillval() || gry == Fillval()) return Fillval();
return michlib::Hypot(grx, gry);
}
real Grad(size_t i) const { return Grad(i % Nx(), i / Nx()); }
};

29
include/traits.h

@ -169,8 +169,31 @@ concept ReadIs1DGeoArray = requires {
};
template<class T>
concept CanInterpolate2D = requires {
concept ReadIs2DXYRectArray = requires {
{
std::declval<ReadType<T>>().GridPoint(0.0, 0.0)
} -> std::convertible_to<struct GridPointLocation>;
std::declval<ReadType<T>>().Ix2X(0)
} -> std::convertible_to<real>;
{
std::declval<ReadType<T>>().Iy2Y(0)
} -> std::convertible_to<real>;
};
template<class T>
concept ReadIs2DXYArray = requires {
{
std::declval<ReadType<T>>().X(0, 0)
} -> std::convertible_to<real>;
{
std::declval<ReadType<T>>().Y(0, 0)
} -> std::convertible_to<real>;
};
template<class T>
concept ReadIs1DArray = requires {
{
std::declval<ReadType<T>>().X(0)
} -> std::convertible_to<real>;
{
std::declval<ReadType<T>>().Y(0)
} -> std::convertible_to<real>;
};

178
include/zarr.h

@ -0,0 +1,178 @@
#pragma once
#include "GPL.h"
#include "cache.h"
#include "curlfuncs.h"
#include "nczarrcommon.h"
#include <json/json.h>
#include <variant>
class ZarrTypes: public NcZarrTypes
{
protected:
template<class VType> class ReadedData
{
//public:
using Vec = std::vector<size_t>;
private:
Vec start, chunkstart;
ArrCounter mainind, chunkind, inchunkind;
std::vector<std::unique_ptr<VType[]>> data;
public:
ReadedData(): mainind(Vec()), chunkind(Vec()), inchunkind(Vec()) {}
ReadedData(size_t N, const size_t* start, const size_t* count, const size_t* csize, std::vector<std::unique_ptr<VType[]>>&& d):
start(start, start + N),
chunkstart(
[](size_t N, const size_t* st, const size_t* cs)
{
Vec out(N);
for(size_t i = 0; i < N; i++) out[i] = st[i] / cs[i];
return out;
}(N, start, csize)),
mainind(Vec(count, count + N)),
chunkind(
[](size_t N, const size_t* st, const size_t* cn, const size_t* cs)
{
Vec out(N);
for(size_t i = 0; i < N; i++) out[i] = (st[i] + cn[i]) / cs[i] - st[i] / cs[i] + 1;
return out;
}(N, start, count, csize)),
inchunkind(Vec(csize, csize + N)),
data(std::move(d))
{
}
VType operator()(size_t lini) const
{
Vec ind = mainind.Index(lini, mainind.Count());
Vec cind(ind.size()), inind(ind.size());
for(size_t i = 0; i < ind.size(); i++)
{
cind[i] = (ind[i] + start[i]) / inchunkind.Count(i) - chunkstart[i]; // indes of chunk
inind[i] = (ind[i] + start[i]) % inchunkind.Count(i); // index inside chunk
}
size_t chunk = chunkind.Index(cind);
size_t inside = inchunkind.Index(inind);
return data[chunk][inside];
}
};
private:
// Create attribute from json value
static AttVT CreateAtt(const Json::Value& val)
{
if(val.type() == Json::intValue) return AttVT{std::in_place_type<int8>, val.asInt64()};
if(val.type() == Json::uintValue) return AttVT(std::in_place_type<uint8>, val.asUInt64());
if(val.type() == Json::realValue) return AttVT(std::in_place_type<double>, val.asDouble());
if(val.type() == Json::stringValue)
{
auto str = val.asString();
return AttVT(std::in_place_type<MString>, MString(str.c_str(), str.size()));
}
if(val.type() == Json::booleanValue) return AttVT(std::in_place_type<bool>, val.asBool());
return AttVT();
}
public:
// Read attributes from .zattrs
static auto ReadAtts(const Json::Value& obj)
{
std::vector<Attribute> out;
if(obj.type() != Json::objectValue) return out;
const auto keys = obj.getMemberNames();
for(const auto& key: keys)
if(key != "_ARRAY_DIMENSIONS") out.emplace_back(key, CreateAtt(obj[key]));
return out;
}
};
class ZarrFunctions: public ZarrTypes
{
std::unique_ptr<GenericCache> cache;
CURLRAII chandle;
MString url;
MString proxyurl;
std::vector<std::vector<size_t>> chunks;
// Find variable names in metadata
static std::vector<MString> ReadVarNames(const Json::Value& meta);
Error AddVar(const MString& name, const Json::Value& zattrs, const Json::Value& zarray);
protected:
ZarrFunctions()
{
auto oldprefix = michlib::GPL.UsePrefix("ZARR");
cache.reset(CreateCache(michlib::GPL.ParameterSValue("Cache", "")));
proxyurl = michlib::GPL.ParameterSValue("Proxy", "");
if(proxyurl.Exist()) curl_easy_setopt(chandle, CURLOPT_PROXY, proxyurl.Buf());
michlib::GPL.UsePrefix(oldprefix);
if(!cache)
{
michlib::errmessage("Can't init data cache");
cache.reset(new FakeCache);
}
}
template<class VType> RetVal<ReadedData<VType>> Read(const MString& var, const size_t* start, const size_t* count) const
{
using Vec = std::vector<size_t>;
size_t ind = FindInd(var, vars);
const size_t N = vars[ind].NDim();
const auto& csize = chunks[ind];
Vec chunkstart(
[](size_t N, const size_t* st, const size_t* cs)
{
Vec out(N);
for(size_t i = 0; i < N; i++) out[i] = st[i] / cs[i];
return out;
}(N, start, csize.data()));
ArrCounter chunkind(
[](size_t N, const size_t* st, const size_t* cn, const size_t* cs)
{
Vec out(N);
for(size_t i = 0; i < N; i++) out[i] = (st[i] + cn[i] - 1) / cs[i] - st[i] / cs[i] + 1;
return out;
}(N, start, count, csize.data()));
bool havefill = vars[ind].Fill().index() > 0;
VType fill = std::visit(
[](auto v)
{
if constexpr(std::is_convertible_v<decltype(v), VType>)
return static_cast<VType>(v);
else
return std::numeric_limits<VType>::max();
},
vars[ind].Fill());
std::vector<std::unique_ptr<VType[]>> cdata;
size_t chunksize = 1;
for(const auto c: csize) chunksize *= c;
cdata.resize(chunkind.N());
for(; chunkind; ++chunkind)
{
cdata[chunkind.Index()].reset(new VType[chunksize]);
auto res = GetChunk(var, chunkind.VIndex(chunkstart), chunksize, sizeof(VType), cdata[chunkind.Index()].get(), havefill ? &fill : nullptr);
if(!res) return res;
}
return ReadedData<VType>(N, start, count, csize.data(), std::move(cdata));
}
Error GetChunk(const MString& var, const std::vector<size_t>& chunkind, size_t chunksize, size_t elsize, void* data, const void* fill) const;
public:
Error Open(const MString& product, const MString& dataset, bool time = true);
};
using Zarr = NcZarrRead<ZarrFunctions>;

2
michlib

@ -1 +1 @@
Subproject commit a2105413f8a10e8695c85706487d4db5d2f54a61
Subproject commit f380988909fadd7f42c7ce09288c4ff3198ca318

6
sources/AVISO.h

@ -1,7 +1,7 @@
#pragma once
#include "layereddata.h"
#include "layereddataz.h"
class AVISOData: public LayeredData
class AVISOData: public LayeredDataZ
{
enum Type
{
@ -49,6 +49,6 @@ class AVISOData: public LayeredData
return "Unknown dataset: " + dataset;
SetTitle(DataTitle());
return LayeredData::Open(dataset);
return LayeredDataZ::Open(dataset);
}
};

26
sources/AVISOLOCAL.cpp

@ -6,7 +6,8 @@ MString AVISOLOCALData::Info() const
if(!isOk()) return "";
NCFileA nc;
struct CoordNames cn, dn;
struct CoordNames cn;
struct DimNames dn;
std::set<MString> vars;
nc.Reset(datapath + "/uv-" + times[0].ToString() + ".nc");
@ -115,28 +116,7 @@ bool AVISOLOCALData::Read(const MString& vname, std::map<MString, AVISOLOCALData
}
if(!name.Exist()) // Conversion read
{
// U and U2 from u and v
if(vname == "U" || vname == "U2")
{
bool square = vname == "U2";
if(!(Read("u", cache, ip, i) && Read("v", cache, ip, i))) return false;
cache[vname] = cache.at("u").CopyGrid();
auto& U = cache.at(vname);
const auto& u = cache.at("u");
const auto& v = cache.at("v");
if(u.Unit().Exist()) U.SetUnit(square ? ("(" + u.Unit() + ")2") : u.Unit());
for(size_t ind = 0; ind < U.N(); ind++)
{
if(u.IsFill(ind) || v.IsFill(ind))
U.V(ind) = U.Fillval();
else
U.V(ind) = square ? (u(ind) * u(ind) + v(ind) * v(ind)) : michlib::Hypot(u(ind), v(ind));
}
return true;
}
return false;
}
return TransformationRead(this, vname, cache, ip, i);
// Direct read
Data data;

4
sources/AVISOLOCAL.h

@ -49,11 +49,11 @@ class AVISOLOCALData: public NCFuncs
return times[i];
}
time_t Timestep() const { return isOk() ? (times[1] - times[0]) : 0; }
time_t Timestep() const { return isOk() ? (times[1] - times[0]).Seconds() : 0; }
explicit operator bool() const { return times.size() > 0; }
bool CheckVar(const MString& vname) const
VarPresence CheckVar(const MString& vname) const
{
NCFileA nc;
nc.Reset(datapath + "/uv-" + times[0].ToString() + ".nc");

9
sources/BINFILE.h

@ -38,11 +38,16 @@ class BINFILEData
return times[i];
}
time_t Timestep() const { return isOk() ? (times[1] - times[0]) : 0; }
time_t Timestep() const { return isOk() ? (times[1] - times[0]).Seconds() : 0; }
explicit operator bool() const { return times.size() > 0; }
bool CheckVar(const MString& vname) const { return vname == "u" || vname == "v" || vname == "U" || vname == "U2"; }
VarPresence CheckVar(const MString& vname) const
{
if(vname == "u" || vname == "v") return VarPresence::INTERNAL;
if(vname == "U" || vname == "U2") return VarPresence::DERIVED;
return VarPresence::NONE;
}
bool Read(const MString& vname, std::map<MString, Data>& cache, size_t i) const;
};

222
sources/COPERNICUS.cpp

@ -0,0 +1,222 @@
#define MICHLIB_NOSOURCE
#include "COPERNICUS.h"
#include "mirrorfuncs.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
using michlib::GPL;
RetVal<std::vector<struct FileInfo>> COPERNICUSData::ReadRemoteFileList(const MString& url) const
{
const static MString pref = "COPERNICUSData::ReadRemoteFileList";
LIBXML_TEST_VERSION
std::vector<struct FileInfo> out;
MString bucket, prefix;
// Split url on prefix and bucket
{
size_t pos = url.Len();
size_t count = 0;
for(size_t i = 0; i < url.Len(); i++)
{
if(url[i] == '/') count++;
if(count == 4)
{
pos = i;
break;
}
}
if(pos == url.Len()) return {pref, "Can't parse url: " + url};
bucket = url.SubStr(1, pos);
prefix = url.SubStr(pos + 2, url.Len() - pos - 1);
}
MString cont;
bool next = true;
CURLRAII chandle;
while(next)
{
MString url = bucket + "?list-type=2&prefix=" + prefix;
if(cont.Exist()) url += "&continuation-token=" + cont;
cont = "";
auto [data, res] = GetUrl(chandle, url);
if(res != CURLE_OK) return {pref, MString("Can't download ") + url + ": " + chandle.Err()};
xmlDocPtr doc = xmlReadMemory(data.Buf(), data.Len(), "data.xml", nullptr, 0);
if(doc == nullptr) return {pref, MString("Can't download ") + url + ": XML parse error"};
auto cur = xmlDocGetRootElement(doc);
if(cur == nullptr)
{
xmlFreeDoc(doc);
return {pref, MString("Can't download ") + url + ": empty XML"};
}
if(xmlStrEqual(cur->name, (const xmlChar*)"ListBucketResult") == 0)
{
xmlFreeDoc(doc);
return {pref, MString("Can't download ") + url + ": unknown XML"};
}
for(const auto* n = cur->children; n; n = n->next)
{
if(xmlStrEqual(n->name, (const xmlChar*)"NextContinuationToken") == 1)
{
auto* content = xmlNodeGetContent(n);
cont = (char*)content;
xmlFree(content);
}
if(xmlStrEqual(n->name, (const xmlChar*)"Contents") == 1)
{
MString fname;
MDateTime mtime;
size_t size = 0;
for(const auto* c = n->children; c; c = c->next)
{
if(xmlStrEqual(c->name, (const xmlChar*)"Key") == 1)
{
auto* content = xmlNodeGetContent(c);
fname = (char*)content;
xmlFree(content);
}
if(xmlStrEqual(c->name, (const xmlChar*)"LastModified") == 1)
{
auto* content = xmlNodeGetContent(c);
mtime.FromString((char*)content);
xmlFree(content);
}
if(xmlStrEqual(c->name, (const xmlChar*)"Size") == 1)
{
auto* content = xmlNodeGetContent(c);
size = MString((char*)content).ToInteger<size_t>();
xmlFree(content);
}
}
out.emplace_back(bucket + "/" + fname, fname.SubStr(prefix.Len() + 2, fname.Len() - prefix.Len() - 1), mtime, size);
}
}
xmlFreeDoc(doc);
next = cont.Exist();
}
std::sort(out.begin(), out.end(), [](const struct FileInfo& a, const struct FileInfo& b) { return a.name < b.name; });
return out;
}
Error COPERNICUSData::Mirror(const CLArgs& args) const
{
const static MString pref = "COPERNICUSData::Mirror";
GPL.UsePrefix("COPERNICUS");
// Local directory
MString mirrorroot = GPL.ParameterSValue("MirrorTo", "");
if(!mirrorroot.Exist()) return {pref, "Local mirror directory not specified"};
if(!args.contains("product")) return {pref, "Copernicus product not specified"};
MString prod = args.at("product");
CopernicusCatalog cat;
std::vector<MString> dsets;
if(args.contains("dataset"))
dsets.push_back(args.at("dataset"));
else
{
auto dlist = cat.DatasetList(prod);
if(!dlist) return dlist.Add(pref, "Can't get list of datasets");
dsets = dlist.Value();
}
michlib::RegExpSimple filter((args.contains("filter") ? args.at("filter") : ".*").Buf());
if(filter.Compile() != 0) return Error(pref, MString("Can't compile regular expression ") + filter.RegStr());
CURLRAII chandle;
for(const auto& dset: dsets)
{
michlib::message("Mirroring " + dset);
auto url = cat.DatasetNativeURL(prod, dset);
if(!url) return {pref, "Can't find data for dataset " + dset + " from product " + prod};
MString locroot = mirrorroot + "/" + prod + "/" + dset;
auto lfilesret = ReadLocalFileList(locroot);
if(!lfilesret) return lfilesret.Add(pref, "Can't get local file list");
const auto& lfiles = lfilesret.Value();
auto rfilesret = ReadRemoteFileList(url.Value());
if(!rfilesret) return rfilesret.Add(pref, "Can't get remote file list");
const auto& rfiles = rfilesret.Value();
std::vector<size_t> down, rem;
std::vector<std::pair<size_t, size_t>> upd;
{
size_t rpos = 0, lpos = 0;
while(rpos != rfiles.size() || lpos != lfiles.size())
{
if(rpos == rfiles.size())
while(lpos != lfiles.size())
{
if(filter.Match(lfiles[lpos].name.Buf())) rem.push_back(lpos);
lpos++;
}
if(lpos == lfiles.size())
while(rpos != rfiles.size())
{
if(filter.Match(rfiles[rpos].name.Buf())) down.push_back(rpos);
rpos++;
}
if(rpos == rfiles.size() || lpos == lfiles.size()) continue;
if(rfiles[rpos].name < lfiles[lpos].name)
{
if(filter.Match(rfiles[rpos].name.Buf())) down.push_back(rpos);
rpos++;
}
else if(lfiles[lpos].name < rfiles[rpos].name)
{
if(filter.Match(lfiles[lpos].name.Buf())) rem.push_back(lpos);
lpos++;
}
else
{
auto delta = rfiles[rpos].mtime.Epoch() - lfiles[lpos].mtime.Epoch();
if(delta < 0) delta = -delta;
if((delta > 0 || rfiles[rpos].size != lfiles[lpos].size) && filter.Match(lfiles[lpos].name.Buf())) upd.emplace_back(rpos, lpos);
lpos++;
rpos++;
}
}
}
michlib::message(MString("New files: ") + down.size());
michlib::message(MString("Obsolete files: ") + rem.size());
michlib::message(MString("Modified files: ") + upd.size());
for(size_t i = 0; i < down.size(); i++)
{
size_t ri = down[i];
auto err = DownloadFile(chandle, rfiles[ri], locroot);
if(!err) return err.Add(pref, "Can't download file");
}
for(size_t i = 0; i < rem.size(); i++)
{
size_t li = rem[i];
auto err = RemoveFile(lfiles[li]);
if(!err) return err.Add(pref, "Can't remove file");
}
for(size_t i = 0; i < upd.size(); i++)
{
size_t ri = upd[i].first;
size_t li = upd[i].second;
auto err = UpdateFile(chandle, rfiles[ri], lfiles[li], locroot);
if(!err) return err.Add(pref, "Can't update file");
}
}
return Error();
}

21
sources/COPERNICUS.h

@ -0,0 +1,21 @@
#pragma once
#include "ParseArgs.h"
#include "copcat.h"
#include "mdatetime.h"
using michlib::MDateTime;
using michlib::MString;
class COPERNICUSData
{
// Get remote file list from url
RetVal<std::vector<struct FileInfo>> ReadRemoteFileList(const MString& url) const;
public:
static constexpr const char* name = "COPERNICUS";
COPERNICUSData() = default;
// Main mirror function
Error Mirror(const CLArgs& args) const;
};

86
sources/GRIDVEL.cpp

@ -0,0 +1,86 @@
#define MICHLIB_NOSOURCE
#include "GRIDVEL.h"
MString GRIDVELData::Info() const
{
if(!isOk()) return "";
// clang-format off
return MString() +
" Region: (" + u.Xb() + " : " + u.Xe() + ") x (" + u.Yb() + " : " + u.Ye() + ")\n" +
" Grid: " + u.Nx() + "x" + u.Ny() + "\n" +
" Supported variables: u, v, U2";
// clang-format on
}
MString GRIDVELData::Open(const CLArgs& args)
{
MString uname = args.contains("u") ? args.at("u") : "";
MString vname = args.contains("v") ? args.at("v") : "";
if(!uname.Exist()) return "File with u component not specified";
if(!vname.Exist()) return "File with v component not specified";
unit = args.contains("unit") ? args.at("unit") : "cm/s";
fill = args.contains("fill") ? args.at("fill").ToReal() : 1e10;
u.Open(uname);
if(!u.Opened()) return "Can't open file: " + uname;
v.Open(vname);
if(!v.Opened()) return "Can't open file: " + vname;
if(u != v) return "Files " + uname + " and " + vname + " have different grids";
return "";
}
bool GRIDVELData::Read(const MString& vname, std::map<MString, GRIDVELData::Data>& cache, size_t i) const
{
if(cache.contains(vname)) return true;
if(!isOk()) return false;
// Only rectangular grids are supported
real xs = (u.Xe() - u.Xb()) / (u.Nx() - 1);
real ys = (u.Ye() - u.Yb()) / (u.Ny() - 1);
Data out(u.Nx(), u.Ny(), u.Xb(), u.Yb(), xs, ys, MString(unit));
// U and U2 from u and v
if(vname == "U" || vname == "U2")
{
bool square = vname == "U2";
if(!(Read("u", cache, i) && Read("v", cache, i))) return false;
cache[vname] = cache.at("u").CopyGrid();
auto& U = cache.at(vname);
const auto& udata = cache.at("u");
const auto& vdata = cache.at("v");
if(udata.Unit().Exist()) U.SetUnit(square ? ("(" + udata.Unit() + ")2") : udata.Unit());
for(size_t ind = 0; ind < U.N(); ind++)
{
if(udata.IsFill(ind) || vdata.IsFill(ind))
U.V(ind) = U.Fillval();
else
U.V(ind) = square ? (udata(ind) * udata(ind) + vdata(ind) * vdata(ind)) : michlib::Hypot(udata(ind), vdata(ind));
}
return true;
}
if(vname == "u" || vname == "v")
{
bool isu = vname == "u";
for(IType ix = 0; ix < u.Nx(); ix++)
for(IType iy = 0; iy < u.Ny(); iy++)
{
if(isu ? (u(ix, iy) == NAN || u(ix, iy) >= 1e10) : (v(ix, iy) == NAN || v(ix, iy) >= 1e10))
out.V(ix, iy) = out.Fillval();
else
out.V(ix, iy) = isu ? u(ix, iy) : v(ix, iy);
}
if(out)
{
cache[vname] = std::move(out);
return true;
}
}
return false;
}

50
sources/GRIDVEL.h

@ -0,0 +1,50 @@
#pragma once
#include "DataAdapters/gridfile.h"
#include "mdatetime.h"
#include "simple2ddata.h"
using michlib::MDateTime;
class GRIDVELData
{
michlib::GridFile u,v;
MString unit;
real fill;
using IType=decltype(u.Nx());
public:
static constexpr const char* name = "GRIDVEL";
static constexpr const char* disabledactions = "genintfile";
using Data = Simple2DData;
GRIDVELData() = default;
MString Info() const;
// TODO: RetVal
MString Open(const CLArgs& args);
bool isOk() const { return u.Opened() &&v.Opened(); }
size_t NTimes() const { return 1; }
MDateTime Time(size_t i) const
{
return MDateTime();
}
//time_t Timestep() const { return isOk() ? (times[1] - times[0]) : 0; }
explicit operator bool() const { return u.Opened() &&v.Opened(); }
VarPresence CheckVar(const MString& vname) const
{
if(vname == "u" || vname == "v") return VarPresence::INTERNAL;
if(vname == "U" || vname == "U2") return VarPresence::DERIVED;
return VarPresence::NONE;
}
bool Read(const MString& vname, std::map<MString, Data>& cache, size_t i) const;
};

6
sources/HYCOM.h

@ -8,6 +8,7 @@ class HYCOMData: public LayeredData
TYPE_UNKNOWN,
TYPE_REANALYSIS,
TYPE_HINDCAST,
TYPE_NRT,
TYPE_FORECAST
};
@ -24,7 +25,8 @@ class HYCOMData: public LayeredData
{
case(TYPE_REANALYSIS): return "GOFS 3.1: 41-layer HYCOM + NCODA Global 1/12 Reanalysis";
case(TYPE_HINDCAST): return "GOFS 3.1: 41-layer HYCOM + NCODA Global 1/12 Analysis Hindcast";
case(TYPE_FORECAST): return "GOFS 3.1: 41-layer HYCOM + NCODA Global 1/12 Analysis Forecast";
case(TYPE_NRT): return "ESPC-D-V02: Global 1/12 Analysis Archive";
case(TYPE_FORECAST): return "ESPC-D-V02: Global 1/12 Forecast";
default: return "No title";
}
}
@ -37,6 +39,8 @@ class HYCOMData: public LayeredData
GPL.UsePrefix("HYCOM");
if(dataset == "Forecast")
type = TYPE_FORECAST;
else if(dataset == "NRT")
type = TYPE_NRT;
else if(dataset == "Hindcast")
type = TYPE_HINDCAST;
else if(dataset == "Reanalysis")

7
sources/MODISBINLOCAL.h

@ -54,11 +54,12 @@ class MODISBINLOCALData
std::pair<const BaseParameters*, MString> Parameters(michlib_internal::ParameterListEx& pars, const CLArgs& args, const struct Region& reg) const;
bool CheckVar(const MString& vname) const
VarPresence CheckVar(const MString& vname) const
{
if(dataset.empty()) return false;
if(dataset.empty()) return VarPresence::NONE;
bool ischl = dataset.front() == "A_CHL" || dataset.front() == "T_CHL";
return (vname == "chl" && ischl) || (vname == "temp" && !ischl);
if((vname == "chl" && ischl) || (vname == "temp" && !ischl)) return VarPresence::INTERNAL;
return VarPresence::NONE;
}
bool isOk() const { return times.size() > 0; }

56
sources/NEMO.h

@ -1,14 +1,26 @@
#pragma once
#include "layereddata.h"
#include "layereddataz.h"
class NEMOData: public LayeredData
class NEMOData: public LayeredDataZ
{
enum Type
{
TYPE_UNKNOWN,
TYPE_DT,
TYPE_DT1,
TYPE_NRT,
TYPE_NRT6
TYPE_NRT6,
TYPE_BALTICDT,
TYPE_BALTICNRT,
TYPE_BALTICNRT1,
TYPE_BLKSEADT,
TYPE_BLKSEANRT,
TYPE_MEDSEADT,
TYPE_MEDSEANRT,
TYPE_BISCDT,
TYPE_BISCNRT,
TYPE_ENWSDT,
TYPE_ENWSNRT
};
Type type = TYPE_UNKNOWN;
@ -23,8 +35,20 @@ class NEMOData: public LayeredData
switch(type)
{
case(TYPE_DT): return "NEMO Delayed time, daily mean (DT)";
case(TYPE_DT1): return "NEMO Delayed time, daily mean, part 2 (DT1)";
case(TYPE_NRT): return "NEMO Near-real time, daily mean (NRT)";
case(TYPE_NRT6): return "NEMO Near-real time, 6h resolution (NRT6)";
case(TYPE_BALTICDT): return "NEMO Delayed time, Baltic region, daily mean (BALTICDT)";
case(TYPE_BALTICNRT): return "NEMO Near-real time, Baltic region, daily mean (BALTICNRT)";
case(TYPE_BALTICNRT1): return "NEMO Near-real time, Baltic region, 1h resolution (BALTICNRT1)";
case(TYPE_BLKSEADT): return "NEMO Delayed time, Black Sea region, daily mean (BLKSEADT)";
case(TYPE_BLKSEANRT): return "NEMO Near-real time time, Black Sea region, daily mean (BLKSEANRT)";
case(TYPE_MEDSEADT): return "NEMO Delayed time, Mediterranean Sea region, daily mean (MEDSEADT)";
case(TYPE_MEDSEANRT): return "NEMO Near-real time, Mediterranean Sea region, daily mean (MEDSEANRT)";
case(TYPE_BISCDT): return "NEMO Delayed time, Atlantic-Iberian Biscay Irish region, daily mean (BISCDT)";
case(TYPE_BISCNRT): return "NEMO Near-real time, Atlantic-Iberian Biscay Irish region, daily mean (BISCNRT)";
case(TYPE_ENWSDT): return "NEMO Delayed time, Atlantic - European North West Shelf region, daily mean (ENWSDT)";
case(TYPE_ENWSNRT): return "NEMO Near-real time, Atlantic - European North West Shelf region, daily mean (ENWSNRT)";
default: return "No title";
}
}
@ -37,14 +61,38 @@ class NEMOData: public LayeredData
GPL.UsePrefix("NEMO");
if(dataset == "DT")
type = TYPE_DT;
else if(dataset == "DT1")
type = TYPE_DT1;
else if(dataset == "NRT")
type = TYPE_NRT;
else if(dataset == "NRT6")
type = TYPE_NRT6;
else if(dataset == "BALTICDT")
type = TYPE_BALTICDT;
else if(dataset == "BALTICNRT")
type = TYPE_BALTICNRT;
else if(dataset == "BALTICNRT1")
type = TYPE_BALTICNRT1;
else if(dataset == "BLKSEADT")
type = TYPE_BLKSEADT;
else if(dataset == "BLKSEANRT")
type = TYPE_BLKSEANRT;
else if(dataset == "MEDSEADT")
type = TYPE_MEDSEADT;
else if(dataset == "MEDSEANRT")
type = TYPE_MEDSEANRT;
else if(dataset == "BISCDT")
type = TYPE_BISCDT;
else if(dataset == "BISCNRT")
type = TYPE_BISCNRT;
else if(dataset == "ENWSDT")
type = TYPE_ENWSDT;
else if(dataset == "ENWSNRT")
type = TYPE_ENWSNRT;
else
return "Unknown dataset: " + dataset;
SetTitle(DataTitle());
return LayeredData::Open(dataset);
return LayeredDataZ::Open(dataset);
}
};

6
sources/NEMOBIO.h

@ -1,7 +1,7 @@
#pragma once
#include "layereddata.h"
#include "layereddataz.h"
class NEMOBIOData: public LayeredData
class NEMOBIOData: public LayeredDataZ
{
enum Type
{
@ -43,6 +43,6 @@ class NEMOBIOData: public LayeredData
return "Unknown dataset: " + dataset;
SetTitle(DataTitle());
return LayeredData::Open(dataset);
return LayeredDataZ::Open(dataset);
}
};

129
sources/TSCDATA.cpp

@ -0,0 +1,129 @@
#define MICHLIB_NOSOURCE
#include "TSCDATA.h"
MString TSCDATAData::Open(const CLArgs& args)
{
if(!args.contains("dataset")) return "path to data not specified";
MString dataset = args.at("dataset");
michlib::NCFileA newnc;
std::vector<MString> newvnames, newlnames;
MString newhistory;
newnc.Reset(dataset);
if(!newnc) return "Can't open file " + dataset;
auto head = newnc.Header();
if(head.Dimensions().size() != 2) return "Unsupported number of dimensions";
if((head.Dimensions()[0].Name() != "longitude" || head.Dimensions()[1].Name() != "latitude") &&
(head.Dimensions()[1].Name() != "longitude" || head.Dimensions()[0].Name() != "latitude"))
return "Unsupported dimensions names";
if(head.Dimensions()[0].Name() == "longitude")
{
nx = head.Dimensions()[0].Len();
ny = head.Dimensions()[1].Len();
}
else
{
ny = head.Dimensions()[0].Len();
nx = head.Dimensions()[1].Len();
}
{
bool lonfound = false, latfound = false;
for(const auto& v: head.Variables())
if(v.Dimensions().size() == 1 && v.Type().Id() == NC_FLOAT)
{
lonfound = lonfound || (v.Dimensions()[0].Name() == "longitude" && v.Name() == "longitude");
latfound = latfound || (v.Dimensions()[0].Name() == "latitude" && v.Name() == "latitude");
}
if(!lonfound) return "Longitude not found";
if(!latfound) return "Latitude not found";
}
for(const auto& v: head.Variables())
{
if(v.Dimensions().size() != 2) continue;
if(v.Type().Id() != NC_FLOAT) continue;
if((v.Dimensions()[0].Name() != "longitude" || v.Dimensions()[1].Name() != "latitude") && (v.Dimensions()[1].Name() != "longitude" || v.Dimensions()[0].Name() != "latitude"))
continue;
newvnames.push_back(v.Name());
auto lname = newnc.A<MString>(v.Name(), "long_name");
newlnames.push_back(lname ? lname.Get() : "");
}
if(newvnames.size() == 0) return "No variables found";
{
auto his = newnc.A<MString>("history");
if(his) newhistory = his;
}
history = std::move(newhistory);
vnames = std::move(newvnames);
lnames = std::move(newlnames);
nc = std::move(newnc);
return "";
}
MString TSCDATAData::Info() const
{
if(!nc) return "";
MString out;
out += MString("Dimensions: ") + nx + " X " + ny + "\n";
out += MString("Variables: ");
for(size_t i = 0; i < vnames.size(); i++) out += ((i == 0) ? "" : ", ") + vnames[i];
out += "\n";
auto his = nc.A<MString>("history");
if(his) out += "Creator: " + his.Get() + "\n";
return out;
}
std::vector<TSCDATAData::DataType> TSCDATAData::ReadLons() const
{
std::vector<DataType> out;
auto lons = nc.V<DataType>("longitude");
if(lons)
{
out.resize(lons.DimLen(0));
for(size_t i = 0; i < out.size(); i++) out[i] = lons(i);
}
return out;
}
std::vector<TSCDATAData::DataType> TSCDATAData::ReadLats() const
{
std::vector<DataType> out;
auto lats = nc.V<DataType>("latitude");
if(lats)
{
out.resize(lats.DimLen(0));
for(size_t i = 0; i < out.size(); i++) out[i] = lats(i);
}
return out;
}
std::vector<TSCDATAData::DataType> TSCDATAData::ReadVar(const MString& name) const
{
std::vector<DataType> out;
bool havevar = false;
for(size_t i = 0; i < vnames.size(); i++) havevar = havevar || (vnames[i] == name);
if(!havevar) return out;
auto var = nc.V<DataType>(name, "latitude", "longitude");
if(var)
{
out.resize(nx * ny);
for(size_t iy = 0; iy < ny; iy++)
for(size_t ix = 0; ix < nx; ix++) out[iy * nx + ix] = var(iy, ix);
}
return out;
}
TSCDATAData::DataType TSCDATAData::FillVal(const MString& name) const
{
auto fill = nc.A<DataType>(name, "_FillValue");
return fill ? fill.Get() : 0.0;
}

35
sources/TSCDATA.h

@ -0,0 +1,35 @@
#pragma once
#include "DataAdapters/ncfilealt.h"
#include "ParseArgs.h"
#include "simple2ddata.h"
class TSCDATAData
{
michlib::NCFileA nc;
std::vector<MString> vnames, lnames;
size_t nx, ny;
MString history;
using DataType = float;
public:
static constexpr const char* name = "TSCDATA";
MString Info() const;
MString Open(const CLArgs& args);
std::vector<DataType> ReadLons() const;
std::vector<DataType> ReadLats() const;
std::vector<DataType> ReadVar(const MString& name) const;
const auto& VarNames() const { return vnames; }
const auto& LongNames() const { return lnames; }
size_t Nx() const { return nx; }
size_t Ny() const { return ny; }
size_t NVar() const { return vnames.size(); }
const auto& History() const { return history; }
DataType FillVal(const MString& name) const;
};

526
sources/VYLET.cpp

@ -0,0 +1,526 @@
#define MICHLIB_NOSOURCE
#include "VYLET.h"
MString VYLETData::Info() const
{
if(!vylet) return "";
MString out;
michlib::CompiledParser xy2lon, xy2lat;
real x, y;
michlib::ParserVars pv;
pv["x"] = &x;
pv["y"] = &y;
vylet->UsePrefix("Datafile_Info");
MString xy2lonstr = vylet->ParameterSValue("xy2lon", "%y");
MString xy2latstr = vylet->ParameterSValue("xy2lat", "%x");
ArifmeticCompiler(xy2lonstr, xy2lon, &pv);
ArifmeticCompiler(xy2latstr, xy2lat, &pv);
real lonb, latb, lone, late;
vylet->UsePrefix("");
x = vylet->ParameterRValue("x0", 0.0);
y = vylet->ParameterRValue("y0", 0.0);
xy2lon.Run(lonb);
xy2lat.Run(latb);
x = vylet->ParameterRValue("x1", 0.0);
y = vylet->ParameterRValue("y1", 0.0);
xy2lon.Run(lone);
xy2lat.Run(late);
out += "Start time: " + start.ToString() + "\n";
out += "End time: " + end.ToString() + " " + (invtime ? "(backward integration)" : "(forward integration)") + "\n";
out += "Region: (" + MString(lonb) + " : " + lone + ") x (" + latb + " : " + late + ")\n";
out += "Grid:" + MString(" ") + lons.size() + "x" + lats.size() + "\n";
out += HasCoast() ? "Coast: checked\n" : "Coast: not checked\n";
out += LengthFast() ? "Trajectory length: fast calculation\n" : "Trajectory length: exact calculation\n";
out += "Borders: ";
bool needcomma = false;
if(Left() >= 0.0)
{
real lon;
if(needcomma) out += ", ";
out += "left=";
x = Left();
xy2lon.Run(lon);
out += lon;
needcomma = true;
}
if(Right() <= maxx)
{
real lon;
if(needcomma) out += ", ";
out += "right=";
x = Right();
xy2lon.Run(lon);
out += lon;
needcomma = true;
}
if(Down() >= 0.0)
{
real lat;
if(needcomma) out += ", ";
out += "bottom=";
y = Down();
xy2lat.Run(lat);
out += lat;
needcomma = true;
}
if(Up() <= maxy)
{
real lat;
if(needcomma) out += ", ";
out += "bottom=";
y = Up();
xy2lat.Run(lat);
out += lat;
needcomma = true;
}
if(!needcomma) out += "no";
out += "\n";
MString vars = "vylD";
if(HasLyap()) vars += ", vylL";
if(HasTime()) vars += ", vylT";
vars += ", vylNx, vylNy, vylRx, vylRy, vylEx, vylEy, vylPhip, vylPhim, vylPhit";
if(HasLyap()) vars += ", vyldS";
vars += ", vylAngle, vylLen";
if(HasTime()) vars += ", vylTmask";
out += "Supported variables: " + vars + "\n";
return out;
}
MString VYLETData::Open(const CLArgs& args)
{
if(!args.contains("dataset")) return "path to data not specified";
MString dataset = args.at("dataset");
decltype(vylet) nvylet;
michlib::RegExpSimple havex("%x"), havey("%y");
nvylet.reset(new michlib::BFileR);
if(nvylet->Open(dataset) != ERR_NOERR) return "Can't open file " + dataset;
nvylet->UsePrefix("ProgramInfo");
if(nvylet->ParameterSValue("Task", "") != "AdvInt:Vylet") return "File " + dataset + " is not vylet file";
nvylet->UsePrefix("");
if(nvylet->ParameterSValue("nettype", "") != "SQUARE") return "File " + dataset + " have unsupported net type";
MString method = nvylet->ParameterSValue("Method", "");
if(!(method == "Bicubic" || method == "BicubicL" || method == "BicubicI" || method == "BicubicIL")) return "File " + dataset + " have unsupported integration method";
invtime = (method == "BicubicI" || method == "BicubicIL");
nvylet->UsePrefix("Datafile_Info");
MString xy2lonstr = nvylet->ParameterSValue("xy2lon", "%y");
MString xy2latstr = nvylet->ParameterSValue("xy2lat", "%x");
if(havey.Match(xy2lonstr.Buf()) || havex.Match(xy2latstr.Buf())) return "File " + dataset + " have unsupported grid";
auto res = ref.FromString(nvylet->ParameterSValue(invtime ? "EndDate" : "BeginDate", ""));
if(!res) return "Can't read reference time";
nvylet->UsePrefix("");
start = R2Time(nvylet->ParameterRValue("tbeg", 0.0));
end = R2Time(nvylet->ParameterRValue("tmax", 0.0));
nvylet->UsePrefix("Datafile");
auto dx = nvylet->ParameterRValue("dx", 0.0);
auto dy = nvylet->ParameterRValue("dy", 0.0);
auto nx = nvylet->ParameterUValue("nx", 0);
auto ny = nvylet->ParameterUValue("ny", 0);
maxx = dx * (nx - 1);
maxy = dy * (ny - 1);
michlib::CompiledParser xy2lon, xy2lat;
real x, y;
michlib::ParserVars pv;
pv["x"] = &x;
pv["y"] = &y;
ArifmeticCompiler(xy2lonstr, xy2lon, &pv);
ArifmeticCompiler(xy2latstr, xy2lat, &pv);
nvylet->UsePrefix("");
auto nlon = nvylet->ParameterUValue("Nx", 0) + 1;
auto nlat = nvylet->ParameterUValue("Ny", 0) + 1;
lons.resize(nlon);
lats.resize(nlat);
elon.resize(nlon * nlat);
elat.resize(nlon * nlat);
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++)
{
x = (*nvylet)[2][iy * lons.size() + ix];
y = (*nvylet)[3][iy * lons.size() + ix];
xy2lon.Run(elon[iy * lons.size() + ix]);
xy2lat.Run(elat[iy * lons.size() + ix]);
}
for(size_t ix = 0; ix < nlon; ix++)
{
x = (*nvylet)[0][ix];
y = (*nvylet)[1][ix];
xy2lon.Run(lons[ix]);
}
for(size_t iy = 0; iy < nlat; iy++)
{
x = (*nvylet)[0][iy * nlon];
y = (*nvylet)[1][iy * nlon];
xy2lat.Run(lats[iy]);
}
vylet = std::move(nvylet);
return "";
}
bool VYLETData::Read(const MString& vname, std::map<MString, VYLETData::Data>& cache, size_t tind) const
{
if(tind != 0) return false;
if(cache.contains(vname)) return true;
Data out;
if(vname == "vylD") out = ReadD();
if(vname == "vylL") out = ReadL();
if(vname == "vylT") out = ReadT();
if(vname == "vylNx") out = ReadNx();
if(vname == "vylNy") out = ReadNy();
if(vname == "vylRx") out = ReadRx();
if(vname == "vylRy") out = ReadRy();
if(vname == "vylEx") out = ReadEx();
if(vname == "vylEy") out = ReadEy();
if(vname == "vylPhip") out = ReadPhip();
if(vname == "vylPhim") out = ReadPhim();
if(vname == "vylPhit") out = ReadPhit();
if(vname == "vyldS") out = ReaddS();
if(vname == "vylAngle") out = ReadAngle();
if(vname == "vylLen") out = ReadLen();
if(vname == "vylTmask") out = ReadTmask();
if(!out) return false;
cache[vname] = std::move(out);
return true;
}
VYLETData::Data VYLETData::ReadD() const
{
Data out(lons, lats);
if(!vylet) return Data();
const real xl = Left();
const real xr = Right();
const real yd = Down();
const real yu = Up();
real x, y;
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++)
{
out(ix, iy) = 0.0;
x = (*vylet)[2][iy * lons.size() + ix];
y = (*vylet)[3][iy * lons.size() + ix];
if(yd > 0.0 && y < yd) out(ix, iy) = -1.0;
if(xl > 0.0 && x < xl) out(ix, iy) = -2.0;
if(yu < maxy && y > yu) out(ix, iy) = -3.0;
if(xr < maxx && x > xr) out(ix, iy) = -4.0;
if(out(ix, iy) >= 0.0)
out(ix, iy) = 6371.0 * michlib::GCD(M_PI * lons[ix] / 180.0, M_PI * lats[iy] / 180.0, M_PI * elon[iy * lons.size() + ix] / 180.0, M_PI * elat[iy * lons.size() + ix] / 180.0);
}
out.SetUnit("km");
out.SetLongName("Distance between start and end points");
out.SetComment("Special values: -1.0 - trajectory leave region via south border, -2.0 - trajectory leave region via west border, -3.0 - trajectory leave region via north border, "
"-4.0 - trajectory leave region via east border");
return out;
}
VYLETData::Data VYLETData::ReadL() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto lcol = Name2ColNum("Log(G.sigma1)");
auto tcol = Name2ColNum("time");
if(lcol == 0 || tcol == 0) return Data();
MDateTime time;
real lambda, days;
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++)
{
time = R2Time((*vylet)[tcol - 1][iy * lons.size() + ix]);
lambda = (*vylet)[lcol - 1][iy * lons.size() + ix];
days = (time - start).D();
out(ix, iy) = lambda / days;
}
out.SetUnit("days-1");
out.SetLongName("Lyapunov exponent");
return out;
}
VYLETData::Data VYLETData::ReadT() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto tcol = Name2ColNum("time");
if(tcol == 0) return Data();
const real xl = Left();
const real xr = Right();
const real yd = Down();
const real yu = Up();
vylet->UsePrefix("");
const real acc = vylet->ParameterRValue("accuracy", 1.0);
const real tstep = 2.0 * M_PI * 1000.0 / acc;
const real maxdays = (end - start).D();
MDateTime time;
real days;
real x, y;
bool inside, maxtime;
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++)
{
x = (*vylet)[2][iy * lons.size() + ix];
y = (*vylet)[3][iy * lons.size() + ix];
time = R2Time((*vylet)[tcol - 1][iy * lons.size() + ix]);
days = (time - start).D();
if(days <= tstep * 1.5) days = 0.0;
inside = x > xl && x < xr && y > yd && y < yu;
maxtime = days >= maxdays - tstep * 0.5;
if(maxtime) days = maxdays;
if(inside && !maxtime) days = -days;
out(ix, iy) = days;
}
out.SetUnit("days");
out.SetLongName("Time to reach border or coast");
out.SetComment("Special values: 0.0 - initial point on the land, " + MString(maxdays) + " - trajectory don't reach border, negative values - trajectory beached on the coast");
return out;
}
VYLETData::Data VYLETData::ReadNx() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto ncol = Name2ColNum("nx=0");
if(ncol == 0) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = (*vylet)[ncol - 1][iy * lons.size() + ix];
out.SetLongName("Number of moments u=0");
return out;
}
VYLETData::Data VYLETData::ReadNy() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto ncol = Name2ColNum("ny=0");
if(ncol == 0) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = (*vylet)[ncol - 1][iy * lons.size() + ix];
out.SetLongName("Number of moments v=0");
return out;
}
VYLETData::Data VYLETData::ReadRx() const
{
Data out(lons, lats);
if(!vylet) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = elon[iy * lons.size() + ix] - lons[ix];
out.SetLongName("Displacement in zonal direction");
return out;
}
VYLETData::Data VYLETData::ReadRy() const
{
Data out(lons, lats);
if(!vylet) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = elat[iy * lons.size() + ix] - lats[iy];
out.SetLongName("Displacement in meridional direction");
return out;
}
VYLETData::Data VYLETData::ReadEx() const
{
Data out(lons, lats);
if(!vylet) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = elon[iy * lons.size() + ix];
out.SetLongName("Final longitude");
return out;
}
VYLETData::Data VYLETData::ReadEy() const
{
Data out(lons, lats);
if(!vylet) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = elat[iy * lons.size() + ix];
out.SetLongName("Final latitude");
return out;
}
VYLETData::Data VYLETData::ReadPhip() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto ncol = Name2ColNum("nphip");
if(ncol == 0) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = (*vylet)[ncol - 1][iy * lons.size() + ix];
out.SetLongName("Number of counterclockwise rotations");
return out;
}
VYLETData::Data VYLETData::ReadPhim() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto ncol = Name2ColNum("nphim");
if(ncol == 0) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = (*vylet)[ncol - 1][iy * lons.size() + ix];
out.SetLongName("Number of clockwise rotations");
return out;
}
VYLETData::Data VYLETData::ReadPhit() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto pcol = Name2ColNum("nphip");
auto mcol = Name2ColNum("nphim");
if(pcol == 0 || mcol == 0) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = (*vylet)[pcol - 1][iy * lons.size() + ix] - (*vylet)[mcol - 1][iy * lons.size() + ix];
out.SetLongName("Difference between the number of rotations counterclockwise and clockwise");
return out;
}
VYLETData::Data VYLETData::ReaddS() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto l1col = Name2ColNum("Log(G.sigma1)");
auto l2col = Name2ColNum("Log(G.sigma2)");
if(l1col == 0 || l2col == 0) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = (*vylet)[l1col - 1][iy * lons.size() + ix] + (*vylet)[l2col - 1][iy * lons.size() + ix];
out.SetLongName("Logarithm of area change multiplier");
return out;
}
VYLETData::Data VYLETData::ReadAngle() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto ncol = Name2ColNum("angle");
if(ncol == 0) return Data();
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = (*vylet)[ncol - 1][iy * lons.size() + ix] / (2.0 * M_PI);
out.SetLongName("Total rotation angle normalized to 2π");
return out;
}
VYLETData::Data VYLETData::ReadLen() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto ncol = Name2ColNum("length");
if(ncol == 0) return Data();
bool sisangle = !LengthFast();
real mul = sisangle ? (6371.0 * M_PI / (60.0 * 180.0)) : 1.0;
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++) out(ix, iy) = mul * (*vylet)[ncol - 1][iy * lons.size() + ix];
if(sisangle)
{
out.SetUnit("km");
out.SetLongName("Trajectory length");
}
out.SetLongName("Trajectory length (in abstract units)");
return out;
}
VYLETData::Data VYLETData::ReadTmask() const
{
Data out(lons, lats);
if(!vylet) return Data();
auto tcol = Name2ColNum("time");
if(tcol == 0) return Data();
vylet->UsePrefix("");
const real acc = vylet->ParameterRValue("accuracy", 1.0);
const real tstep = 2.0 * M_PI * 1000.0 / acc;
const real maxdays = (end - start).D();
MDateTime time;
real days;
bool maxtime;
for(size_t iy = 0; iy < lats.size(); iy++)
for(size_t ix = 0; ix < lons.size(); ix++)
{
time = R2Time((*vylet)[tcol - 1][iy * lons.size() + ix]);
days = (time - start).D();
maxtime = days >= maxdays - tstep * 0.5;
out(ix, iy) = maxtime ? 1.0 : NAN;
}
out.SetLongName("Flag");
out.SetComment("Values: 1.0 - the trajectory did not reach either the coast or the borders, NaN - overwise");
return out;
}

145
sources/VYLET.h

@ -0,0 +1,145 @@
#pragma once
#include "BFileR.h"
#include "ParseArgs.h"
#include "mdatetime.h"
#include "mregex.h"
#include "simple2ddata.h"
#include <memory>
#include <set>
using michlib::M_PI;
using michlib::MDateTime;
using michlib::real;
using michlib::Round;
class VYLETData
{
std::unique_ptr<michlib::BFileR> vylet;
MDateTime start, end, ref;
bool invtime;
real maxx, maxy;
std::vector<real> lons, lats;
std::vector<real> elon, elat;
MDateTime R2Time(real r) const
{
auto sec = static_cast<time_t>(Round(r * MDateTime::secondsperday));
return ref + (invtime ? -sec : sec);
};
public:
using Data = Rect2DData;
private:
auto Name2ColNum(const MString& name) const
{
auto nc = vylet->Columns();
decltype(nc) i;
for(i = 1; i <= nc; i++)
if(vylet->ColumnName(i) == name) return i;
i = 0;
return i;
}
Data ReadD() const;
Data ReadL() const;
Data ReadT() const;
Data ReadNx() const;
Data ReadNy() const;
Data ReadRx() const;
Data ReadRy() const;
Data ReadEx() const;
Data ReadEy() const;
Data ReadPhip() const;
Data ReadPhim() const;
Data ReadPhit() const;
Data ReaddS() const;
Data ReadAngle() const;
Data ReadLen() const;
Data ReadTmask() const;
public:
static constexpr const char* name = "VYLET";
static constexpr const char* disabledactions = "genintfile uv";
MString DefaultVars() const
{
MString vars = "vylD,vylRx,vylRy,vylEx,vylEy,vylAngle,vylLen";
if(HasLyap()) vars += ",vylL";
if(HasTime()) vars += ",vylT";
return vars;
}
VarPresence CheckVar(const MString& vname) const
{
std::set<MString> vars{"vylD", "vylNx", "vylNy", "vylRx", "vylRy", "vylEx", "vylEy", "vylPhip", "vylPhim", "vylPhit", "vylAngle", "vylLen"};
if(HasLyap()) vars.insert("vylL");
if(HasLyap()) vars.insert("vyldS");
if(HasTime()) vars.insert("vylT");
if(HasTime()) vars.insert("vylTmask");
return vars.contains(vname) ? VarPresence::DERIVED : VarPresence::NONE;
}
bool Read(const MString& vname, std::map<MString, Data>& cache, size_t tind) const;
size_t NTimes() const { return vylet ? 1 : 0; }
MDateTime Time(size_t i) const
{
if(i == 0 && vylet) return start;
return MDateTime();
}
bool HasLyap() const
{
if(!vylet) return false;
vylet->UsePrefix("");
MString method = vylet->ParameterSValue("Method", "");
return method == "BicubicL" || method == "BicubicIL";
}
bool HasCoast() const
{
if(!vylet) return false;
vylet->UsePrefix("");
return vylet->ParameterBValue("checkcoast", true);
}
real Left() const
{
vylet->UsePrefix("");
return vylet->ParameterRValue("xl", -1.0);
}
real Right() const
{
vylet->UsePrefix("");
return vylet->ParameterRValue("xr", maxx + 1.0);
}
real Down() const
{
vylet->UsePrefix("");
return vylet->ParameterRValue("yd", -1.0);
}
real Up() const
{
vylet->UsePrefix("");
return vylet->ParameterRValue("yu", maxy + 1.0);
}
bool HasBorders() const { return Left() >= 0.0 || Right() <= maxx || Down() >= 0.0 || Up() <= maxy; }
bool HasTime() const { return HasCoast() || HasBorders(); }
bool LengthFast() const
{
vylet->UsePrefix("");
return !vylet->ParameterBValue("sisangle", false);
}
MString Info() const;
MString Open(const CLArgs& args);
};

13
src/CMakeLists.txt

@ -2,15 +2,26 @@ set(EXENAME odm)
set(ACTIONLISTINC ${CMAKE_CURRENT_BINARY_DIR}/../include/actionlist.h) # Include actions files and define the actions classes list
set(DATALISTINC ${CMAKE_CURRENT_BINARY_DIR}/../include/datalist.h) # Include data sources files and define the data sources classes list
find_package(PkgConfig REQUIRED)
find_library(netcdf netcdf REQUIRED)
find_library(udunits udunits2 REQUIRED)
find_package(OpenMP REQUIRED)
find_package(CURL REQUIRED)
find_package(LibXml2 REQUIRED)
find_package(SQLite3 REQUIRED)
pkg_check_modules(JSONCPP REQUIRED jsoncpp)
pkg_check_modules(BLOSC REQUIRED blosc)
pkg_check_modules(LIBPQ REQUIRED libpq)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
include_directories(${JSONCPP_INCLUDE_DIRS} ${BLOSC_INCLUDE_DIRS} ${LIBPQ_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIRS} ${SQLite3_INCLUDE_DIRS})
file(GLOB srcs CONFIGURE_DEPENDS *.cpp)
add_executable(${EXENAME} ${srcs} ${ACTIONLISTINC} ${SOURCELISTINC})
target_include_directories(${EXENAME} PRIVATE ../michlib/michlib ${CMAKE_CURRENT_BINARY_DIR}/../include)
target_link_libraries(${EXENAME} ${linker_options} ${netcdf} OpenMP::OpenMP_CXX teos)
target_link_libraries(${EXENAME} ${linker_options} ${netcdf} ${udunits} OpenMP::OpenMP_CXX CURL::libcurl ${JSONCPP_LINK_LIBRARIES} ${BLOSC_LINK_LIBRARIES} ${LIBPQ_LINK_LIBRARIES} LibXml2::LibXml2 SQLite::SQLite3 teos)
set_target_properties(${EXENAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
install(TARGETS ${EXENAME})

254
src/cache.cpp

@ -0,0 +1,254 @@
#define MICHLIB_NOSOURCE
#include "cache.h"
SQLiteConnection::DBType SQLiteConnection::db = nullptr;
size_t SQLiteConnection::count = 0;
std::vector<SQLiteConnection::FuncType> SQLiteConnection::destructs = {};
PostgreSQLConnection::DBType PostgreSQLConnection::conn = nullptr;
size_t PostgreSQLConnection::count = 0;
std::vector<PostgreSQLConnection::FuncType> PostgreSQLConnection::destructs = {};
bool SQLiteCache::regdest = false;
bool PostgreSQLCache::regdest = false;
bool FileInfoCache::regdest = false;
void FileInfoCache::GetDirId()
{
if(dirid != 0) return;
const char* params[] = {dir.Buf()};
int plens[] = {int_cast<int>(dir.Len())};
int pfor[] = {0};
PGresultRAII res = PQexecParams(conn, "SELECT id FROM dirs WHERE name=$1::text;", 1, nullptr, params, plens, pfor, 1);
if(PQresultStatus(res) != PGRES_TUPLES_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
return;
}
else if(PQntuples(res) == 0)
{
res = PQexecParams(conn,
"INSERT INTO dirs(name,id) VALUES ($1, (SELECT min(num.numid) FROM (SELECT generate_series(1, (SELECT COALESCE((SELECT max(id) FROM dirs), 1)) + "
"1, 1) AS numid) num LEFT JOIN dirs ON dirs.id=num.numid WHERE id IS NULL)) RETURNING id;",
1, nullptr, params, plens, pfor, 1);
if(PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
}
if(PQntuples(res) == 0) return;
}
if(PQgetlength(res, 0, 0) == sizeof(dirid)) dirid = *pointer_cast<const decltype(dirid)*>(PQgetvalue(res, 0, 0));
michlib::message("Dirid: ", Invert(dirid));
}
FileInfoCache::FileInfoCache(FileInfoCache::CallbackType&& readfunc_, const MString& dir_): readfunc(std::move(readfunc_)), dir(dir_), dirid(0)
{
if(!conn) return;
if(!regdest)
{
// Create table
PGresultRAII res = PQexec(conn, "SET client_min_messages=WARNING;");
res = PQexec(conn, "BEGIN;"
"CREATE TABLE IF NOT EXISTS dirs(name TEXT PRIMARY KEY, id INTEGER UNIQUE NOT NULL CONSTRAINT id_is_positive CHECK(id>0));"
"CREATE TABLE IF NOT EXISTS files(name TEXT NOT NULL, size BIGINT NOT NULL CONSTRAINT size_is_positive CHECK(size>0), modtime TIMESTAMP(0) NOT NULL, "
"dirid INTEGER REFERENCES dirs(id) ON DELETE CASCADE, lastaccess TIMESTAMP(0) NOT NULL, data BYTEA NOT NULL, PRIMARY KEY(name,dirid));"
"COMMIT;");
if(PQresultStatus(res) != PGRES_COMMAND_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
}
res = PQexec(conn, "SET client_min_messages=NOTICE;");
conn.AddDestructor(
[](PostgreSQLConnection::DBType conn)
{
PGresultRAII res = PQexec(conn, "BEGIN;"
"DELETE FROM files WHERE lastaccess+'100 days'::interval<localtimestamp;"
"DELETE FROM dirs WHERE id NOT IN (SELECT dirid FROM files);"
"COMMIT;");
});
regdest = true;
}
GetDirId();
//UpdateCache();
}
Error FileInfoCache::UpdateCache(bool force) const
{
const static MString pref = "FileInfoCache::UpdateCache";
DIRRAII dhandle;
dhandle.reset(opendir(dir.Buf()));
if(!dhandle) return {pref, "Can't open directory " + dir};
int dfd = dirfd(dhandle);
errno = 0;
struct dirent* dent = readdir(dhandle);
if(errno != 0) return {pref, "Can't read directory " + dir};
struct stat st;
do {
if(dent->d_name[0] != '.')
{
int ret = fstatat(dfd, dent->d_name, &st, 0);
if(ret != 0) return {pref, "Can't stat " + dir + "/" + dent->d_name};
if(S_ISREG(st.st_mode)) // Regular file
{
const char* params[] = {dent->d_name, pointer_cast<const char*>(&dirid)};
int plens[] = {int_cast<int>(strlen(dent->d_name)), sizeof(dirid)};
int pfor[] = {0, 1};
bool querysucc = true;
time_t modtime;
size_t size;
PGresultRAII res = PQexecParams(conn, "SELECT size,modtime FROM files WHERE name=$1::text AND dirid=$2::integer;", 2, nullptr, params, plens, pfor, 1);
if(PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
querysucc = false;
}
else if(PQntuples(res) == 0 || PQntuples(res) > 1)
querysucc = false;
if(querysucc)
{
size = *pointer_cast<const decltype(size)*>(PQgetvalue(res, 0, 0));
modtime = raw2epoch(*pointer_cast<const time_t*>(PQgetvalue(res, 0, 1)));
}
else
{
size = int_cast<size_t>(st.st_size);
modtime = st.st_mtim.tv_sec;
}
if(!querysucc || force || size != int_cast<size_t>(st.st_size) || modtime != st.st_mtim.tv_sec)
{
auto ret = GetData(dent->d_name);
// Remove entry
if(!ret && querysucc)
{
PGresultRAII dres = PQexecParams(conn, "DELETE FROM files WHERE name=$1::text AND dirid=$2::integer;", 2, nullptr, params, plens, pfor, 1);
if(PQresultStatus(dres) != PGRES_COMMAND_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(dres)));
michlib::errmessage(PQerrorMessage(conn));
}
}
else // Update or insert
{
auto sizei = Invert(size);
auto modtimei = epoch2raw(modtime);
const char* params[] = {dent->d_name, pointer_cast<const char*>(&sizei), pointer_cast<const char*>(&modtimei), pointer_cast<const char*>(&dirid), ret.value().Buf()};
int plens[] = {int_cast<int>(strlen(dent->d_name)), sizeof(sizei), sizeof(modtimei), sizeof(dirid), int_cast<int>(ret.value().Len())};
int pfor[] = {0, 1, 1, 1, 1};
PGresultRAII res = PQexecParams(conn,
"INSERT INTO files (name,size,modtime,dirid,lastaccess,data) VALUES($1::text, $2::bigint, $3::timestamp, $4::integer, localtimestamp, $5) "
"ON CONFLICT ON CONSTRAINT files_pkey DO UPDATE SET "
"size=EXCLUDED.size, modtime=EXCLUDED.modtime, lastaccess=EXCLUDED.lastaccess, data=EXCLUDED.data;",
5, nullptr, params, plens, pfor, 1);
if(PQresultStatus(res) != PGRES_COMMAND_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
}
} // Insert or update branch
} // Need data update
} // Regular file
} // if(dent->d_name[0] != '.')
dent = readdir(dhandle);
} while(dent != nullptr || errno != 0);
return Error();
}
FileInfoCache::DataType FileInfoCache::GetInfo(const MString& name) const
{
if(!*this) return GetData(name);
bool querysucc = true;
MString data;
time_t modtime;
size_t size;
{
const char* params[] = {name.Buf(), pointer_cast<const char*>(&dirid)};
int plens[] = {int_cast<int>(name.Len()), sizeof(dirid)};
int pfor[] = {0, 1};
PGresultRAII res =
PQexecParams(conn, "UPDATE files SET lastaccess=localtimestamp WHERE name=$1::text AND dirid=$2::integer RETURNING data,size,modtime;", 2, nullptr, params, plens, pfor, 1);
if(PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
querysucc = false;
}
if(PQntuples(res) == 0 || PQntuples(res) > 1)
{
michlib::errmessage("Data for file ", dir + "/" + name, (PQntuples(res) == 0 ? " not found " : " duplicated "), "in cache");
querysucc = false;
}
if(querysucc)
{
data = MString(PQgetvalue(res, 0, 0), PQgetlength(res, 0, 0));
size = *pointer_cast<const decltype(size)*>(PQgetvalue(res, 0, 1));
modtime = raw2epoch(*pointer_cast<const time_t*>(PQgetvalue(res, 0, 2)));
}
}
{
struct stat st;
int ret = stat((dir + "/" + name).Buf(), &st);
if(ret != 0) return DataType();
if(querysucc && st.st_mtim.tv_sec == modtime && size == int_cast<size_t>(st.st_size)) return data;
modtime = st.st_mtim.tv_sec;
size = st.st_size;
}
auto ret = GetData(name);
if(ret)
{
auto sizei = Invert(size);
auto modtimei = epoch2raw(modtime);
const char* params[] = {name.Buf(), pointer_cast<const char*>(&sizei), pointer_cast<const char*>(&modtimei), pointer_cast<const char*>(&dirid), ret.value().Buf()};
int plens[] = {int_cast<int>(name.Len()), sizeof(sizei), sizeof(modtimei), sizeof(dirid), int_cast<int>(ret.value().Len())};
int pfor[] = {0, 1, 1, 1, 1};
PGresultRAII res = PQexecParams(conn,
"INSERT INTO files (name,size,modtime,dirid,lastaccess,data) VALUES($1::text, $2::bigint, $3::timestamp, $4::integer, localtimestamp, $5) "
"ON CONFLICT ON CONSTRAINT files_pkey DO UPDATE SET "
"size=EXCLUDED.size, modtime=EXCLUDED.modtime, lastaccess=EXCLUDED.lastaccess, data=EXCLUDED.data;",
5, nullptr, params, plens, pfor, 1);
if(PQresultStatus(res) != PGRES_COMMAND_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
}
}
return ret;
}

180
src/copcat.cpp

@ -0,0 +1,180 @@
#define MICHLIB_NOSOURCE
#include "copcat.h"
#include "GPL.h"
#include "mirrorfuncs.h"
const MString CopernicusCatalog::caturl = "https://stac.marine.copernicus.eu/metadata/catalog.stac.json";
CopernicusCatalog::CopernicusCatalog()
{
auto oldprefix = michlib::GPL.UsePrefix("COPERNICUS");
// Cache
cache.reset(CreateCache(michlib::GPL.ParameterSValue("Cache", "")));
if(!cache)
{
michlib::errmessage("Can't init cache");
cache.reset(new FakeCache);
}
// Proxy
auto proxyurl = michlib::GPL.ParameterSValue("Proxy", "");
if(proxyurl.Exist()) curl_easy_setopt(chandle, CURLOPT_PROXY, proxyurl.Buf());
michlib::GPL.UsePrefix(oldprefix);
GetCatalog();
}
Error CopernicusCatalog::GetCatalog()
{
if(Valid()) return Error();
auto ret = GetJSON(caturl);
if(ret)
catalog = ret.Value();
else
return ret.Add("CopernicusCatalog::GetCatalog", "can't download catalog");
return Error();
}
RetVal<std::vector<MString>> CopernicusCatalog::ProductList() const
{
static const MString pref = "CopernicusCatalog::ProductList";
if(!Valid()) return {pref, "no catalog"};
const auto& links = catalog["links"];
if(links.type() != Json::arrayValue) return {pref, "no \"links\" section in the catalog"};
std::vector<MString> out;
for(Json::ArrayIndex i = 0; i < links.size(); i++)
{
const auto& rel = links[i]["rel"];
const auto& href = links[i]["href"];
if(rel.type() == Json::stringValue && href.type() == Json::stringValue && rel.asString() == "child")
{
auto str = href.asString();
str.erase(str.find('/'));
out.emplace_back(str.c_str());
}
}
return out;
}
RetVal<MString> CopernicusCatalog::ProductURL(const MString& prod) const
{
static const MString pref = "CopernicusCatalog::ProductURL";
if(!Valid()) return {pref, "no catalog"};
const auto& links = catalog["links"];
if(links.type() != Json::arrayValue) return {pref, "no \"links\" section in the catalog"};
for(Json::ArrayIndex i = 0; i < links.size(); i++)
{
const auto& href = links[i]["href"];
if(href.type() == Json::stringValue && href.asString() == (prod + "/product.stac.json").Buf()) return DirName(caturl) + "/" + MString(href.asString().c_str());
}
return {pref, "unknown product: " + prod};
}
RetVal<std::vector<MString>> CopernicusCatalog::DatasetList(const MString& prod) const
{
static const MString pref = "CopernicusCatalog::DatasetList";
MString url;
{
auto ret = ProductURL(prod);
if(!ret) return ret.Add(pref, "Can't get url for the product " + prod);
url = ret.Value();
}
auto ret = GetJSON(url);
if(!ret) return ret.Add(pref, "Can't download product " + prod);
const auto& links = ret.Value()["links"];
if(links.type() != Json::arrayValue) return {pref, "no \"links\" section in the product " + prod + " description"};
std::vector<MString> out;
for(Json::ArrayIndex i = 0; i < links.size(); i++)
{
const auto& rel = links[i]["rel"];
const auto& href = links[i]["href"];
if(rel.type() == Json::stringValue && href.type() == Json::stringValue && rel.asString() == "item")
{
auto str = href.asString();
str.erase(str.find('/'));
out.emplace_back(str.c_str());
}
}
return out;
}
RetVal<MString> CopernicusCatalog::DatasetURL(const MString& prod, const MString& dataset) const
{
static const MString pref = "CopernicusCatalog::DatasetURL";
MString url;
{
auto ret = ProductURL(prod);
if(!ret) return ret.Add(pref, "Can't get url for the product " + prod);
url = ret.Value();
}
auto ret = GetJSON(url);
if(!ret) return ret.Add(pref, "Can't download product " + prod);
const auto& links = ret.Value()["links"];
if(links.type() != Json::arrayValue) return {pref, "no \"links\" section in the product " + prod + " description"};
for(Json::ArrayIndex i = 0; i < links.size(); i++)
{
const auto& href = links[i]["href"];
if(href.type() == Json::stringValue && href.asString() == (dataset + "/dataset.stac.json").Buf()) return DirName(url) + "/" + MString(href.asString().c_str());
}
return {pref, "unknown dataset: " + dataset};
}
RetVal<MString> CopernicusCatalog::AssetURL(const MString& prod, const MString& dataset, const MString& asset) const
{
static const MString pref = "CopernicusCatalog::AssetURL";
MString url;
{
auto ret = DatasetURL(prod, dataset);
if(!ret) return ret.Add(pref, "Can't get url for the dataset " + dataset);
url = ret.Value();
}
auto ret = GetJSON(url);
if(!ret) return ret.Add(pref, "Can't download dataset " + dataset);
const auto& href = ret.Value()["assets"][asset.Buf()]["href"];
if(!href || href.type() != Json::stringValue) return {pref, "href for the asset " + asset + " not found"};
return MString(href.asString().c_str());
}
RetVal<Json::Value> CopernicusCatalog::GetJSON(const MString& url) const
{
const static MString pref = "CopernicusCatalog::GetJSON";
Json::Reader reader;
Json::Value obj;
MString content;
auto [val, suc] = cache->Get(url);
if(suc)
content = std::move(val);
else
{
michlib::message(url + " not found in cache, downloading");
auto [out, res] = GetUrl(chandle, url);
if(res != CURLE_OK) return Error(pref, MString("can't download JSON: ") + chandle.Err());
cache->Put(url, out, 3600);
content = std::move(out);
}
reader.parse(content.Buf(), content.Buf() + content.Len(), obj, false);
return obj;
}

41
src/curlfuncs.cpp

@ -0,0 +1,41 @@
#define MICHLIB_NOSOURCE
#include "curlfuncs.h"
#include <unistd.h>
using michlib::pointer_cast;
using michlib::uint1;
size_t Write2String(char* ptr, size_t size, size_t n, void* data)
{
MString* out = pointer_cast<MString*>(data);
*out += MString(ptr, size * n);
return size * n;
}
size_t Write2File(char* ptr, size_t size, size_t n, void* data)
{
const int* fd = pointer_cast<const int*>(data);
size_t count = size * n;
const uint1* buf = pointer_cast<const uint1*>(ptr);
while(count != 0)
{
auto wr = write(*fd, buf, count);
if(wr == -1) return 0;
count -= wr;
buf += wr;
}
return size * n;
}
std::pair<MString, CURLcode> GetUrl(const CURLRAII& chandle, const MString& url)
{
MString out;
curl_easy_setopt(chandle, CURLOPT_URL, url.Buf());
curl_easy_setopt(chandle, CURLOPT_WRITEFUNCTION, Write2String);
curl_easy_setopt(chandle, CURLOPT_WRITEDATA, &out);
auto res = curl_easy_perform(chandle);
return {out, res};
}

102
src/layereddata.cpp

@ -51,8 +51,9 @@ MString LayeredData::Open(const MString& dataset)
nc.emplace_back(std::move(url));
if(!nc.back())
{
auto failedurl = nc.back().Url();
nc.clear();
return "Can't connect to url " + url;
return "Can't connect to url " + failedurl;
}
}
else
@ -139,20 +140,30 @@ std::pair<const BaseParameters*, MString> LayeredData::Parameters(michlib_intern
real lon2 = ToGeoDomain(reg.lone, dom);
real lat1 = reg.latb;
real lat2 = reg.late;
bool global = lone - lonb + 1.5 * lonstep > 360.0;
// Special case when the longitude lies in a small sector between the end and the start
if(global)
{
if(lon1 < lonb) lon1 = lone;
if(lon2 > lone) lon2 = lonb;
ppar->yb = static_cast<size_t>(Floor((lat1 - latb) / latstep));
ppar->ye = static_cast<size_t>(Ceil((lat2 - latb) / latstep));
if(ppar->ye > dname.ny - 1) ppar->ye = dname.ny - 1;
if(ppar->yb >= ppar->ye) return {nullptr, "Latb must be lesser then late"};
}
else
{
if(lon1 < lonb) lon1 = lonb;
if(lon2 > lone) lon2 = lone;
}
ppar->xb = static_cast<size_t>(Floor((lon1 - lonb) / lonstep));
ppar->xe = static_cast<size_t>(Ceil((lon2 - lonb) / lonstep));
if(ppar->xb == ppar->xe) return {nullptr, "Lonb must be not equal late"};
if(!global && ppar->xb > ppar->xe) return {nullptr, "Lonb must be lesser then lone"};
ppar->yb = static_cast<size_t>(Floor((lat1 - latb) / latstep));
ppar->ye = static_cast<size_t>(Ceil((lat2 - latb) / latstep));
if(ppar->ye > dname.ny - 1) ppar->ye = dname.ny - 1;
if(ppar->yb >= ppar->ye) return {nullptr, "Latb must be lesser then late"};
if(depth < 0.0 || depth > depths.back())
ppar->layer = (depth < 0.0) ? 0 : (depths.size() - 1);
@ -186,84 +197,7 @@ bool LayeredData::Read(const MString& vname, std::map<MString, LayeredData::Data
auto p = dynamic_cast<const struct Parameters*>(ip);
auto [name, id, tid] = VarNameLoc(vname, times[i]);
if(!name.Exist()) // Conversion read
{
// ptemp from temp and sal
if(vname == "ptemp")
{
if(!(Read("temp", cache, ip, i) && Read("sal", cache, ip, i))) return false;
cache[vname] = cache.at("temp").CopyGrid();
auto& ptemp = cache.at(vname);
const auto& temp = cache.at("temp");
const auto& sal = cache.at("sal");
ptemp.SetUnit("degrees_C");
for(size_t ind = 0; ind < ptemp.N(); ind++)
{
if(temp.IsFill(ind) || sal.IsFill(ind))
ptemp.V(ind) = ptemp.Fillval();
else
ptemp.V(ind) = Temp2PTemp(temp.V(ind), sal.V(ind), Depth(p->layer), ptemp.Lon(ind), ptemp.Lat(ind));
}
return true;
}
// temp from ptemp and sal
if(vname == "temp")
{
if(!(Read("ptemp", cache, ip, i) && Read("sal", cache, ip, i))) return false;
cache[vname] = cache.at("ptemp").CopyGrid();
auto& temp = cache.at(vname);
const auto& ptemp = cache.at("ptemp");
const auto& sal = cache.at("sal");
temp.SetUnit("degrees_C");
for(size_t ind = 0; ind < temp.N(); ind++)
{
if(ptemp.IsFill(ind) || sal.IsFill(ind))
temp.V(ind) = temp.Fillval();
else
temp.V(ind) = PTemp2Temp(ptemp.V(ind), sal.V(ind), Depth(p->layer), temp.Lon(ind), temp.Lat(ind));
}
return true;
}
// pdens from temp and sal
if(vname == "pdens")
{
bool tempispot = HaveVar("ptemp");
if(!(Read(tempispot ? "ptemp" : "temp", cache, ip, i) && Read("sal", cache, ip, i))) return false;
cache[vname] = cache.at("sal").CopyGrid();
auto& pdens = cache.at(vname);
const auto& temp = cache.at(tempispot ? "ptemp" : "temp");
const auto& sal = cache.at("sal");
pdens.SetUnit("kg m-3");
for(size_t ind = 0; ind < pdens.N(); ind++)
{
if(temp.IsFill(ind) || sal.IsFill(ind))
pdens.V(ind) = pdens.Fillval();
else
pdens.V(ind) = tempispot ? PTemp2PDens(temp.V(ind), sal.V(ind), Depth(p->layer), pdens.Lon(ind), pdens.Lat(ind))
: Temp2PDens(temp.V(ind), sal.V(ind), Depth(p->layer), pdens.Lon(ind), pdens.Lat(ind));
}
return true;
}
// U and U2 from u and v
if(vname == "U" || vname == "U2")
{
bool square = vname == "U2";
if(!(Read("u", cache, ip, i) && Read("v", cache, ip, i))) return false;
cache[vname] = cache.at("u").CopyGrid();
auto& U = cache.at(vname);
const auto& u = cache.at("u");
const auto& v = cache.at("v");
if(u.Unit().Exist()) U.SetUnit(square ? ("(" + u.Unit() + ")2") : u.Unit());
for(size_t ind = 0; ind < U.N(); ind++)
{
if(u.IsFill(ind) || v.IsFill(ind))
U.V(ind) = U.Fillval();
else
U.V(ind) = square ? (u(ind) * u(ind) + v(ind) * v(ind)) : michlib::Hypot(u(ind), v(ind));
}
return true;
}
return false;
}
return TransformationRead(this, vname, cache, ip, i);
// Direct read
bool nodepth = false;

295
src/layereddataz.cpp

@ -0,0 +1,295 @@
#define MICHLIB_NOSOURCE
#include "layereddataz.h"
MString LayeredDataZ::Info() const
{
if(!isOk()) return "";
MString d;
for(size_t i = 0; i < NDepths(); i++) d += MString(" ") + "(" + i + " " + Depth(i) + ")";
std::set<MString> vars;
for(const auto& f: nc) GetVars(f, vars);
MString svars;
{
bool first = true;
for(const auto& v: vars)
{
svars += (first ? "" : ", ") + v;
first = false;
}
}
// clang-format off
return
"Dataset: " + Title() + "\n" +
" Begin date: " + Time(0).ToString() + "\n" +
" End date: " + Time(NTimes()-1).ToString() + "\n" +
" Time step: " + Timestep() + " seconds\n" +
" Time moments: " + NTimes() + "\n" +
" Region: (" + lonb + " : " + lone + ") x (" + latb + " : " + late + ")\n" +
" Grid: " + dname.nx + "x" + dname.ny + " (" + lonstep + " x " + latstep + ")\n" +
" Depths:" + d + "\n" +
" Supported variables: " + svars;
// clang-format on
}
MString LayeredDataZ::Open(const MString& dataset)
{
nc.clear();
MString proxyurl = GPL.ParameterSValue("USEPROXY", "");
if(proxyurl.Exist()) proxy.Activate("all_proxy", proxyurl);
nc.clear();
size_t i = 1;
while(true)
{
MString url = GPL.ParameterSValue(dataset + "_URL" + i, "");
if(url.Exist())
{
// Split url on product and dataset
auto words = url.Split(":");
if(words.size() == 0 || words.size() > 2)
{
nc.clear();
return "Invalid url " + url;
}
MString product = words[0];
MString dataset = words.size() == 2 ? words[1] : "";
nc.emplace_back();
{
auto ret = nc.back().OpenZarr(product, dataset);
if(!ret)
{
nc.clear();
return "Can't open " + dataset + " of " + product;
}
}
}
else
break;
i++;
}
if(nc.size() == 0) return "No urls for dataset " + dataset + " specified in config";
dname = GetDNames(nc[0]);
if(!(dname.lonname.Exist() && dname.latname.Exist()))
{
nc.clear();
return "Can't find longitude/latitude";
}
if(!dname.timename.Exist())
{
nc.clear();
return "Can't find time";
}
auto cn = GetCNames(nc[0]);
// Read times
for(auto& f: nc)
{
auto ret = f.ReadTimes(cn.timename);
if(!ret)
{
nc.clear();
return "Can't read times";
}
times.insert(times.end(), f.Times().begin(), f.Times().end());
}
std::sort(times.begin(), times.end());
auto last = std::unique(times.begin(), times.end());
times.erase(last, times.end());
depthinv = false;
if(cn.depthname.Exist())
{
auto ret = nc[0].Read(cn.depthname, depths);
if(!ret)
{
nc.clear();
return "Can't read depths";
}
if(depths.back() <= 0 && depths.front() <= 0) std::ranges::transform(depths, depths.begin(), std::negate{});
if(depths.back() < depths.front() && depths.size() > 1)
{
depthinv = true;
for(size_t i = 0; i < depths.size() - i - 1; i++) std::swap(depths[i], depths[depths.size() - i - 1]);
}
}
else // Surface only data
{
depths.resize(1);
depths[0] = 0;
}
std::vector<double> lons, lats;
{
auto ret = nc[0].Read(cn.lonname, lons);
if(!ret)
{
nc.clear();
return "Can't get longitudes";
}
}
{
auto ret = nc[0].Read(cn.latname, lats);
if(!ret)
{
nc.clear();
return "Can't get latitudes";
}
}
lonb = lons[0];
latb = lats[0];
lone = lons.back();
late = lats.back();
lonstep = (lone - lonb) / (dname.nx - 1);
latstep = (late - latb) / (dname.ny - 1);
return "";
}
std::pair<const BaseParameters*, MString> LayeredDataZ::Parameters(michlib_internal::ParameterListEx& pars, const CLArgs& args, const struct Region& reg) const
{
std::unique_ptr<struct Parameters> ppar{new struct Parameters};
ppar->layer = args.contains("layer") ? args.at("layer").ToInteger<size_t>() : 0;
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);
{
auto dom = DetGeoDomain(lonb, lone);
real lon1 = ToGeoDomain(reg.lonb, dom);
real lon2 = ToGeoDomain(reg.lone, dom);
real lat1 = reg.latb;
real lat2 = reg.late;
bool global = lone - lonb + 1.5 * lonstep > 360.0;
// Special case when the longitude lies in a small sector between the end and the start
if(global)
{
if(lon1 < lonb) lon1 = lone;
if(lon2 > lone) lon2 = lonb;
}
else
{
if(lon1 < lonb) lon1 = lonb;
if(lon2 > lone) lon2 = lone;
}
ppar->xb = static_cast<size_t>(Floor((lon1 - lonb) / lonstep));
ppar->xe = static_cast<size_t>(Ceil((lon2 - lonb) / lonstep));
if(ppar->xb == ppar->xe) return {nullptr, "Lonb must be not equal late"};
if(!global && ppar->xb > ppar->xe) return {nullptr, "Lonb must be lesser then lone"};
ppar->yb = static_cast<size_t>(Floor((lat1 - latb) / latstep));
ppar->ye = static_cast<size_t>(Ceil((lat2 - latb) / latstep));
if(ppar->ye > dname.ny - 1) ppar->ye = dname.ny - 1;
if(ppar->yb >= ppar->ye) return {nullptr, "Latb must be lesser then late"};
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++)
{
if(depth >= depths[i] && depth <= depths[i + 1])
{
ppar->layer = (depth - depths[i] <= depths[i + 1] - depth) ? i : (i + 1);
break;
}
}
}
pars.SetParameter("depth", Depth(ppar->layer));
pars.SetParameter("layer", ppar->layer);
pars.SetParameter("dataset", Title());
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(), ""};
}
bool LayeredDataZ::Read(const MString& vname, std::map<MString, LayeredDataZ::Data>& cache, const BaseParameters* ip, size_t i) const
{
if(cache.contains(vname)) return true;
if(!isOk()) return false;
auto p = dynamic_cast<const struct Parameters*>(ip);
auto [name, id, tid] = VarNameLoc(vname, times[i]);
if(!name.Exist()) // Conversion read
return TransformationRead(this, vname, cache, ip, i);
// Direct read
bool nodepth = false;
Data data;
//auto head = nc[id]->Header();
for(const auto& v: nc[id].VarNames())
if(v == name)
{
if(nc[id].NDim(v) == 3) nodepth = true;
data = ReadVarRaw(nc[id], name, tid, nodepth, p);
if(data)
{
cache[vname] = std::move(data);
return true;
}
}
return false;
}
LayeredDataZ::Data LayeredDataZ::ReadVarRaw(const NC& f, const MString& name, size_t i, bool nodepth, const struct LayeredDataZ::Parameters* p) const
{
real unitmul = 1.0;
real offset = 0.0, scale = 1.0;
if(f.HasAtt(name, "add_offset")) offset = f.AttReal(name, "add_offset");
if(f.HasAtt(name, "scale_factor")) scale = f.AttReal(name, "scale_factor");
MString unit;
if(f.HasAtt(name, "units")) unit = f.AttString(name, "units");
if(unit == "m s-1" || unit == "m/s")
{
unitmul = 100.0;
unit = "cm/s";
}
Data data((p->xb < p->xe) ? (p->xe - p->xb + 1) : (dname.nx + p->xe - p->xb + 1), p->ye - p->yb + 1, Lon(p->xb), Lat(p->yb), lonstep, latstep, std::move(unit));
auto trans = [scale, offset, unitmul](auto raw) -> real { return (raw * scale + offset) * unitmul; };
auto rlayer = depthinv ? depths.size() - p->layer - 1 : p->layer;
if(p->xb < p->xe)
{
auto ret = nodepth ? f.Read(name, data, trans, {dname.lonname, p->xb, p->xe - p->xb + 1}, {dname.latname, p->yb, p->ye - p->yb + 1}, {dname.timename, i, 1})
: f.Read(name, data, trans, {dname.lonname, p->xb, p->xe - p->xb + 1}, {dname.latname, p->yb, p->ye - p->yb + 1}, {dname.timename, i, 1},
{dname.depthname, rlayer, 1});
if(!ret) return Data();
}
else
{
{
auto ret = nodepth ? f.Read(name, data, trans, {dname.lonname, p->xb, dname.nx - p->xb}, {dname.latname, p->yb, p->ye - p->yb + 1}, {dname.timename, i, 1})
: f.Read(name, data, trans, {dname.lonname, p->xb, dname.nx - p->xb}, {dname.latname, p->yb, p->ye - p->yb + 1}, {dname.timename, i, 1},
{dname.depthname, rlayer, 1});
if(!ret) return Data();
}
{
size_t shift = dname.nx - p->xb + 1;
auto shifteddata = [&data, shift](size_t ix, size_t iy) -> real& { return data(ix + shift, iy); };
auto ret =
nodepth ? f.Read(name, shifteddata, trans, {dname.lonname, 0, p->xe + 1}, {dname.latname, p->yb, p->ye - p->yb + 1}, {dname.timename, i, 1})
: f.Read(name, shifteddata, trans, {dname.lonname, 0, p->xe + 1}, {dname.latname, p->yb, p->ye - p->yb + 1}, {dname.timename, i, 1}, {dname.depthname, rlayer, 1});
if(!ret) return Data();
}
}
return data;
}

134
src/mirrorfuncs.cpp

@ -0,0 +1,134 @@
#define MICHLIB_NOSOURCE
#include "mirrorfuncs.h"
#include "StringFunctions.h"
#include "filehelpers.h"
#include "merrors.h"
using michlib::FD;
using michlib::message;
bool MakePath(const MString& dname)
{
struct stat st;
int ret = stat(dname.Buf(), &st);
if(ret == 0) return S_ISDIR(st.st_mode);
auto dirs = michlib::Split_on_words(dname, "/", false);
MString cdir = "";
for(const auto& dir: dirs)
{
cdir += "/" + dir;
ret = stat(cdir.Buf(), &st);
if(ret == 0 && S_ISDIR(st.st_mode)) continue;
if(ret == 0 && !S_ISDIR(st.st_mode)) return false;
ret = mkdir(cdir.Buf(), 0755);
if(ret != 0) return false;
}
return true;
}
RetVal<std::vector<struct FileInfo>> ReadLocalFileList(const MString& dir, const bool nofollow, const MString& path)
{
const static MString pref = "ReadLocalFileList";
std::vector<struct FileInfo> out;
DIRRAII dhandle;
MakePath(dir);
dhandle.reset(opendir(dir.Buf()));
if(!dhandle) return {pref, "Can't open directory " + path + (path.Exist() ? "/" : "") + dir};
int dfd = dirfd(dhandle);
errno = 0;
struct dirent* dent = readdir(dhandle);
if(errno != 0) return {pref, "Can't read directory " + path + (path.Exist() ? "/" : "") + dir};
struct stat st;
do {
if(dent->d_name[0] != '.')
{
int ret = fstatat(dfd, dent->d_name, &st, nofollow ? AT_SYMLINK_NOFOLLOW : 0);
if(ret != 0) return {pref, "Can't stat " + path + "/" + dir + "/" + dent->d_name};
if(S_ISDIR(st.st_mode)) // Directory, recurse
{
auto list = ReadLocalFileList(dir + "/" + dent->d_name, nofollow, path + (path.Exist() ? "/" : "") + dent->d_name);
if(!list) return list;
out.insert(out.end(), list.Value().begin(), list.Value().end());
}
if(S_ISREG(st.st_mode)) // Regular file
{
out.emplace_back(dir + "/" + dent->d_name, path + (path.Exist() ? "/" : "") + dent->d_name, MDateTime(st.st_mtim.tv_sec, st.st_mtim.tv_nsec), st.st_size);
}
// Ignore non-directories, non-files
}
dent = readdir(dhandle);
} while(dent != nullptr || errno != 0);
if(errno != 0) return {pref, "Can't read directory " + path + "/" + dir};
std::sort(out.begin(), out.end(), [](const struct FileInfo& a, const struct FileInfo& b) { return a.name < b.name; });
return out;
}
Error DownloadFile(const CURLRAII& chandle, const struct FileInfo& rinfo, const MString& root)
{
const static MString pref = "DownloadFile";
message("Downloading " + rinfo.url);
MString dname = DirName(rinfo.name), fname = FileName(rinfo.name);
FD fd;
if(!MakePath(root + "/" + dname)) return {pref, "Can't create directory " + root + "/" + dname};
fd.Reset(creat((root + "/" + rinfo.name).Buf(), 0644));
if(!fd) return {pref, "Can't create file " + root + "/" + rinfo.name};
int cfd = fd.Get();
curl_easy_setopt(chandle, CURLOPT_WRITEFUNCTION, Write2File);
curl_easy_setopt(chandle, CURLOPT_WRITEDATA, &cfd);
curl_easy_setopt(chandle, CURLOPT_URL, rinfo.url.Buf());
auto res = curl_easy_perform(chandle);
if(res != CURLE_OK)
{
unlink((root + "/" + rinfo.name).Buf());
return {pref, MString("Can't download file: ") + chandle.Err()};
}
{
struct timespec times[2];
times[0].tv_sec = times[1].tv_sec = rinfo.mtime.Epoch();
times[0].tv_nsec = times[1].tv_nsec = 0;
int ret = futimens(fd, times);
if(ret != 0)
{
unlink((root + "/" + rinfo.name).Buf());
return {pref, "Can't set mtime for file: " + root + "/" + rinfo.name};
}
}
return Error();
}
Error RemoveFile(const struct FileInfo& linfo)
{
const static MString pref = "RemoveFile";
message("Remove " + linfo.url);
int ret = unlink(linfo.url.Buf());
if(ret != 0) return {pref, "Can't remove file " + linfo.url};
return Error();
}
Error UpdateFile(const CURLRAII& chandle, const struct FileInfo& rinfo, const struct FileInfo& linfo, const MString& root)
{
const static MString pref = "UpdateFile";
message("Update " + linfo.url);
auto rm = RemoveFile(linfo);
if(!rm) return rm.Add(pref, "Can't remove file");
auto df = DownloadFile(chandle, rinfo, root);
if(!df) return df.Add(pref, "Can't download file");
return Error();
}

168
src/ncfilew.cpp

@ -1,105 +1,143 @@
#define MICHLIB_NOSOURCE
#include "ncfilew.h"
MString NCFileW::CreateFile(NCFileW::Type stype, const MString& name, const MString& history, int compression, size_t nx, size_t ny)
MString NCFileW::CreateFile(NCFileW::Type stype, const MString& name, int compression, size_t nx, size_t ny)
{
if(stype == UNKNOWN) return "Can't determine file type";
compress = compression;
int ret;
MString text;
const float node_offset = 0.0;
ret = nc_create(name.Buf(), NC_CLOBBER | NC_NETCDF4, &ncid);
if(ret != NC_NOERR) return "Can't create netcdf file: " + name;
Open(name);
if(!*this) return "Can't create netcdf file " + name + ": " + ErrMessage();
ret = nc_put_att_text(ncid, NC_GLOBAL, "history", history.Len() + 1, history.Buf());
if(ret != NC_NOERR) return "Can't write history attribute in the netcdf file";
AddAtt("node_offset", node_offset);
switch(stype)
{
case(G1V1):
case(PSET):
{
ret = nc_def_dim(ncid, "i", nx, &xdimid);
if(ret != NC_NOERR) return "Can't create dimension in the netcdf file";
ret = nc_def_var(ncid, "longitude", NC_FLOAT, 1, &xdimid, &xid);
if(ret != NC_NOERR) return "Can't create longitude variable in the netcdf file";
ret = nc_def_var(ncid, "latitude", NC_FLOAT, 1, &xdimid, &yid);
if(ret != NC_NOERR) return "Can't create latitude variable in the netcdf file";
AddDim("i", nx);
AddVar("x", NC_FLOAT, "i");
AddVar("y", NC_FLOAT, "i");
break;
}
case(G1V2):
case(GPSET):
{
ret = nc_def_dim(ncid, "longitude", nx, &xdimid);
if(ret != NC_NOERR) return "Can't create x-dimension in the netcdf file";
ret = nc_def_dim(ncid, "latitude", ny, &ydimid);
if(ret != NC_NOERR) return "Can't create y-dimension in the netcdf file";
ret = nc_def_var(ncid, "longitude", NC_FLOAT, 1, &xdimid, &xid);
if(ret != NC_NOERR) return "Can't create longitude variable in the netcdf file";
ret = nc_def_var(ncid, "latitude", NC_FLOAT, 1, &ydimid, &yid);
if(ret != NC_NOERR) return "Can't create latitude variable in the netcdf file";
AddDim("i", nx);
AddVar("longitude", NC_FLOAT, "i");
AddVar("latitude", NC_FLOAT, "i");
break;
}
case(G2V2):
case(RGRID):
{
ret = nc_def_dim(ncid, "longitude", nx, &xdimid);
if(ret != NC_NOERR) return "Can't create x-dimension in the netcdf file";
ret = nc_def_dim(ncid, "latitude", ny, &ydimid);
if(ret != NC_NOERR) return "Can't create y-dimension in the netcdf file";
ret = nc_def_var(ncid, "longitude", NC_FLOAT, 2, dimid, &xid);
if(ret != NC_NOERR) return "Can't create longitude variable in the netcdf file";
ret = nc_def_var(ncid, "latitude", NC_FLOAT, 2, dimid, &yid);
if(ret != NC_NOERR) return "Can't create latitude variable in the netcdf file";
AddDim("x", nx);
AddDim("y", ny);
AddVar("x", NC_FLOAT, "x");
AddVar("y", NC_FLOAT, "y");
break;
}
case(GRGRID):
{
AddDim("longitude", nx);
AddDim("latitude", ny);
AddVar("longitude", NC_FLOAT, "longitude");
AddVar("latitude", NC_FLOAT, "latitude");
break;
}
case(GRID):
{
AddDim("x", nx);
AddDim("y", ny);
AddVar("x", NC_FLOAT, "y", "x");
AddVar("y", NC_FLOAT, "y", "x");
break;
}
case(GGRID):
{
AddDim("longitude", nx);
AddDim("latitude", ny);
AddVar("longitude", NC_FLOAT, "latitude", "longitude");
AddVar("latitude", NC_FLOAT, "latitude", "longitude");
break;
}
case(UNKNOWN): return "Can't determine file type";
}
ret = nc_def_var_deflate(ncid, xid, 1, 1, compress);
if(ret != NC_NOERR) return "Can't set deflate parameters for longitude variable in the netcdf file";
ret = nc_def_var_deflate(ncid, yid, 1, 1, compress);
if(ret != NC_NOERR) return "Can't set deflate parameters for latitude variable in the netcdf file";
text = "longitude";
ret = nc_put_att_text(ncid, xid, "standard_name", text.Len() + 1, text.Buf());
if(ret != NC_NOERR) return "Can't write standard_name attribute of longitude variable in the netcdf file";
text = "latitude";
ret = nc_put_att_text(ncid, yid, "standard_name", text.Len() + 1, text.Buf());
if(ret != NC_NOERR) return "Can't write standard_name attribute of latitude variable in the netcdf file";
if(IsGeoType(stype))
{
SetComp("longitude", compress);
SetComp("latitude", compress);
AddAtt("longitude", "standard_name", "longitude");
AddAtt("longitude", "long_name", "Longitude");
AddAtt("latitude", "standard_name", "latitude");
AddAtt("latitude", "long_name", "Latitude");
}
else
{
SetComp("x", compress);
SetComp("y", compress);
AddAtt("x", "long_name", "x-coordinate");
AddAtt("y", "long_name", "y-coordinate");
}
if(!*this) return "Can't set grid in the netcdf file " + name + ": " + ErrMessage();
type = stype;
return "";
}
MString NCFileW::AddTimeData(const TimeData& tdata, bool tisindex)
{
tdep = tisindex;
AddDim("time", tdata.steps.size());
AddVar("time", Type2NCType<decltype(tdata.steps)::value_type>, "time");
SetComp("time", compress);
AddAtt("time", "standard_name", "time");
AddAtt("time", "long_name", "time");
AddAtt("time", "units", tdata.UnitName());
WriteVar("time", tdata.steps.data());
if(!*this) return MString("Can't add time data to the netcdf file: ") + ErrMessage();
return "";
}
MString NCFileW::AddVariable(const MString& name, const MString& stname, const MString& lname, const MString& units, const MString& comment)
{
if(type == UNKNOWN) return "File not opened";
if(HaveVar(name)) return "Variable " + name + " already defined";
struct Var v(name, 0);
int ret;
if(type == G1V1)
ret = nc_def_var(ncid, v.name.Buf(), NC_FLOAT, 1, &xdimid, &v.id);
if(Is1DType(type))
{
if(tdep)
AddVar(name, NC_FLOAT, "time", "i");
else
ret = nc_def_var(ncid, v.name.Buf(), NC_FLOAT, 2, dimid, &v.id);
if(ret != NC_NOERR) return "Can't create " + v.name + " variable in the netcdf file";
ret = nc_def_var_deflate(ncid, v.id, 1, 1, compress);
if(ret != NC_NOERR) return "Can't set deflate parameters for " + v.name + " variable in the netcdf file";
AddVar(name, NC_FLOAT, "i");
}
else if(IsGeoType(type))
{
if(tdep)
AddVar(name, NC_FLOAT, "time", "latitude", "longitude");
else
AddVar(name, NC_FLOAT, "latitude", "longitude");
}
else
{
if(tdep)
AddVar(name, NC_FLOAT, "time", "y", "x");
else
AddVar(name, NC_FLOAT, "y", "x");
}
if(stname.Exist()) ret = nc_put_att_text(ncid, v.id, "standard_name", stname.Len() + 1, stname.Buf());
if(ret != NC_NOERR) return "Can't write standard_name attribute of " + v.name + " variable in the netcdf file";
if(lname.Exist()) ret = nc_put_att_text(ncid, v.id, "long_name", lname.Len() + 1, lname.Buf());
if(ret != NC_NOERR) return "Can't write long_name attribute of " + v.name + " variable in the netcdf file";
if(units.Exist()) ret = nc_put_att_text(ncid, v.id, "units", units.Len() + 1, units.Buf());
if(ret != NC_NOERR) return "Can't write units attribute of " + v.name + " variable in the netcdf file";
if(comment.Exist()) ret = nc_put_att_text(ncid, v.id, "comment", comment.Len() + 1, comment.Buf());
if(ret != NC_NOERR) return "Can't write comment attribute of " + v.name + " variable in the netcdf file";
SetComp(name, compress);
if(stname.Exist()) AddAtt(name, "standard_name", stname);
if(lname.Exist()) AddAtt(name, "long_name", lname);
if(units.Exist()) AddAtt(name, "units", units);
if(comment.Exist()) AddAtt(name, "comment", comment);
AddAtt(name, "_FillValue", fill);
ret = nc_put_att_float(ncid, v.id, "_FillValue", NC_FLOAT, 1, &fill);
if(ret != NC_NOERR) return "Can't write _FillValue attribute of " + v.name + " variable in the netcdf file";
if(!*this) return "Can't add variable " + name + ": " + ErrMessage();
vars.push_back(v);
return "";
}

129
src/ncfuncs.cpp

@ -1,9 +1,9 @@
#define MICHLIB_NOSOURCE
#include "ncfuncs.h"
NCFuncs::CoordNames NCFuncs::GetDNames(const NCFileA& nc)
NCFuncs::DimNames NCFuncs::GetDNames(const NCFileA& nc)
{
CoordNames out;
DimNames out;
auto head = nc.Header();
for(const auto& dim: head.Dimensions())
{
@ -31,6 +31,36 @@ NCFuncs::CoordNames NCFuncs::GetDNames(const NCFileA& nc)
return out;
}
NCFuncs::DimNames NCFuncs::GetDNames(const NCZarr& nc)
{
DimNames out;
for(const auto& v: nc.VarNames())
for(const auto& dim: nc.DimNames(v))
{
if(dim == "lon" || dim == "longitude")
{
out.lonname = dim;
out.nx = nc.DimSize(v, dim);
}
if(dim == "lat" || dim == "latitude")
{
out.latname = dim;
out.ny = nc.DimSize(v, dim);
}
if(dim == "depth" || dim == "elevation")
{
out.depthname = dim;
out.nz = nc.DimSize(v, dim);
}
if(dim == "time")
{
out.timename = dim;
out.nt = nc.DimSize(v, dim);
}
}
return out;
}
NCFuncs::CoordNames NCFuncs::GetCNames(const NCFileA& nc)
{
CoordNames out;
@ -55,12 +85,40 @@ NCFuncs::CoordNames NCFuncs::GetCNames(const NCFileA& nc)
if(islat) out.latname = v.Name();
if(isdepth) out.depthname = v.Name();
if(istime) out.timename = v.Name();
}
return out;
}
if(islon) out.nx = v.Dimensions().size();
if(islat) out.ny = v.Dimensions().size();
if(isdepth) out.nz = v.Dimensions().size();
if(istime) out.nt = v.Dimensions().size();
NCFuncs::CoordNames NCFuncs::GetCNames(const NCZarr& nc)
{
CoordNames out;
for(const auto& v: nc.VarNames()) // Try to define coordinates by attribute standard_name or attribute axis
{
auto havestname = nc.HasAtt(v, "standard_name");
auto haveaxis = nc.HasAtt(v, "axis");
if(!(havestname || haveaxis)) continue;
auto stname = nc.AttString(v, "standard_name");
auto axis = nc.AttString(v, "axis");
bool islon = false, islat = false, isdepth = false, istime = false;
if(stname == "longitude") islon = true;
if(stname == "latitude") islat = true;
if(stname == "depth") isdepth = true;
if(stname == "time") istime = true;
if(!out.lonname.Exist() && axis == "X") islon = true;
if(!out.latname.Exist() && axis == "Y") islat = true;
if(!out.depthname.Exist() && axis == "Z") isdepth = true;
if(!out.timename.Exist() && axis == "T") istime = true;
if(islon) out.lonname = v;
if(islat) out.latname = v;
if(isdepth) out.depthname = v;
if(istime) out.timename = v;
}
// If time not found just check variable "time"
if(!out.timename.Exist() && nc.HasVar("time")) out.timename = "time";
return out;
}
@ -79,6 +137,26 @@ void NCFuncs::GetVars(const NCFileA& nc, std::set<MString>& vars)
if(vars.contains("u") && vars.contains("v")) vars.emplace("U");
if(vars.contains("u") && vars.contains("v")) vars.emplace("U2");
if(vars.contains("ssh")) vars.emplace("ugeo");
if(vars.contains("ssh")) vars.emplace("vgeo");
}
void NCFuncs::GetVars(const NCZarr& nc, std::set<MString>& vars)
{
for(const auto& v: nc.VarNames())
{
if(!nc.HasAtt(v, "standard_name")) continue;
auto ret = nc.AttString(v, "standard_name");
if(StName2Name(ret).Exist()) vars.emplace(StName2Name(ret));
}
if((vars.contains("ptemp") || vars.contains("temp")) && vars.contains("sal")) vars.emplace("pdens");
if(vars.contains("ptemp") && vars.contains("sal")) vars.emplace("temp");
if(vars.contains("temp") && vars.contains("sal")) vars.emplace("ptemp");
if(vars.contains("u") && vars.contains("v")) vars.emplace("U");
if(vars.contains("u") && vars.contains("v")) vars.emplace("U2");
if(vars.contains("ssh")) vars.emplace("ugeo");
if(vars.contains("ssh")) vars.emplace("vgeo");
}
std::tuple<MDateTime, time_t, bool> NCFuncs::Refdate(const MString& refdate)
@ -91,6 +169,7 @@ std::tuple<MDateTime, time_t, bool> NCFuncs::Refdate(const MString& refdate)
auto ci = words.begin();
if(ci != words.end())
{
if(*ci == "seconds") step = 1;
if(*ci == "minutes") step = 60;
if(*ci == "hours") step = 3600;
if(*ci == "days") step = 3600 * 24;
@ -114,15 +193,16 @@ MString NCFuncs::StName2Name(const MString& stname)
if(stname == "sea_surface_elevation") return "ssh";
if(stname == "eastward_sea_water_velocity") return "u";
if(stname == "northward_sea_water_velocity") return "v";
if(stname == "surface_geostrophic_eastward_sea_water_velocity") return "u";
if(stname == "surface_geostrophic_northward_sea_water_velocity") return "v";
if(stname == "upward_sea_water_velocity") return "w";
if(stname == "surface_geostrophic_eastward_sea_water_velocity") return "ugs";
if(stname == "surface_geostrophic_northward_sea_water_velocity") return "vgs";
if(stname == "mass_concentration_of_chlorophyll_a_in_sea_water") return "chl";
if(stname == "mole_concentration_of_nitrate_in_sea_water") return "NO3";
if(stname == "net_primary_production_of_biomass_expressed_as_carbon_per_unit_volume_in_sea_water") return "prprod";
if(stname == "mole_concentration_of_phytoplankton_expressed_as_carbon_in_sea_water") return "Cchl";
if(stname == "mole_concentration_of_phosphate_in_sea_water") return "PO4";
if(stname == "mole_concentration_of_silicate_in_sea_water") return "Si";
if(stname == "mole_concentration_of_dissolved_molecular_oxygen_in_sea_water") return "O2";
return "";
}
@ -136,17 +216,39 @@ MString NCFuncs::Name2StName(const MString& name)
if(name == "u") return "eastward_sea_water_velocity";
if(name == "v") return "northward_sea_water_velocity";
if(name == "w") return "upward_sea_water_velocity";
if(name == "ugs") return "surface_geostrophic_eastward_sea_water_velocity";
if(name == "vgs") return "surface_geostrophic_northward_sea_water_velocity";
if(name == "chl") return "mass_concentration_of_chlorophyll_a_in_sea_water";
if(name == "NO3") return "mole_concentration_of_nitrate_in_sea_water";
if(name == "prprod") return "net_primary_production_of_biomass_expressed_as_carbon_per_unit_volume_in_sea_water";
if(name == "Cchl") return "mole_concentration_of_phytoplankton_expressed_as_carbon_in_sea_water";
if(name == "PO4") return "mole_concentration_of_phosphate_in_sea_water";
if(name == "Si") return "mole_concentration_of_silicate_in_sea_water";
if(name == "O2") return "mole_concentration_of_dissolved_molecular_oxygen_in_sea_water";
return "";
}
MString NCFuncs::Name2LongName(const MString& name)
{
if(name == "ptemp") return "Potential temperature";
if(name == "temp") return "Temperature";
if(name == "sal") return "Salinity";
if(name == "mld") return "Mixed layer depth";
if(name == "ssh") return "Sea surface height";
if(name == "u") return "X-velocity";
if(name == "v") return "Y-velocity";
if(name == "w") return "Z-velocity";
if(name == "ugeo") return "Geostrophic eastward velocity from ssh";
if(name == "vgeo") return "Geostrophic northward velocity from ssh";
if(name == "ugs") return "Geostrophic eastward velocity";
if(name == "vgs") return "Geostrophic northward velocity";
if(name == "chl") return "Concentration of chlorophyll";
if(name == "NO3") return "Concentration of nitrates";
if(name == "prprod") return "Primary production";
if(name == "Cchl") return "Concentration of chlorophyll carbon";
if(name == "PO4") return "Concentration of phosphates";
if(name == "Si") return "Concentration of silicates";
if(name == "O2") return "Concentration of dissolved oxygen";
if(name == "U") return "Module of horizontal velocity";
if(name == "U2") return "Squared horizontal velocity";
return "";
@ -163,3 +265,14 @@ bool NCFuncs::HaveVar(const NCFileA& nc, const MString& vname)
}
return false;
}
bool NCFuncs::HaveVar(const NCZarr& nc, const MString& vname)
{
for(const auto& v: nc.VarNames())
{
if(!nc.HasAtt(v, "standard_name")) continue;
auto stname = nc.AttString(v, "standard_name");
if(StName2Name(stname) == vname) return true;
}
return false;
}

231
src/ncsimple.cpp

@ -0,0 +1,231 @@
#define MICHLIB_NOSOURCE
#include "ncsimple.h"
RetVal<std::vector<NCSimpleFunctions::Attribute>> NCSimpleFunctions::ReadAtts(int vid) const
{
static const MString pref = "NCSimple::ReadAtts";
int natt;
int ret;
if(vid == NC_GLOBAL)
ret = nc_inq_natts(ncid, &natt);
else
ret = nc_inq_var(ncid, vid, nullptr, nullptr, nullptr, nullptr, &natt);
if(ret != NC_NOERR) return Error(pref, MString("Can't inquire number of attributes: ") + nc_strerror(ret));
std::vector<Attribute> out;
char name[NC_MAX_NAME + 1];
for(int aid = 0; aid < natt; aid++)
{
nc_type type;
size_t len;
ret = nc_inq_attname(ncid, vid, aid, name);
if(ret != NC_NOERR) return Error(pref, MString("Can't inquire attribute name: ") + nc_strerror(ret));
ret = nc_inq_atttype(ncid, vid, name, &type);
if(ret != NC_NOERR) return Error(pref, MString("Can't inquire attribute type: ") + nc_strerror(ret));
ret = nc_inq_attlen(ncid, vid, name, &len);
if(ret != NC_NOERR) return Error(pref, MString("Can't inquire attribute length: ") + nc_strerror(ret));
if(type == NC_DOUBLE || type == NC_FLOAT)
{
if(len == 1)
{
double d;
ret = nc_get_att_double(ncid, vid, name, &d);
if(ret != NC_NOERR) return Error(pref, MString("Can't read attribute ") + name + ": " + nc_strerror(ret));
out.emplace_back(MString(name), d);
}
else
{
std::vector<double> dd(len);
ret = nc_get_att_double(ncid, vid, name, dd.data());
if(ret != NC_NOERR) return Error(pref, MString("Can't read attribute ") + name + ": " + nc_strerror(ret));
for(size_t i = 0; i < dd.size(); i++) out.emplace_back(MString(name) + "[" + i + "]", dd[i]);
}
}
else if(type == NC_BYTE || type == NC_SHORT || type == NC_INT || type == NC_INT64)
{
if(len == 1)
{
long long i;
ret = nc_get_att_longlong(ncid, vid, name, &i);
if(ret != NC_NOERR) return Error(pref, MString("Can't read attribute ") + name + ": " + nc_strerror(ret));
out.emplace_back(MString(name), int_cast<int8>(i));
}
else
{
std::vector<long long> ii(len);
ret = nc_get_att_longlong(ncid, vid, name, ii.data());
if(ret != NC_NOERR) return Error(pref, MString("Can't read attribute ") + name + ": " + nc_strerror(ret));
for(size_t i = 0; i < ii.size(); i++) out.emplace_back(MString(name) + "[" + i + "]", int_cast<int8>(ii[i]));
}
}
else if(type == NC_UBYTE || type == NC_USHORT || type == NC_UINT || type == NC_UINT64)
{
if(len == 1)
{
unsigned long long u;
ret = nc_get_att_ulonglong(ncid, vid, name, &u);
if(ret != NC_NOERR) return Error(pref, MString("Can't read attribute ") + name + ": " + nc_strerror(ret));
out.emplace_back(MString(name), int_cast<uint8>(u));
}
else
{
std::vector<unsigned long long> uu(len);
ret = nc_get_att_ulonglong(ncid, vid, name, uu.data());
if(ret != NC_NOERR) return Error(pref, MString("Can't read attribute ") + name + ": " + nc_strerror(ret));
for(size_t i = 0; i < uu.size(); i++) out.emplace_back(MString(name) + "[" + i + "]", int_cast<uint8>(uu[i]));
}
}
else if(type == NC_CHAR)
{
std::vector<char> ss(len + 1, 0);
ret = nc_get_att_text(ncid, vid, name, ss.data());
if(ret != NC_NOERR) return Error(pref, MString("Can't read attribute ") + name + ": " + nc_strerror(ret));
out.emplace_back(MString(name), MString(ss.data()));
}
else
return Error(pref, MString("Unsupported type of attribute ") + name);
// Ignore all other types
}
return out;
}
Error NCSimpleFunctions::Open(const MString& filename)
{
static const MString pref = "NCSimple::Open";
// Cleanup
gats.clear();
dims.clear();
vars.clear();
nc_close(ncid);
std::vector<Attribute> newgats;
std::vector<Dimension> newdims;
std::vector<Variable> newvars;
char name[NC_MAX_NAME + 1];
// Open
{
auto ret = nc_open(filename.Buf(), 0, &ncid);
if(ret != NC_NOERR) return Error(pref, "Can't open file " + filename + ": " + nc_strerror(ret));
}
// Dimensions
{
int ndim;
auto ret = nc_inq_dimids(ncid, &ndim, nullptr, 1);
if(ret != NC_NOERR) return Error(pref, "Can't inquire number of dimensions in file " + filename + ": " + nc_strerror(ret));
std::vector<int> dimids(ndim);
ret = nc_inq_dimids(ncid, nullptr, dimids.data(), 1);
if(ret != NC_NOERR) return Error(pref, "Can't inquire dimension ids in file " + filename + ": " + nc_strerror(ret));
size_t len;
for(const auto id: dimids)
{
ret = nc_inq_dim(ncid, id, name, &len);
if(ret != NC_NOERR) return Error(pref, "Can't inquire dimension name and size in file " + filename + ": " + nc_strerror(ret));
newdims.emplace_back(name, len);
}
}
// Global attributes
{
auto ret = ReadAtts(NC_GLOBAL);
if(!ret) return ret.Add(pref, "Can't read global attributes in file " + filename);
newgats = std::move(ret.Value());
}
// Variables
{
int nvar;
auto ret = nc_inq_varids(ncid, &nvar, nullptr);
if(ret != NC_NOERR) return Error(pref, "Can't inquire number of variables in file " + filename + ": " + nc_strerror(ret));
std::vector<int> varids(nvar);
ret = nc_inq_varids(ncid, nullptr, varids.data());
if(ret != NC_NOERR) return Error(pref, "Can't inquire variables ids in file " + filename + ": " + nc_strerror(ret));
for(const auto vid: varids)
{
nc_type nctype;
int ndim;
auto ret = nc_inq_var(ncid, vid, name, &nctype, &ndim, nullptr, nullptr);
if(ret != NC_NOERR) return Error(pref, "Can't inquire variable info in file " + filename + ": " + nc_strerror(ret));
VarType vt = VarType::UNDEF;
if(nctype == NC_FLOAT) vt = VarType::FLOAT;
if(nctype == NC_DOUBLE) vt = VarType::DOUBLE;
if(nctype == NC_BYTE) vt = VarType::INT1;
if(nctype == NC_SHORT) vt = VarType::INT2;
if(nctype == NC_INT) vt = VarType::INT4;
if(nctype == NC_INT64) vt = VarType::INT8;
if(nctype == NC_UBYTE) vt = VarType::UINT1;
if(vt == VarType::UNDEF) return Error(pref, "Unsupported type of variable " + MString(name) + " in file " + filename);
std::vector<int> dimids(ndim);
ret = nc_inq_vardimid(ncid, vid, dimids.data());
if(ret != NC_NOERR) return Error(pref, "Can't inquire variable dimensions in file " + filename + ": " + nc_strerror(ret));
std::vector<size_t> dims;
char dname[NC_MAX_NAME + 1];
for(const auto did: dimids)
{
auto ret = nc_inq_dimname(ncid, did, dname);
if(ret != NC_NOERR) return Error(pref, "Can't inquire dimension name in file " + filename + ": " + nc_strerror(ret));
size_t ind = newdims.size();
for(size_t i = 0; i < newdims.size() && ind == newdims.size(); i++)
if(dname == newdims[i].Name()) ind = i;
if(ind == newdims.size()) return Error(pref, "Can't find dimension " + MString(dname) + " of variable " + name + " in file " + filename);
dims.push_back(ind);
}
auto atts = ReadAtts(vid);
const char fname[] = "_FillValue";
if(!atts) return Error(pref, "Can't get attributes of variable " + MString(name) + " in file " + filename);
std::vector<Attribute> vatts = std::move(atts.Value());
Variable::FillType fill;
if(FindInd(fname, vatts) != vatts.size())
{
if(nctype == NC_FLOAT || nctype == NC_DOUBLE)
{
double d;
auto ret = nc_get_att_double(ncid, vid, fname, &d);
if(ret != NC_NOERR) return Error(pref, "Can't get fill value for the variable " + MString(name) + " in file " + filename + ": " + nc_strerror(ret));
fill = d;
}
if(nctype == NC_BYTE || nctype == NC_SHORT || nctype == NC_INT || nctype == NC_INT64)
{
long long l;
auto ret = nc_get_att_longlong(ncid, vid, fname, &l);
if(ret != NC_NOERR) return Error(pref, "Can't get fill value for the variable " + MString(name) + " in file " + filename + ": " + nc_strerror(ret));
fill = int_cast<int8>(l);
}
if(nctype == NC_UBYTE || nctype == NC_USHORT || nctype == NC_UINT || nctype == NC_UINT64)
{
unsigned long long u;
auto ret = nc_get_att_ulonglong(ncid, vid, fname, &u);
if(ret != NC_NOERR) return Error(pref, "Can't get fill value for the variable " + MString(name) + " in file " + filename + ": " + nc_strerror(ret));
fill = int_cast<uint8>(u);
}
}
newvars.emplace_back(name, vt, std::move(dims), std::move(vatts), fill);
}
}
gats = std::move(newgats);
dims = std::move(newdims);
vars = std::move(newvars);
return Error();
}

250
src/zarr.cpp

@ -0,0 +1,250 @@
#define MICHLIB_NOSOURCE
#include "zarr.h"
#include "copcat.h"
#include <blosc.h>
std::vector<MString> ZarrFunctions::ReadVarNames(const Json::Value& meta)
{
std::vector<MString> out;
if(meta.type() != Json::objectValue) return out;
const auto keys = meta.getMemberNames();
for(const auto& key: keys)
{
if(!key.ends_with("/.zarray")) continue;
const auto vname = key.substr(0, key.size() - 8);
const auto& zattr = meta[vname + "/.zattrs"];
if(!(zattr && zattr.type() == Json::objectValue)) continue;
MString name(vname.c_str(), vname.size());
bool found = false;
for(size_t id = 0; id < out.size(); id++)
if(out[id] == name)
{
found = true;
break;
}
if(!found) out.emplace_back(std::move(name));
}
return out;
}
Error ZarrFunctions::AddVar(const MString& name, const Json::Value& zattrs, const Json::Value& zarray)
{
static const MString pref = "Zarr::AddVar";
VarType newtype;
Variable::FillType fill;
// Checks for parameters in zarray
{
const auto& cid = zarray["compressor"]["id"];
if(!cid || cid.type() != Json::stringValue || cid.asString() != "blosc") return {pref, "Unsupported compressor: " + MString(cid.asString().c_str())};
}
{
const auto& zf = zarray["zarr_format"];
if(!zf || (zf.type() != Json::uintValue && zf.type() != Json::intValue) || zf.asUInt() != 2) return {pref, "Unsupported format version: " + MString(zf.asUInt())};
}
{
const auto& ord = zarray["order"];
if(!ord || ord.type() != Json::stringValue || ord.asString() != "C") return {pref, "Order in not C"};
}
{
const auto& f = zarray["filters"];
if(f.type() != Json::nullValue) return {pref, "Filters is not null"};
}
// Read dtype
{
const auto& dtype = zarray["dtype"];
if(!dtype || dtype.type() != Json::stringValue) return {pref, "No datatype"};
const auto str = dtype.asString();
if(str == "<f4")
newtype = VarType::FLOAT;
else if(str == "<f8")
newtype = VarType::DOUBLE;
else if(str == "|i1")
newtype = VarType::INT1;
else if(str == "|u1")
newtype = VarType::UINT1;
else if(str == "<i2")
newtype = VarType::INT2;
else if(str == "<i4")
newtype = VarType::INT4;
else if(str == "<i8")
newtype = VarType::INT8;
else
return {pref, "Unsupported datatype: " + MString(str.c_str())};
}
// Read fill_value
{
const auto& fillval = zarray["fill_value"];
if(!fillval) // return {pref, "No fillval"};
fill = 0;
else if(fillval.type() == Json::uintValue)
fill = fillval.asUInt64();
else if(fillval.type() == Json::intValue)
fill = fillval.asInt64();
else if(fillval.type() == Json::realValue)
fill = fillval.asDouble();
else if(fillval.type() == Json::stringValue && fillval.asString() == "NaN")
fill = NAN;
}
// Read attributes
auto atts = ReadAtts(zattrs);
std::vector<MString> dnames;
std::vector<size_t> dsizes;
std::vector<size_t> csizes;
std::vector<size_t> dids;
// Read dimensions names
{
const auto& arrdim = zattrs["_ARRAY_DIMENSIONS"];
if(!(arrdim && arrdim.type() == Json::arrayValue)) return {pref, "_ARRAY_DIMENSIONS not found"};
for(Json::ArrayIndex i = 0; i < arrdim.size(); i++)
if(const auto& dim = arrdim[i]; dim.type() == Json::stringValue)
{
const auto val = dim.asString();
dnames.emplace_back(val.c_str(), val.size());
}
}
// Read dimensions sizes
{
const auto& shape = zarray["shape"];
if(!(shape && shape.type() == Json::arrayValue)) return {pref, "shape not found"};
for(Json::ArrayIndex i = 0; i < shape.size(); i++)
if(const auto& s = shape[i]; s.type() == Json::uintValue || s.type() == Json::intValue) dsizes.push_back(s.asUInt());
}
// Read chunk sizes
{
const auto& chunk = zarray["chunks"];
if(!(chunk && chunk.type() == Json::arrayValue)) return {pref, "chunks not found"};
for(Json::ArrayIndex i = 0; i < chunk.size(); i++)
if(const auto& c = chunk[i]; c.type() == Json::uintValue || c.type() == Json::intValue) csizes.push_back(c.asUInt());
}
if(dnames.size() != dsizes.size() || dnames.size() != csizes.size()) return {pref, "shape and chunks are in contradiction"};
dids.resize(dnames.size());
// Check dimensions names and sizes
for(size_t i = 0; i < dnames.size(); i++)
{
bool found = false;
for(size_t id = 0; id < dims.size(); id++)
if(dims[id].Name() == dnames[i])
{
found = true;
if(dims[id].Size() != dsizes[i])
return {pref, "According to previous data, the dimension " + dnames[i] + " has a size of " + dims[id].Size() + ", but here it is defined as " + dsizes[i]};
dids[i] = id;
break;
}
if(!found)
{
dids[i] = dims.size();
dims.emplace_back(dnames[i], dsizes[i]);
}
}
vars.emplace_back(name, newtype, std::move(dids), std::move(atts), fill);
chunks.push_back(std::move(csizes));
return Error();
}
Error ZarrFunctions::GetChunk(const MString& var, const std::vector<size_t>& chunkind, size_t chunksize, size_t elsize, void* data, const void* fill) const
{
static const MString pref = "Zarr::GetChunk";
MString str = url + "/" + var + "/";
for(size_t i = 0; i < chunkind.size(); i++) str += (i == 0 ? "" : ".") + MString(chunkind[i]);
auto [content, suc] = cache->Get(str);
if(!suc)
{
michlib::message(str + " not found in cache, downloading");
CURLRAII myhandle; // TODO: remove this workaround of unknown bug
if(proxyurl.Exist()) curl_easy_setopt(myhandle, CURLOPT_PROXY, proxyurl.Buf());
//auto [out, res] = GetUrl(chandle, str);
auto [out, res] = GetUrl(myhandle, str);
if(res != CURLE_OK) return Error(pref, MString("can't download chunk: ") + chandle.Err());
long respcode;
//curl_easy_getinfo(chandle, CURLINFO_RESPONSE_CODE, &respcode);
curl_easy_getinfo(myhandle, CURLINFO_RESPONSE_CODE, &respcode);
if(respcode == 403) out = ""; // Failed chunk download mean that this chunk contains only fill
cache->Put(str, out, 3600);
content = std::move(out);
}
if(content.Exist())
{
size_t nb, cb, bs;
blosc_cbuffer_sizes(content.Buf(), &nb, &cb, &bs);
if(cb != content.Len()) return Error(pref, MString("bytes download: ") + content.Len() + ", but compressed bytes " + cb);
if(nb != chunksize * elsize) return Error(pref, MString("decompressed bytes: ") + nb + ", but buffer size " + chunksize * elsize);
auto res = blosc_decompress_ctx(content.Buf(), data, chunksize * elsize, 1);
if(int_cast<size_t>(res) != chunksize * elsize) return Error(pref, MString("decompress only ") + res + " bytes of " + chunksize * elsize);
}
else
{
if(fill == nullptr) return Error(pref, MString("can't download chunk: ") + chandle.Err());
for(size_t i = 0; i < chunksize; i++) memcpy(michlib::P1(data) + i * elsize, fill, elsize);
}
return Error();
}
Error ZarrFunctions::Open(const MString& product, const MString& dataset, bool time)
{
static const MString pref = "Zarr::Open";
gats.clear();
dims.clear();
vars.clear();
CopernicusCatalog cat;
Json::Value json;
MString realdataset;
if(!dataset.Exist())
{
auto dsets = cat.DatasetList(product);
if(!dsets) return dsets.Add(pref, "Can't get default dataset of product " + product);
realdataset = dsets.Value()[0];
}
else
realdataset = dataset;
{
auto urlret = time ? cat.DatasetTimeURL(product, realdataset) : cat.DatasetGeoURL(product, realdataset);
if(!urlret) return urlret.Add(pref, "Can't get url for the dataset " + realdataset + " of product " + product);
url = urlret.Value();
auto ret = cat.GetJSON(url + "/.zmetadata");
if(ret)
json = ret.Value();
else
return ret.Add(pref, "can't download .zmetadata");
}
const auto& meta = json["metadata"];
if(!meta) return {pref, "No \"metadata\" key in JSON data"};
if(meta[".zattrs"]) gats = ReadAtts(meta[".zattrs"]);
auto vnames = ReadVarNames(meta);
for(size_t i = 0; i < vnames.size(); i++)
{
auto err = AddVar(vnames[i], meta[(vnames[i] + "/.zattrs").Buf()], meta[(vnames[i] + "/.zarray").Buf()]);
if(!err) return err.Add(pref, "Can't init variable " + vnames[i]);
}
return Error();
}
Loading…
Cancel
Save