1 /*
2   This file is part of CDO. CDO is a collection of Operators to manipulate and analyse Climate model Data.
3 
4   Author: Uwe Schulzweida
5 
6 */
7 
8 #include <algorithm>  // sort
9 
10 #include <cdi.h>
11 
12 #include "cdo_rlimit.h"
13 #include "process_int.h"
14 #include "cdo_vlist.h"
15 #include "param_conversion.h"
16 #include <mpim_grid.h>
17 #include "util_files.h"
18 #include "cdo_options.h"
19 #include "cdi_lockedIO.h"
20 
21 static int globalGridType = CDI_UNDEFID;
22 
23 struct GridInfo2
24 {
25   int globalIndicesID;
26   bool needed;
27 };
28 
29 struct CollgridInfo
30 {
31   CdoStreamID streamID;
32   int vlistID;
33   VarList varList;
34   Field field;
35   std::vector<std::vector<long>> cellIndex;
36 };
37 
38 struct xyinfoType
39 {
40   double x = 0.0, y = 0.0;
41   int id = -1;
42 };
43 
44 static bool
cmpx(const xyinfoType & a,const xyinfoType & b)45 cmpx(const xyinfoType &a, const xyinfoType &b)
46 {
47   return (a.x < b.x);
48 }
49 
50 static bool
cmpxy_lt(const xyinfoType & a,const xyinfoType & b)51 cmpxy_lt(const xyinfoType &a, const xyinfoType &b)
52 {
53   return (a.y < b.y || (std::fabs(a.y - b.y) <= 0 && a.x < b.x));
54 }
55 
56 static bool
cmpxy_gt(const xyinfoType & a,const xyinfoType & b)57 cmpxy_gt(const xyinfoType &a, const xyinfoType &b)
58 {
59   return (a.y > b.y || (std::fabs(a.y - b.y) <= 0 && a.x < b.x));
60 }
61 
62 static int
gen_coll_grid(int ngrids,int nfiles,std::vector<CollgridInfo> & collgridInfo,int gindex,long nxblocks)63 gen_coll_grid(int ngrids, int nfiles, std::vector<CollgridInfo> &collgridInfo, int gindex, long nxblocks)
64 {
65   auto lsouthnorth = true;
66   auto lregular = false;
67   auto lcurvilinear = false;
68 
69   long nx = (nxblocks != -1) ? nxblocks : -1;
70 
71   auto gridID = vlistGrid(collgridInfo[0].vlistID, gindex);
72   const auto gridtype0 = (globalGridType != CDI_UNDEFID) ? globalGridType : gridInqType(gridID);
73   if (ngrids > 1 && gridtype0 == GRID_GENERIC && gridInqXsize(gridID) == 0 && gridInqYsize(gridID) == 0) return -1;
74 
75   auto lunstructured = (gridtype0 == GRID_UNSTRUCTURED);
76   const auto nv = lunstructured ? gridInqNvertex(gridID) : 0;
77   const auto lcenter = (globalGridType == CDI_UNDEFID && gridHasCoordinates(gridID));
78   const auto lbounds = (lunstructured && globalGridType == CDI_UNDEFID && gridHasBounds(gridID));
79 
80   std::vector<xyinfoType> xyinfo(nfiles);
81   std::vector<long> xsize(nfiles), ysize(nfiles);
82   Varray2D<double> xvals(nfiles), yvals(nfiles);
83   Varray2D<double> xbounds(nfiles), ybounds(nfiles);
84 
85   for (int fileID = 0; fileID < nfiles; fileID++)
86     {
87       gridID = vlistGrid(collgridInfo[fileID].vlistID, gindex);
88       const auto gridtype = (globalGridType != CDI_UNDEFID) ? globalGridType : gridInqType(gridID);
89       if (gridtype == GRID_LONLAT || gridtype == GRID_GAUSSIAN || gridtype == GRID_PROJECTION)
90         lregular = true;
91       else if (gridtype == GRID_CURVILINEAR)
92         lcurvilinear = true;
93       else if (gridtype == GRID_UNSTRUCTURED)
94         lunstructured = true;
95       else if (gridtype == GRID_GENERIC /*&& gridInqXsize(gridID) > 0 && gridInqYsize(gridID) > 0*/)
96         ;
97       else
98         cdo_abort("Unsupported grid type: %s!", gridNamePtr(gridtype));
99 
100       xsize[fileID] = lunstructured ? gridInqSize(gridID) : gridInqXsize(gridID);
101       ysize[fileID] = lunstructured ? 1 : gridInqYsize(gridID);
102       if (xsize[fileID] == 0) xsize[fileID] = 1;
103       if (ysize[fileID] == 0) ysize[fileID] = 1;
104 
105       if (lregular)
106         {
107           xvals[fileID].resize(xsize[fileID]);
108           yvals[fileID].resize(ysize[fileID]);
109         }
110       else if (lcurvilinear || lunstructured)
111         {
112           if (lcenter) xvals[fileID].resize(xsize[fileID] * ysize[fileID]);
113           if (lcenter) yvals[fileID].resize(xsize[fileID] * ysize[fileID]);
114           if (lbounds) xbounds[fileID].resize(nv * xsize[fileID] * ysize[fileID]);
115           if (lbounds) ybounds[fileID].resize(nv * xsize[fileID] * ysize[fileID]);
116         }
117 
118       if (lregular || lcurvilinear || lunstructured)
119         {
120           if (lcenter) gridInqXvals(gridID, xvals[fileID].data());
121           if (lcenter) gridInqYvals(gridID, yvals[fileID].data());
122           if (lbounds) gridInqXbounds(gridID, xbounds[fileID].data());
123           if (lbounds) gridInqYbounds(gridID, ybounds[fileID].data());
124         }
125       // printf("fileID %d, gridID %d\n", fileID, gridID);
126 
127       xyinfo[fileID].id = fileID;
128       if (lregular)
129         {
130           xyinfo[fileID].x = xvals[fileID][0];
131           xyinfo[fileID].y = yvals[fileID][0];
132           if (ysize[fileID] > 1 && yvals[fileID][0] > yvals[fileID][ysize[fileID] - 1]) lsouthnorth = false;
133         }
134     }
135 
136   if (Options::cdoVerbose && lregular)
137     for (int fileID = 0; fileID < nfiles; fileID++) printf("1 %d %g %g \n", xyinfo[fileID].id, xyinfo[fileID].x, xyinfo[fileID].y);
138 
139   if (lregular)
140     {
141       std::sort(xyinfo.begin(), xyinfo.end(), cmpx);
142 
143       if (Options::cdoVerbose)
144         for (int fileID = 0; fileID < nfiles; fileID++)
145           printf("2 %d %g %g \n", xyinfo[fileID].id, xyinfo[fileID].x, xyinfo[fileID].y);
146 
147       auto cmpxy = lsouthnorth ? cmpxy_lt : cmpxy_gt;
148       std::sort(xyinfo.begin(), xyinfo.end(), cmpxy);
149 
150       if (Options::cdoVerbose)
151         for (int fileID = 0; fileID < nfiles; fileID++)
152           printf("3 %d %g %g \n", xyinfo[fileID].id, xyinfo[fileID].x, xyinfo[fileID].y);
153 
154       if (nx <= 0)
155         {
156           nx = 1;
157           for (int fileID = 1; fileID < nfiles; fileID++)
158             {
159               if (DBL_IS_EQUAL(xyinfo[0].y, xyinfo[fileID].y))
160                 nx++;
161               else
162                 break;
163             }
164         }
165     }
166   else
167     {
168       if (nx <= 0) nx = nfiles;
169     }
170 
171   const long ny = nfiles / nx;
172   if (nx * ny != nfiles) cdo_abort("Number of input files (%ld) and number of blocks (%ldx%ld) differ!", nfiles, nx, ny);
173 
174   long xsize2 = 0;
175   for (long i = 0; i < nx; ++i) xsize2 += xsize[xyinfo[i].id];
176   long ysize2 = 0;
177   for (long j = 0; j < ny; ++j) ysize2 += ysize[xyinfo[j * nx].id];
178   if (Options::cdoVerbose) cdo_print("xsize2 %ld  ysize2 %ld", xsize2, ysize2);
179 
180   {  // verify size of data
181     const long xs = xsize[xyinfo[0].id];
182     for (long j = 1; j < ny; ++j)
183       if (xsize[xyinfo[j * nx].id] != xs) cdo_abort("xsize=%ld differ from first file (xsize=%ld)!", xsize[xyinfo[j * nx].id], xs);
184     const long ys = ysize[xyinfo[0].id];
185     for (long i = 1; i < nx; ++i)
186       if (ysize[xyinfo[i].id] != ys) cdo_abort("ysize=%ld differ from first file (ysize=%ld)!", ysize[xyinfo[i].id], ys);
187   }
188 
189   Varray<double> xvals2, yvals2;
190   Varray<double> xbounds2, ybounds2;
191   if (lregular)
192     {
193       xvals2.resize(xsize2);
194       yvals2.resize(ysize2);
195     }
196   else if (lcurvilinear || lunstructured)
197     {
198       if (lcenter) xvals2.resize(xsize2 * ysize2);
199       if (lcenter) yvals2.resize(xsize2 * ysize2);
200       if (lbounds) xbounds2.resize(nv * xsize2 * ysize2);
201       if (lbounds) ybounds2.resize(nv * xsize2 * ysize2);
202     }
203 
204   std::vector<long> xoff(nx + 1), yoff(ny + 1);
205 
206   xoff[0] = 0;
207   for (long i = 0; i < nx; ++i)
208     {
209       const long idx = xyinfo[i].id;
210       if (lregular) array_copy(xsize[idx], xvals[idx].data(), &xvals2[xoff[i]]);
211       xoff[i + 1] = xoff[i] + xsize[idx];
212     }
213 
214   yoff[0] = 0;
215   for (long j = 0; j < ny; ++j)
216     {
217       const long idx = xyinfo[j * nx].id;
218       if (lregular) array_copy(ysize[idx], yvals[idx].data(), &yvals2[yoff[j]]);
219       yoff[j + 1] = yoff[j] + ysize[idx];
220     }
221 
222   for (int fileID = 0; fileID < nfiles; fileID++)
223     {
224       const long idx = xyinfo[fileID].id;
225       const long iy = fileID / nx;
226       const long ix = fileID - iy * nx;
227 
228       const long offset = yoff[iy] * xsize2 + xoff[ix];
229       // printf("fileID %d %d, iy %d, ix %d, offset %d\n", fileID, xyinfo[fileID].id, iy, ix, offset);
230 
231       long ij = 0;
232       for (long j = 0; j < ysize[idx]; ++j)
233         for (long i = 0; i < xsize[idx]; ++i)
234           {
235             if (lcurvilinear || lunstructured)
236               {
237                 if (lcenter) xvals2[offset + j * xsize2 + i] = xvals[idx][ij];
238                 if (lcenter) yvals2[offset + j * xsize2 + i] = yvals[idx][ij];
239                 if (lbounds) for (long k = 0; k < nv; ++k) xbounds2[(offset + j * xsize2 + i) * nv + k] = xbounds[idx][ij * nv + k];
240                 if (lbounds) for (long k = 0; k < nv; ++k) ybounds2[(offset + j * xsize2 + i) * nv + k] = ybounds[idx][ij * nv + k];
241               }
242             collgridInfo[idx].cellIndex[gindex][ij++] = offset + j * xsize2 + i;
243           }
244     }
245 
246   const auto gridID2 = gridCreate(gridtype0, xsize2 * ysize2);
247   if (!lunstructured)
248     {
249       gridDefXsize(gridID2, xsize2);
250       gridDefYsize(gridID2, ysize2);
251     }
252   else if (nv > 0)
253     {
254       gridDefNvertex(gridID2, nv);
255     }
256 
257   if (lregular || lcurvilinear || lunstructured)
258     {
259       if (lcenter) gridDefXvals(gridID2, xvals2.data());
260       if (lcenter) gridDefYvals(gridID2, yvals2.data());
261       if (lbounds) gridDefXbounds(gridID2, xbounds2.data());
262       if (lbounds) gridDefYbounds(gridID2, ybounds2.data());
263     }
264 
265   gridID = vlistGrid(collgridInfo[0].vlistID, gindex);
266 
267   grid_copy_keys(gridID, gridID2);
268 
269   if (gridtype0 == GRID_PROJECTION) grid_copy_mapping(gridID, gridID2);
270 
271   return gridID2;
272 }
273 /*
274 static void
275 coll_cells_reg2d(const Field &field1, Field &field2, const CollgridInfo &collgridInfo, size_t nlon)
276 {
277   const auto nx = collgridInfo.nx;
278   const auto ny = collgridInfo.ny;
279 
280   for (size_t j = 0; j < ny; ++j)
281     {
282       const auto offset1 = j * nx;
283       const auto offset2 = collgridInfo.offset + j * nlon;
284 
285       if (field1.memType == MemType::Float)
286         for (size_t i = 0; i < nx; ++i) field2.vec_f[offset2 + i] = field1.vec_f[offset1 + i];
287       else
288         for (size_t i = 0; i < nx; ++i) field2.vec_d[offset2 + i] = field1.vec_d[offset1 + i];
289     }
290 }
291 */
292 static void
collect_cells(const Field & field1,Field & field2,const std::vector<long> & cellIndex)293 collect_cells(const Field &field1, Field &field2, const std::vector<long> &cellIndex)
294 {
295   const auto patchSize = field1.size;
296 
297   if (field1.memType == MemType::Float)
298     for (size_t i = 0; i < patchSize; ++i) field2.vec_f[cellIndex[i]] = field1.vec_f[i];
299   else
300     for (size_t i = 0; i < patchSize; ++i) field2.vec_d[cellIndex[i]] = field1.vec_d[i];
301 }
302 
303 static std::vector<int>
get_var_gridindex(int vlistID)304 get_var_gridindex(int vlistID)
305 {
306   const auto nvars = vlistNvars(vlistID);
307   const auto ngrids = vlistNgrids(vlistID);
308 
309   std::vector<int> varGridIndex(nvars, 0);
310   for (int varID = 0; varID < nvars; ++varID)
311     {
312       const auto gridID = vlistInqVarGrid(vlistID, varID);
313       for (int index = 0; index < ngrids; ++index)
314         {
315           if (gridID == vlistGrid(vlistID, index))
316             {
317               varGridIndex[varID] = index;
318               break;
319             }
320         }
321     }
322 
323   return varGridIndex;
324 }
325 
326 static std::vector<GridInfo2>
get_gridinfo(int vlistID,const VarList & varlist,const std::vector<int> & varGridIndex,std::vector<bool> & selectedVars)327 get_gridinfo(int vlistID, const VarList &varlist, const std::vector<int> &varGridIndex, std::vector<bool> &selectedVars)
328 {
329   const auto nvars = vlistNvars(vlistID);
330   const auto ngrids = vlistNgrids(vlistID);
331 
332   std::vector<GridInfo2> gridInfo(ngrids);
333   for (int index = 0; index < ngrids; ++index)
334     {
335       gridInfo[index].globalIndicesID = -1;
336       gridInfo[index].needed = false;
337     }
338 
339   int globalCellIndicesID = -1;
340   int globalVertIndicesID = -1;
341   int globalEdgeIndicesID = -1;
342   for (int varID = 0; varID < nvars; ++varID)
343     {
344       if      (strcmp(varlist[varID].name, "global_cell_indices") == 0) globalCellIndicesID = varID;
345       else if (strcmp(varlist[varID].name, "global_vert_indices") == 0) globalVertIndicesID = varID;
346       else if (strcmp(varlist[varID].name, "global_edge_indices") == 0) globalEdgeIndicesID = varID;
347     }
348   if (globalCellIndicesID != -1) selectedVars[globalCellIndicesID] = false;
349   if (globalVertIndicesID != -1) selectedVars[globalVertIndicesID] = false;
350   if (globalEdgeIndicesID != -1) selectedVars[globalEdgeIndicesID] = false;
351 
352   if (globalCellIndicesID != -1) gridInfo[varGridIndex[globalCellIndicesID]].globalIndicesID = globalCellIndicesID;
353   if (globalVertIndicesID != -1) gridInfo[varGridIndex[globalVertIndicesID]].globalIndicesID = globalVertIndicesID;
354   if (globalEdgeIndicesID != -1) gridInfo[varGridIndex[globalEdgeIndicesID]].globalIndicesID = globalEdgeIndicesID;
355 
356   for (int varID = 0; varID < nvars; ++varID)
357     if (selectedVars[varID]) gridInfo[varGridIndex[varID]].needed = true;
358 
359   return gridInfo;
360 }
361 
362 static std::vector<bool>
get_selected_vars(int nsel,int noff,int vlistID1,const VarList & varList1)363 get_selected_vars(int nsel, int noff, int vlistID1, const VarList &varList1)
364 {
365   const auto nvars = vlistNvars(vlistID1);
366   std::vector<bool> selectedVars(nvars, false);
367   if (nsel == 0)
368     {
369       for (int varID = 0; varID < nvars; varID++) selectedVars[varID] = true;
370     }
371   else
372     {
373       if (Options::cdoVerbose)
374         for (int i = 0; i < nsel; i++) cdo_print("name %d = %s", i + 1, cdo_operator_argv(noff + i).c_str());
375 
376       std::vector<bool> selfound(nsel);
377       for (int i = 0; i < nsel; i++) selfound[i] = false;
378 
379       for (int varID = 0; varID < nvars; varID++)
380         {
381           for (int isel = 0; isel < nsel; isel++)
382             {
383               if (cdo_operator_argv(noff + isel) == varList1[varID].name)
384                 {
385                   selfound[isel] = true;
386                   selectedVars[varID] = true;
387                 }
388             }
389         }
390 
391       int err = 0;
392       for (int isel = 0; isel < nsel; isel++)
393         {
394           if (selfound[isel] == false)
395             {
396               err++;
397               cdo_warning("Variable name %s not found!", cdo_operator_argv(noff + isel).c_str());
398             }
399         }
400       if (err) cdo_abort("Could not find all requested variables: (%d/%d)", nsel - err, nsel);
401     }
402 
403   return selectedVars;
404 }
405 
406 static void
select_vars(const std::vector<bool> & selectedVars,int vlistID1,const VarList & varList1)407 select_vars(const std::vector<bool> &selectedVars, int vlistID1, const VarList &varList1)
408 {
409   const auto nvars = vlistNvars(vlistID1);
410   int numVars = 0;
411 
412   for (int varID = 0; varID < nvars; varID++)
413     {
414       if (selectedVars[varID])
415         {
416           numVars++;
417           const auto nlevels = varList1[varID].nlevels;
418           for (int levID = 0; levID < nlevels; levID++) vlistDefFlag(vlistID1, varID, levID, true);
419         }
420     }
421 
422   if (numVars == 0) cdo_abort("No variables selected!");
423 }
424 
425 void *
Collgrid(void * process)426 Collgrid(void *process)
427 {
428   int nxblocks = -1;
429 
430   cdo_initialize(process);
431 
432   const auto nfiles = cdo_stream_cnt() - 1;
433   const auto ofilename = cdo_get_stream_name(nfiles);
434 
435   if (!Options::cdoOverwriteMode && FileUtils::file_exists(ofilename) && !FileUtils::user_file_overwrite(ofilename))
436     cdo_abort("Outputfile %s already exists!", ofilename);
437 
438   std::vector<CollgridInfo> collgridInfo(nfiles);
439 
440   cdo::set_numfiles(nfiles + 8);
441 
442   for (int fileID = 0; fileID < nfiles; fileID++)
443     {
444       const auto streamID = cdo_open_read(fileID);
445       const auto vlistID = cdo_stream_inq_vlist(streamID);
446       collgridInfo[fileID].streamID = streamID;
447       collgridInfo[fileID].vlistID = vlistID;
448       varListInit(collgridInfo[fileID].varList, vlistID);
449     }
450 
451   const auto &varList1 = collgridInfo[0].varList;
452   const auto vlistID1 = collgridInfo[0].vlistID;
453   vlistClearFlag(vlistID1);
454 
455   // check that the contents is always the same
456   for (int fileID = 1; fileID < nfiles; fileID++) vlist_compare(vlistID1, collgridInfo[fileID].vlistID, CMP_NAME | CMP_NLEVEL);
457 
458   auto nsel = cdo_operator_argc();
459   int noff = 0;
460 
461   if (nsel > 0)
462     {
463       auto argument = cdo_operator_argv(0).c_str();
464       if (strcmp(argument, "gridtype=unstructured") == 0)
465         {
466           nsel--;
467           noff++;
468           globalGridType = GRID_UNSTRUCTURED;
469         }
470       else
471         {
472           auto len = (int) strlen(argument);
473           while (--len >= 0 && isdigit(argument[len]))
474             ;
475 
476           if (len == -1)
477             {
478               nsel--;
479               noff++;
480               nxblocks = parameter_to_int(argument);
481             }
482         }
483     }
484 
485   auto selectedVars = get_selected_vars(nsel, noff, vlistID1, varList1);
486   const auto varGridIndex = get_var_gridindex(vlistID1);
487   const auto gridInfo = get_gridinfo(vlistID1, varList1, varGridIndex, selectedVars);
488   select_vars(selectedVars, vlistID1, varList1);
489 
490   const auto vlistID2 = vlistCreate();
491   cdo_vlist_copy_flag(vlistID2, vlistID1);
492   vlistDefNtsteps(vlistID2, vlistNtsteps(vlistID1));
493 
494   const auto taxisID1 = vlistInqTaxis(vlistID1);
495   const auto taxisID2 = taxisDuplicate(taxisID1);
496   vlistDefTaxis(vlistID2, taxisID2);
497 
498   // if ( Options::cdoVerbose ) vlistPrint(vlistID1);
499   // if ( Options::cdoVerbose ) vlistPrint(vlistID2);
500 
501   const auto ngrids1 = vlistNgrids(vlistID1);
502   const auto ngrids2 = vlistNgrids(vlistID2);
503 
504   for (int fileID = 0; fileID < nfiles; fileID++)
505     {
506       collgridInfo[fileID].cellIndex.resize(ngrids1);
507       for (int gindex = 0; gindex < ngrids1; ++gindex)
508         {
509           if (gridInfo[gindex].needed)
510             {
511               const auto patchSize = gridInqSize(vlistGrid(collgridInfo[fileID].vlistID, gindex));
512               collgridInfo[fileID].cellIndex[gindex].resize(patchSize);
513             }
514         }
515     }
516 
517   std::vector<int> gridID2s(ngrids2);
518 
519   for (int i2 = 0; i2 < ngrids2; ++i2)
520     {
521       int i1;
522       for (i1 = 0; i1 < ngrids1; ++i1)
523         if (vlistGrid(vlistID1, i1) == vlistGrid(vlistID2, i2)) break;
524 
525       gridID2s[i2] = gen_coll_grid(ngrids2, nfiles, collgridInfo, i1, nxblocks);
526     }
527 
528   for (int i = 0; i < ngrids2; ++i)
529     {
530       if (gridID2s[i] != -1) vlistChangeGridIndex(vlistID2, i, gridID2s[i]);
531     }
532 
533   VarList varList2;
534   varListInit(varList2, vlistID2);
535 
536   const auto nvars2 = vlistNvars(vlistID2);
537   std::vector<bool> collectVars2(nvars2, false);
538   for (int varID = 0; varID < nvars2; varID++)
539     {
540       const auto gridID = varList2[varID].gridID;
541       for (int i = 0; i < ngrids2; ++i)
542         {
543           if (gridID2s[i] != -1 && gridID == vlistGrid(vlistID2, i))
544             {
545               collectVars2[varID] = true;
546               break;
547             }
548         }
549     }
550 
551   const auto streamID2 = cdo_open_write(nfiles);
552   cdo_def_vlist(streamID2, vlistID2);
553 
554   Field field2;
555 
556   int nrecs0 = 0;
557   int tsID = 0;
558   do
559     {
560       nrecs0 = cdo_stream_inq_timestep(collgridInfo[0].streamID, tsID);
561       for (int fileID = 1; fileID < nfiles; fileID++)
562         {
563           const auto nrecs = cdo_stream_inq_timestep(collgridInfo[fileID].streamID, tsID);
564           if (nrecs != nrecs0)
565             cdo_abort("Number of records at time step %d of %s and %s differ!", tsID + 1, cdo_get_stream_name(0),
566                       cdo_get_stream_name(fileID));
567         }
568 
569       cdo_taxis_copy_timestep(taxisID2, taxisID1);
570 
571       if (nrecs0 > 0) cdo_def_timestep(streamID2, tsID);
572 
573       for (int recID = 0; recID < nrecs0; recID++)
574         {
575           int varID = 0, levelID = 0;
576           for (int fileID = nfiles - 1; fileID >= 0; fileID--)
577             cdo_inq_record(collgridInfo[fileID].streamID, &varID, &levelID);
578 
579           // if (Options::cdoVerbose && tsID == 0) printf(" tsID, recID, varID, levelID %d %d %d %d\n", tsID, recID, varID, levelID);
580 
581           const auto gindex = varGridIndex[varID];
582           if (gridInfo[gindex].needed && gridInfo[gindex].globalIndicesID == varID)
583             {
584               Varray<double> cellIndex;
585               for (int fileID = 0; fileID < nfiles; fileID++)
586                 {
587                   const auto patchSize = collgridInfo[fileID].varList[varID].gridsize;
588                   if (cellIndex.size() < patchSize) cellIndex.resize(patchSize);
589                   size_t nmiss;
590                   cdo_read_record(collgridInfo[fileID].streamID, cellIndex.data(), &nmiss);
591                   for (size_t i = 0; i < patchSize; ++i)
592                     collgridInfo[fileID].cellIndex[gindex][i] = std::lround(cellIndex[i]) - 1;
593                 }
594             }
595 
596           if (vlistInqFlag(vlistID1, varID, levelID) == true)
597             {
598               const auto varID2 = vlistFindVar(vlistID2, varID);
599               const auto levelID2 = vlistFindLevel(vlistID2, varID, levelID);
600               //if (Options::cdoVerbose && tsID == 0) printf("varID %d %d levelID %d %d\n", varID, varID2, levelID, levelID2);
601 
602               field2.init(varList2[varID2]);
603 
604               const auto gridsize = field2.size;
605               const auto missval = field2.missval;
606               if (field2.memType == MemType::Float)
607                 for (size_t i = 0; i < gridsize; i++) field2.vec_f[i] = missval;
608               else
609                 for (size_t i = 0; i < gridsize; i++) field2.vec_d[i] = missval;
610 
611 #ifdef _OPENMP
612 #pragma omp parallel for default(shared)
613 #endif
614               for (int fileID = 0; fileID < nfiles; fileID++)
615                 {
616                   auto &field1 = collgridInfo[fileID].field;
617                   field1.init(collgridInfo[fileID].varList[varID]);
618                   cdo_read_record(collgridInfo[fileID].streamID, field1);
619 
620                   if (collectVars2[varID2]) collect_cells(field1, field2, collgridInfo[fileID].cellIndex[gindex]);
621                 }
622 
623               cdo_def_record(streamID2, varID2, levelID2);
624 
625               if (collectVars2[varID2])
626                 {
627                   field_num_mv(field2);
628                   cdo_write_record(streamID2, field2);
629                 }
630               else
631                 {
632                   cdo_write_record(streamID2, collgridInfo[0].field);
633                 }
634             }
635         }
636 
637       tsID++;
638     }
639   while (nrecs0 > 0);
640 
641   for (int fileID = 0; fileID < nfiles; fileID++) cdo_stream_close(collgridInfo[fileID].streamID);
642 
643   cdo_stream_close(streamID2);
644 
645   cdo_finish();
646 
647   return nullptr;
648 }
649