Magvector.cc 13.8 KB
Newer Older
1
2
3
4
/*
  This file is part of CDO. CDO is a collection of Operators to
  manipulate and analyse Climate model Data.

Uwe Schulzweida's avatar
Uwe Schulzweida committed
5
  Copyright (C) 2003-2019 Uwe Schulzweida, <uwe.schulzweida AT mpimet.mpg.de>
6
7
8
9
10
11
12
13
14
15
16
  See COPYING file for copying and redistribution conditions.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
*/
17
18
#ifdef HAVE_CONFIG_H
#include "config.h" /* HAVE_LIBMAGICS */
Uwe Schulzweida's avatar
Uwe Schulzweida committed
19
20
21
#endif

#include <cdi.h>
Oliver Heidmann's avatar
Oliver Heidmann committed
22

23
#include "process_int.h"
24
#include <mpim_grid.h>
25
#include "dmemory.h"
Oliver Heidmann's avatar
Oliver Heidmann committed
26
#include "printinfo.h"
Uwe Schulzweida's avatar
Uwe Schulzweida committed
27

28
#ifdef HAVE_LIBMAGICS
Uwe Schulzweida's avatar
Uwe Schulzweida committed
29

30
#include <magics_api.h>
Uwe Schulzweida's avatar
Uwe Schulzweida committed
31
32
#include "magics_template_parser.h"
#include "results_template_parser.h"
33
#include "string_utilities.h"
Uwe Schulzweida's avatar
Uwe Schulzweida committed
34

35
36
#define DBG 0

Uwe Schulzweida's avatar
Uwe Schulzweida committed
37
int VECTOR, STREAM;
38
39
const char *vector_params[] = { "thin_fac", "unit_vec", "device", "step_freq" };
int vector_param_count = sizeof(vector_params) / sizeof(char *);
40
41
42

/* Default Magics Values */
double THIN_FAC = 2.0, UNIT_VEC = 25.0;
43
extern int ANIM_FLAG, STEP_FREQ;
44

Uwe Schulzweida's avatar
Uwe Schulzweida committed
45
extern int checkdevice(char *device_in);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
46

Uwe Schulzweida's avatar
Uwe Schulzweida committed
47
48
49
50
extern char *DEVICE;
extern char *DEVICE_TABLE;
extern int DEVICE_COUNT;

51
static void
Uwe Schulzweida's avatar
Uwe Schulzweida committed
52
magvector(const char *plotfile, int operatorID, const char *varname, long nlon, long nlat, double *grid_center_lon,
53
          double *grid_center_lat, double *uarray, double *varray, int nparam, std::vector<std::string> &params, char *datetime)
Uwe Schulzweida's avatar
Uwe Schulzweida committed
54

Uwe Schulzweida's avatar
Uwe Schulzweida committed
55
{
56
57
58
59
60
  long i;
  double dlon = 0, dlat = 0;
  int split_str_count;
  char plotfilename[4096];
  const char *sep_char = "=";
Uwe Schulzweida's avatar
Uwe Schulzweida committed
61
62
  char **split_str = nullptr;
  char *temp_str = nullptr;
63
64
65
66
  char *titlename;

  (void) varname;

Uwe Schulzweida's avatar
Uwe Schulzweida committed
67
  if (uarray == nullptr && varray == nullptr)
68
    {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
69
      fprintf(stderr, " No Velocity Components in input file, cannot creaate Vector PLOT!\n");
70
71
72
      return;
    }

Uwe Schulzweida's avatar
Uwe Schulzweida committed
73
  if (uarray == nullptr || varray == nullptr)
74
    {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
75
      fprintf(stderr, " Found only one Velocity Component in input file, cannot create Vector PLOT!\n");
76
77
78
79
80
      return;
    }

  if (DBG)
    {
81
      fprintf(stderr, "Num params %ld\n", params.size());
82

Uwe Schulzweida's avatar
Uwe Schulzweida committed
83
      for (i = 0; i < nparam; i++) fprintf(stderr, "Param %s\n", params[i].c_str());
84
85
86
87
88
89
90
      fflush(stderr);
    }

  for (i = 0; i < nparam; ++i)
    {
      split_str_count = 0;
      sep_char = "=";
91
      split_str_count = cstrSplitWithSeperator(params[i].c_str(), sep_char, &split_str);
92
93
      (void) split_str_count;

94
      if (cstrIsEqual(split_str[0], "thin_fac"))
95
96
97
98
99
        {
          THIN_FAC = atof(split_str[1]);
          if (DBG) fprintf(stderr, "THIN FACTOR %g\n", THIN_FAC);
        }

100
      if (cstrIsEqual(split_str[0], "unit_vec"))
101
102
103
104
105
        {
          UNIT_VEC = atof(split_str[1]);
          if (DBG) fprintf(stderr, "UNIT VECTOR %g\n", UNIT_VEC);
        }

106
      if (cstrIsEqual(split_str[0], "device"))
107
108
        {
          temp_str = strdup(split_str[1]);
109
          cstrToUpperCase(temp_str);
110
111
112
113
114
115
          DEVICE = temp_str;
          if (DBG) fprintf(stderr, "DEVICE %s\n", DEVICE);

          mag_setc("output_format", DEVICE);
        }

116
      if (cstrIsEqual(split_str[0], "step_freq"))
117
118
119
120
121
122
123
124
125
126
        {
          STEP_FREQ = atoi(split_str[1]);
          if (DBG) fprintf(stderr, "STEP FREQ %d\n", STEP_FREQ);
        }

      Free(split_str);
    }

  if (nlon > 1)
    {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
127
      for (i = 1; i < nlon; ++i) dlon += (grid_center_lon[i] - grid_center_lon[i - 1]);
128
129
130
131
132
      dlon /= (nlon - 1);
    }

  if (nlat > 1)
    {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
133
      for (i = 1; i < nlat; ++i) dlat += (grid_center_lat[nlon * i] - grid_center_lat[nlon * (i - 1)]);
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
      dlat /= (nlat - 1);
    }

  /* magics_template_parser( magics_node ); */

  /* results_template_parser(results_node, varname ); */

  sprintf(plotfilename, "Velocity Vectors %s", datetime);
  titlename = strdup(plotfilename);
  sprintf(plotfilename, "%s", plotfile);

  mag_setc("output_name", plotfilename);
  mag_new("page");

  /* Set the input data */
  mag_setr("input_field_initial_latitude", grid_center_lat[0]);
  mag_setr("input_field_latitude_step", dlat);

  mag_setr("input_field_initial_longitude", grid_center_lon[0]);
  mag_setr("input_field_longitude_step", dlon);

  mag_set2r("input_wind_u_component", uarray, nlon, nlat);
  mag_set2r("input_wind_v_component", varray, nlon, nlat);

  mag_seti("map_label_latitude_frequency", 2);
  mag_seti("map_label_longitude_frequency", 2);
  /*mag_setr ("map_label_height",0.5);*/
  mag_setr("map_label_height", 0.4);

  if (operatorID == VECTOR)
    {
      /* Magics functions for performing vector operation */
      /*
        mag_setc("wind_legend_only", "on" );
        mag_setc("wind_legend_text", "on" );
      */

      mag_setc("legend", "on");
      mag_setc("wind_flag_cross_boundary", "on");
      mag_seti("wind_arrow_thickness", 1);
      mag_coast();

Uwe Schulzweida's avatar
Uwe Schulzweida committed
176
      if (IS_NOT_EQUAL(THIN_FAC, 2.0f)) mag_setr("wind_thinning_factor", THIN_FAC);
177
178

      /*wind_arrow_unit_velocity */
Uwe Schulzweida's avatar
Uwe Schulzweida committed
179
      if (IS_NOT_EQUAL(UNIT_VEC, 25.0f)) mag_setr("wind_arrow_unit_velocity", UNIT_VEC);
180
181
182
183
184
185
186
187

      mag_wind();

      mag_set1c("text_lines", (const char **) &titlename, 1);
      mag_setc("text_colour", "black");
      mag_setc("text_justification", "centre");
      mag_text();
    }
Uwe Schulzweida's avatar
Uwe Schulzweida committed
188
189
}

190
191
static void
init_MAGICS()
Uwe Schulzweida's avatar
Uwe Schulzweida committed
192
193

{
Uwe Schulzweida's avatar
Uwe Schulzweida committed
194

195
  setenv("MAGPLUS_QUIET", "1", 1); /* To suppress magics messages */
196
197
  mag_open();

Uwe Schulzweida's avatar
Uwe Schulzweida committed
198
  /* Some standard parameters affectng the magics environment, moved from the xml file  ** begin ** */
199
  mag_setc("page_id_line", "off");
Uwe Schulzweida's avatar
Uwe Schulzweida committed
200
201
}

202
203
static void
quit_MAGICS()
Uwe Schulzweida's avatar
Uwe Schulzweida committed
204
{
205
206
  mag_close();
  if (DBG) fprintf(stdout, "Exiting From MAGICS\n");
Uwe Schulzweida's avatar
Uwe Schulzweida committed
207
208
}

209
static void
210
VerifyVectorParameters(int num_param, std::vector<std::string> &param_names, int opID)
Uwe Schulzweida's avatar
Uwe Schulzweida committed
211
{
212

Uwe Schulzweida's avatar
Uwe Schulzweida committed
213
  int i, j;
214
215
  bool found = false, syntax = true, halt_flag = false;
  int split_str_count;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
216
  int param_count = 0;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
217
218
  const char **params = nullptr;
  char **split_str = nullptr;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
219
220
  const char *sep_char = "=";

Uwe Schulzweida's avatar
Uwe Schulzweida committed
221
  // char  *vector_params[] = {"min","max","count","interval","list","colour","thickness","style","RGB"};
Uwe Schulzweida's avatar
Uwe Schulzweida committed
222

223
  for (i = 0; i < num_param; ++i)
Uwe Schulzweida's avatar
Uwe Schulzweida committed
224
225
    {
      split_str_count = 0;
226
227
      found = false;
      syntax = true;
228
      split_str_count = cstrSplitWithSeperator(param_names[i].c_str(), sep_char, &split_str);
229
230
231
232
233
234
235
236
237
238
239
240
241
242

      if (DBG) fprintf(stderr, "Verifying params!\n");

      if (split_str_count > 1)
        {

          if (opID == VECTOR)
            {
              param_count = vector_param_count;
              params = vector_params;
            }

          for (j = 0; j < param_count; ++j)
            {
243
              if (cstrIsEqual(split_str[0], params[j]))
244
                {
245
                  found = true;
246

Uwe Schulzweida's avatar
Uwe Schulzweida committed
247
248
                  if (cstrIsEqual(split_str[0], "thin_fac") || cstrIsEqual(split_str[0], "unit_vec")
                      || cstrIsEqual(split_str[0], "step_freq"))
249
                    {
250
                      if (!cstrIsNumeric(split_str[1])) syntax = false;
251
252
                    }

253
                  if (cstrIsEqual(split_str[0], "device"))
254
                    {
255
                      if (cstrIsNumeric(split_str[1]))
256
                        syntax = false;
257
258
                      else
                        {
259
                          if (cstrIsEqual(split_str[0], "device"))
260
                            {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
261
                              if (DBG) fprintf(stderr, "Parameter value '%s'\n", split_str[1]);
262
                              if (checkdevice(split_str[1])) syntax = false;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
263

Uwe Schulzweida's avatar
Uwe Schulzweida committed
264
                              /* Vector not supported in google earth format */
265
                              if (cstrIsEqual(split_str[1], "KML") || cstrIsEqual(split_str[1], "kml"))
Uwe Schulzweida's avatar
Uwe Schulzweida committed
266
                                {
267
                                  syntax = false;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
268
                                  if (DBG) fprintf(stderr, "Parameter value '%s'\n", split_str[1]);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
269
                                }
270
271
272
273
274
275
                            }
                        }
                    }
                }
            }
        }
Uwe Schulzweida's avatar
Uwe Schulzweida committed
276
      else
277
        {
278
          syntax = false;
279
280
        }

281
      if (!found)
282
        {
283
          halt_flag = true;
284
          fprintf(stderr, "Invalid parameter  '%s'\n", param_names[i].c_str());
285
        }
286
      if (found && !syntax)
287
        {
288
          halt_flag = true;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
289
          fprintf(stderr, "Invalid parameter specification  '%s'\n", param_names[i].c_str());
290
291
292
        }

      if (split_str) Free(split_str);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
293
    }
294

295
  if (halt_flag) exit(0);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
296
297
298
}
#endif

299
300
void *
Magvector(void *process)
Uwe Schulzweida's avatar
Uwe Schulzweida committed
301
{
302
  cdoInitialize(process);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
303

304
#ifdef HAVE_LIBMAGICS
Uwe Schulzweida's avatar
Uwe Schulzweida committed
305
306
  int nrecs;
  int levelID;
307
  size_t nmiss;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
308
309
  char varname[CDI_MAX_NAME];
  char units[CDI_MAX_NAME];
310
  char datetimestr[64];
Uwe Schulzweida's avatar
Uwe Schulzweida committed
311

Uwe Schulzweida's avatar
Uwe Schulzweida committed
312
  int nparam = operatorArgc();
313
  std::vector<std::string> &pnames = cdoGetOperArgv();
314

Uwe Schulzweida's avatar
Uwe Schulzweida committed
315
316
  int VECTOR = cdoOperatorAdd("vector", 0, 0, nullptr);
  int STREAM = cdoOperatorAdd("stream", 0, 0, nullptr);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
317

Uwe Schulzweida's avatar
Uwe Schulzweida committed
318
  int operatorID = cdoOperatorID();
319
320

  if (nparam)
321
    {
322
323
      if (DBG)
        {
324
          for (int i = 0; i < nparam; i++) fprintf(stderr, "Param %d is %s!\n", i + 1, pnames[i].c_str());
325
326
327
        }

      VerifyVectorParameters(nparam, pnames, operatorID);
328
    }
Uwe Schulzweida's avatar
Uwe Schulzweida committed
329

330
  const auto streamID = cdoOpenRead(0);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
331

332
333
  const auto vlistID = cdoStreamInqVlist(streamID);
  const auto taxisID = vlistInqTaxis(vlistID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
334

Uwe Schulzweida's avatar
Uwe Schulzweida committed
335
336
  int found = 0;
  int varID = 0;
337
  auto gridID = vlistInqVarGrid(vlistID, varID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
338
  // int zaxisID = vlistInqVarZaxis(vlistID, varID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
339

Uwe Schulzweida's avatar
Uwe Schulzweida committed
340
341
  if (gridInqType(gridID) == GRID_GME) cdoAbort("GME grid unsupported!");
  if (gridInqType(gridID) == GRID_UNSTRUCTURED) cdoAbort("Unstructured grid unsupported!");
Uwe Schulzweida's avatar
Uwe Schulzweida committed
342

Uwe Schulzweida's avatar
Uwe Schulzweida committed
343
  if (gridInqType(gridID) != GRID_CURVILINEAR) gridID = gridToCurvilinear(gridID, 1);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
344

345
  const auto gridsize = gridInqSize(gridID);
346
347
  int nlon = gridInqXsize(gridID);
  int nlat = gridInqYsize(gridID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
348
  // int nlev     = zaxisInqSize(zaxisID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
349

350
351
352
353
  double *uarray = (double *) Malloc(gridsize * sizeof(double));
  double *varray = (double *) Malloc(gridsize * sizeof(double));
  double *grid_center_lat = (double *) Malloc(gridsize * sizeof(double));
  double *grid_center_lon = (double *) Malloc(gridsize * sizeof(double));
Uwe Schulzweida's avatar
Uwe Schulzweida committed
354
355
356
357
358
359

  gridInqYvals(gridID, grid_center_lat);
  gridInqXvals(gridID, grid_center_lon);

  /* Convert lat/lon units if required */
  gridInqXunits(gridID, units);
360
  grid_to_degree(units, gridsize, grid_center_lon, "grid center lon");
Uwe Schulzweida's avatar
Uwe Schulzweida committed
361
  gridInqYunits(gridID, units);
362
  grid_to_degree(units, gridsize, grid_center_lat, "grid center lat");
363

Uwe Schulzweida's avatar
Uwe Schulzweida committed
364
  int tsID = 0;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
365

Uwe Schulzweida's avatar
Uwe Schulzweida committed
366
  /* HARDCODED THE FILE NAME .. TO BE SENT AS COMMAND LINE ARGUMENT FOR THE MAGICS OPERATOR */
Uwe Schulzweida's avatar
Uwe Schulzweida committed
367
  /*
Uwe Schulzweida's avatar
Uwe Schulzweida committed
368
369
  init_XMLtemplate_parser( Filename );
  updatemagics_and_results_nodes( );
Uwe Schulzweida's avatar
Uwe Schulzweida committed
370
  */
Uwe Schulzweida's avatar
Uwe Schulzweida committed
371

372
  init_MAGICS();
Uwe Schulzweida's avatar
Uwe Schulzweida committed
373

374
  while ((nrecs = cdoStreamInqTimestep(streamID, tsID)))
Uwe Schulzweida's avatar
Uwe Schulzweida committed
375
    {
376
      if (ANIM_FLAG)
377
        {
378
          if (tsID % STEP_FREQ)
379
            {
380
381
              tsID++;
              continue;
382
383
            }
        }
Uwe Schulzweida's avatar
Uwe Schulzweida committed
384
385
      else
        {
386
          if (!STEP_FREQ && tsID)
Uwe Schulzweida's avatar
Uwe Schulzweida committed
387
            {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
388
              cdoWarning("File has values at more than one time step! Image created for first time step!!!");
389
              break;
Uwe Schulzweida's avatar
Uwe Schulzweida committed
390
391
            }
        }
392

393
394
395
      const auto vdate = taxisInqVdate(taxisID);
      const auto vtime = taxisInqVtime(taxisID);
      sprintf(datetimestr, "%s %s", dateToString(vdate).c_str(), timeToString(vtime).c_str());
396
397
398

      for (int recID = 0; recID < nrecs; recID++)
        {
399
          cdoInqRecord(streamID, &varID, &levelID);
400
401
402
403
404

          vlistInqVarName(vlistID, varID, varname);

          if (operatorID == VECTOR)
            {
405
              if (cstrIsEqual(varname, "var131") || cstrIsEqual(varname, "u"))  // U Velocity as per GRIB is var131, as per NC 'u'
406
                {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
407
                  if (DBG) fprintf(stderr, "Found U VEL in Varname %s\n", varname);
408
                  cdoReadRecord(streamID, uarray, &nmiss);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
409
                  if (nmiss) cdoSetNAN(vlistInqVarMissval(vlistID, varID), gridsize, uarray);
410
411
                  found++;
                }
412
              if (cstrIsEqual(varname, "var132") || cstrIsEqual(varname, "v"))  // V Velocity as per GRIB  is var132, as per NC 'v'
413
                {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
414
                  if (DBG) fprintf(stderr, "Found V VEL in Varname %s\n", varname);
415
                  cdoReadRecord(streamID, varray, &nmiss);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
416
                  if (nmiss) cdoSetNAN(vlistInqVarMissval(vlistID, varID), gridsize, varray);
417
418
419
420
421
422
423
424
                  found++;
                }
              if (found == 2) break;
            }
          else if (operatorID == STREAM)
            fprintf(stderr, " Stream Operator Un-Supported!\n");
          else
            fprintf(stderr, " Operator Un-Supported!\n");
Uwe Schulzweida's avatar
Uwe Schulzweida committed
425
        }
426
427
428
429
430

      if (operatorID == VECTOR)
        {
          if (found == 2)
            {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
431
              if (DBG) fprintf(stderr, "Found Both U & V VEL, Creating vector fields! \n");
Uwe Schulzweida's avatar
Uwe Schulzweida committed
432
433
              magvector(cdoGetStreamName(1), operatorID, varname, nlon, nlat, grid_center_lon, grid_center_lat, uarray, varray,
                        nparam, pnames, datetimestr);
434
435
436
            }
          else if (found == 1)
            {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
437
              fprintf(stderr, "Found only one Velocity Component in input file, cannot create Vector PLOT!\n");
438
439
440
441
              break;
            }
          else if (found == 0)
            {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
442
              fprintf(stderr, "No Velocity Components in input file, cannot create Vector PLOT!\n");
443
444
445
446
              break;
            }
        }

Uwe Schulzweida's avatar
Uwe Schulzweida committed
447
448
449
      tsID++;

      /*
450
      if( ANIM_FLAG )
Uwe Schulzweida's avatar
Uwe Schulzweida committed
451
        tsID++;
452
453
      else
        {
Uwe Schulzweida's avatar
Uwe Schulzweida committed
454
455
           cdoWarning("File has values at more than one time step! Image created for first time step!!!");
           if( STEP_FREQ > 1 ) cdoWarning("Step frequency parameter ignored!!!"); break;
456
        }
Uwe Schulzweida's avatar
Uwe Schulzweida committed
457
      */
Uwe Schulzweida's avatar
Uwe Schulzweida committed
458
459
    }

460
  cdoStreamClose(streamID);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
461

462
463
464
465
  if (uarray) Free(uarray);
  if (varray) Free(varray);
  if (grid_center_lon) Free(grid_center_lon);
  if (grid_center_lat) Free(grid_center_lat);
Uwe Schulzweida's avatar
Uwe Schulzweida committed
466

Uwe Schulzweida's avatar
Uwe Schulzweida committed
467
  /*   quit_XMLtemplate_parser( ); */
Uwe Schulzweida's avatar
Uwe Schulzweida committed
468

469
  quit_MAGICS();
Uwe Schulzweida's avatar
Uwe Schulzweida committed
470

Uwe Schulzweida's avatar
Uwe Schulzweida committed
471
#else
472

Uwe Schulzweida's avatar
Uwe Schulzweida committed
473
  cdoAbort("MAGICS support not compiled in!");
474

Uwe Schulzweida's avatar
Uwe Schulzweida committed
475
#endif
476

Uwe Schulzweida's avatar
Uwe Schulzweida committed
477
  cdoFinish();
Uwe Schulzweida's avatar
Uwe Schulzweida committed
478

Uwe Schulzweida's avatar
Uwe Schulzweida committed
479
  return nullptr;
480
}