GRASS GIS 7 Programmer's Manual  7.9.dev(2021)-e5379bbd7
parser_json.c
Go to the documentation of this file.
1 /*!
2  \file lib/gis/parser_json.c
3 
4  \brief GIS Library - converts the command line arguments into actinia JSON process
5  chain building blocks
6 
7  (C) 2018-2021 by the GRASS Development Team
8 
9  This program is free software under the GNU General Public License
10  (>=v2). Read the file COPYING that comes with GRASS for details.
11 
12  \author Soeren Gebbert
13  */
14 
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <grass/glocale.h>
19 #include <grass/gis.h>
20 
21 #include "parser_local_proto.h"
22 
23 void check_create_import_opts(struct Option *, char *, FILE *);
24 void check_create_export_opts(struct Option *, char *, FILE *);
25 char *check_mapset_in_layer_name(char *, int);
26 
27 /*!
28  \brief This function generates actinia JSON process chain building blocks
29  from the command line arguments that can be used in the actinia processing API.
30 
31  The following commands will create according JSON output:
32 
33  r.slope.aspect elevation="elevation@https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif" slope="slope+GTiff" aspect="aspect+GTiff" --json
34 
35  {
36  "module": "r.slope.aspect",
37  "id": "r.slope.aspect_1804289383",
38  "inputs":[
39  {"import_descr": {"source":"https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif", "type":"raster"},
40  "param": "elevation", "value": "elevation"},
41  {"param": "format", "value": "degrees"},
42  {"param": "precision", "value": "FCELL"},
43  {"param": "zscale", "value": "1.0"},
44  {"param": "min_slope", "value": "0.0"}
45  ],
46  "outputs":[
47  {"export": {"format":"GTiff", "type":"raster"},
48  "param": "slope", "value": "slope"},
49  {"export": {"format":"GTiff", "type":"raster"},
50  "param": "aspect", "value": "aspect"}
51  ]
52  }
53 
54  v.out.ascii input="hospitals@PERMANENT" output="myfile+TXT" --json
55 
56  {
57  "module": "v.out.ascii",
58  "id": "v.out.ascii_1804289383",
59  "inputs":[
60  {"param": "input", "value": "hospitals@PERMANENT"},
61  {"param": "layer", "value": "1"},
62  {"param": "type", "value": "point,line,boundary,centroid,area,face,kernel"},
63  {"param": "format", "value": "point"},
64  {"param": "separator", "value": "pipe"},
65  {"param": "precision", "value": "8"}
66  ],
67  "outputs":[
68  {"export": {"format":"TXT", "type":"file"},
69  "param": "output", "value": "$file::myfile"}
70  ]
71  }
72 
73  v.info map="hospitals@PERMANENT" -c --json
74 
75  {
76  "module": "v.info",
77  "id": "v.info_1804289383",
78  "flags":"c",
79  "inputs":[
80  {"param": "map", "value": "hospitals@PERMANENT"},
81  {"param": "layer", "value": "1"}
82  ]
83  }
84 
85 
86  A process chain has the following form
87 
88 {
89  'list': [{
90  'module': 'g.region',
91  'id': 'g_region_1',
92  'inputs': [{'import_descr': {'source': 'https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif',
93  'type': 'raster'},
94  'param': 'raster',
95  'value': 'elev_ned_30m_new'}],
96  'flags': 'p'
97  },
98  {
99  'module': 'r.slope.aspect',
100  'id': 'r_slope_aspect_1',
101  'inputs': [{'param': 'elevation',
102  'value': 'elev_ned_30m_new'}],
103  'outputs': [{'export': {'format': 'GTiff',
104  'type': 'raster'},
105  'param': 'slope',
106  'value': 'elev_ned_30m_new_slope'}],
107  'flags': 'a'},
108  {
109  'module': 'r.univar',
110  'id': 'r_univar_1',
111  'inputs': [{"import_descr": {"source": "LT52170762005240COA00",
112  "type": "landsat",
113  "landsat_atcor": "dos1"},
114  'param': 'map',
115  'value': 'LT52170762005240COA00_dos1.1'}],
116  'stdout': {'id': 'stats', 'format': 'kv', 'delimiter': '='},
117  'flags': 'a'
118  },
119  {
120  'module': 'exporter',
121  'id': 'exporter_1',
122  'outputs': [{'export': {'format': 'GTiff',
123  'type': 'raster'},
124  'param': 'map',
125  'value': 'LT52170762005240COA00_dos1.1'}]
126  },
127  {
128  "id": "ascii_out",
129  "module": "r.out.ascii",
130  "inputs": [{"param": "input",
131  "value": "elevation@PERMANENT"},
132  {"param": "precision", "value": "0"}],
133  "stdout": {"id": "elev_1", "format": "table", "delimiter": " "},
134  "flags": "h"
135  },
136  {
137  "id": "ascii_export",
138  "module": "r.out.ascii",
139  "inputs": [{"param": "input",
140  "value": "elevation@PERMANENT"}],
141  "outputs": [
142  {"export": {"type": "file", "format": "TXT"},
143  "param": "output",
144  "value": "$file::out1"}
145  ]
146  },
147  {
148  "id": "raster_list",
149  "module": "g.list",
150  "inputs": [{"param": "type",
151  "value": "raster"}],
152  "stdout": {"id": "raster", "format": "list", "delimiter": "\n"}
153  },
154  {
155  "module": "r.what",
156  "id": "r_what_1",
157  "verbose": True,
158  "flags": "nfic",
159  "inputs": [
160  {
161  "param": "map",
162  "value": "landuse96_28m@PERMANENT"
163  },
164  {
165  "param": "coordinates",
166  "value": "633614.08,224125.12,632972.36,225382.87"
167  },
168  {
169  "param": "null_value",
170  "value": "null"
171  },
172  {
173  "param": "separator",
174  "value": "pipe"
175  }
176  ],
177  "stdout": {"id": "sample", "format": "table", "delimiter": "|"}
178  }
179  ],
180  'webhooks': {'update': 'http://business-logic.company.com/api/v1/actinia-update-webhook',
181  'finished': 'http://business-logic.company.com/api/v1/actinia-finished-webhook'},
182  'version': '1'
183 }
184 
185 */
186 char *G__json(void)
187 {
188  FILE *fp = stdout;
189 
190  /*FILE *fp = NULL; */
191  char *type;
192  char *file_name = NULL;
193  int c;
194  int random_int = rand();
195  int num_flags = 0;
196  int num_inputs = 0;
197  int num_outputs = 0;
198  int i = 0;
199 
200  char age[KEYLENGTH];
201  char element[KEYLENGTH]; /*cell, file, grid3, vector */
202  char desc[KEYLENGTH];
203 
204  file_name = G_tempfile();
205 
206  /* fprintf(stderr, "Filename: %s\n", file_name); */
207  fp = fopen(file_name, "w+");
208  if (fp == NULL) {
209  fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
210  exit(EXIT_FAILURE);
211  }
212 
213  if (st->n_flags) {
214  struct Flag *flag;
215 
216  for (flag = &st->first_flag; flag; flag = flag->next_flag) {
217  if (flag->answer)
218  num_flags += 1;;
219  }
220  }
221 
222  /* Count input and output options */
223  if (st->n_opts) {
224  struct Option *opt;
225 
226  for (opt = &st->first_option; opt; opt = opt->next_opt) {
227  if (opt->answer) {
228  if (opt->gisprompt) {
229  G__split_gisprompt(opt->gisprompt, age, element, desc);
230  /* fprintf(stderr, "age: %s element: %s desc: %s\n", age, element, desc); */
231  if (G_strncasecmp("new", age, 3) == 0) {
232  /*fprintf(fp, "new: %s\n", opt->gisprompt); */
233  num_outputs += 1;
234  }
235  else {
236  /*fprintf(fp, "%s\n", opt->gisprompt); */
237  num_inputs += 1;
238  }
239  }
240  else {
241  num_inputs += 1;
242  }
243  }
244  }
245  }
246 
247  fprintf(fp, "{\n");
248  fprintf(fp, " \"module\": \"%s\",\n", G_program_name());
249  fprintf(fp, " \"id\": \"%s_%i\"", G_program_name(), random_int);
250 
251  if (st->n_flags && num_flags > 0) {
252  struct Flag *flag;
253 
254  fprintf(fp, ",\n");
255  fprintf(fp, " \"flags\":\"");
256 
257  for (flag = &st->first_flag; flag; flag = flag->next_flag) {
258  if (flag->answer)
259  fprintf(fp, "%c", flag->key);
260  }
261  fprintf(fp, "\"");
262  }
263 
264  /* Print the input options
265  */
266  if (st->n_opts && num_inputs > 0) {
267  struct Option *opt;
268 
269  i = 0;
270  fprintf(fp, ",\n");
271  fprintf(fp, " \"inputs\":[\n");
272  for (opt = &st->first_option; opt; opt = opt->next_opt) {
273  if (opt->gisprompt) {
274  G__split_gisprompt(opt->gisprompt, age, element, desc);
275  if (G_strncasecmp("new", age, 3) != 0) {
276  if (opt->answer) {
277  check_create_import_opts(opt, element, fp);
278  i++;
279  if (i < num_inputs) {
280  fprintf(fp, ",\n");
281  }
282  else {
283  fprintf(fp, "\n");
284  }
285  }
286  }
287  }
288  else if (opt->answer) {
289  /* Check for input options */
290  fprintf(fp, " {\"param\": \"%s\", ", opt->key);
291  fprintf(fp, "\"value\": \"%s\"}", opt->answer);
292  i++;
293  if (i < num_inputs) {
294  fprintf(fp, ",\n");
295  }
296  else {
297  fprintf(fp, "\n");
298  }
299  }
300  }
301  fprintf(fp, " ]");
302  }
303 
304  /* Print the output options
305  */
306  if (st->n_opts && num_outputs > 0) {
307  struct Option *opt;
308 
309  i = 0;
310  fprintf(fp, ",\n");
311  fprintf(fp, " \"outputs\":[\n");
312  for (opt = &st->first_option; opt; opt = opt->next_opt) {
313  if (opt->gisprompt) {
314  G__split_gisprompt(opt->gisprompt, age, element, desc);
315  if (G_strncasecmp("new", age, 3) == 0) {
316  if (opt->answer) {
317  check_create_export_opts(opt, element, fp);
318  i++;
319  if (i < num_outputs) {
320  fprintf(fp, ",\n");
321  }
322  else {
323  fprintf(fp, "\n");
324  }
325  }
326  }
327  }
328  }
329  fprintf(fp, " ]\n");
330  }
331 
332  fprintf(fp, "}\n");
333  fclose(fp);
334 
335  /* Print the file content to stdout */
336  fp = fopen(file_name, "r");
337  if (fp == NULL) {
338  fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
339  exit(EXIT_FAILURE);
340  }
341 
342  c = fgetc(fp);
343  while (c != EOF) {
344  fprintf(stdout, "%c", c);
345  c = fgetc(fp);
346  }
347  fclose(fp);
348 
349  return file_name;
350 }
351 
352 /* \brief Check the provided answer and generate the import statement
353  dependent on the element type (cell, vector, grid3, file)
354 
355  {'import_descr': {'source': 'https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif',
356  'type': 'raster'},
357  'param': 'map',
358  'value': 'elevation'}
359  */
360 void check_create_import_opts(struct Option *opt, char *element, FILE * fp)
361 {
362  int i = 0, urlfound = 0;
363  int has_import = 0;
364  char **tokens;
365 
366  G_debug(2, "tokenize opt string: <%s> with '@'", opt->answer);
367  tokens = G_tokenize(opt->answer, "@");
368  while (tokens[i]) {
369  G_chop(tokens[i]);
370  i++;
371  }
372  if (i > 2)
373  G_fatal_error(_("Input string not understood: <%s>. Multiple '@' chars?"),
374  opt->answer);
375 
376  if (i > 1) {
377  /* check if tokens[1] starts with an URL or name@mapset */
378  G_debug(2, "tokens[1]: <%s>", tokens[1]);
379  if (strncmp(tokens[1], "http://", 7) == 0 ||
380  strncmp(tokens[1], "https://", 8) == 0 ||
381  strncmp(tokens[1], "ftp://", 6) == 0) {
382  urlfound = 1;
383  G_debug(2, "URL found");
384  }
385  else {
386  urlfound = 0;
387  G_debug(2, "name@mapset found");
388  }
389  }
390 
391  fprintf(fp, " {");
392 
393  if (i > 1 && urlfound == 1) {
394  if (G_strncasecmp("cell", element, 4) == 0) {
395  fprintf(fp,
396  "\"import_descr\": {\"source\":\"%s\", \"type\":\"raster\"},\n ",
397  tokens[1]);
398  has_import = 1;
399  }
400  else if (G_strncasecmp("file", element, 4) == 0) {
401  fprintf(fp,
402  "\"import_descr\": {\"source\":\"%s\", \"type\":\"file\"},\n ",
403  tokens[1]);
404  has_import = 1;
405  }
406  else if (G_strncasecmp("vector", element, 4) == 0) {
407  fprintf(fp,
408  "\"import_descr\": {\"source\":\"%s\", \"type\":\"vector\"},\n ",
409  tokens[1]);
410  has_import = 1;
411  }
412  }
413 
414  fprintf(fp, "\"param\": \"%s\", ", opt->key);
415  /* In case of import the mapset must be removed always */
416  if (urlfound == 1) {
417  fprintf(fp, "\"value\": \"%s\"",
418  check_mapset_in_layer_name(tokens[0], has_import));
419  }
420  else {
421  fprintf(fp, "\"value\": \"%s\"",
422  check_mapset_in_layer_name(opt->answer, has_import));
423  };
424  fprintf(fp, "}");
425 
426  G_free_tokens(tokens);
427 }
428 
429 /* \brief Check the provided answer and generate the export statement
430  dependent on the element type (cell, vector, grid3, file)
431 
432  "outputs": [
433  {"export": {"type": "file", "format": "TXT"},
434  "param": "output",
435  "value": "$file::out1"},
436  {'export': {'format': 'GTiff', 'type': 'raster'},
437  'param': 'map',
438  'value': 'LT52170762005240COA00_dos1.1'}
439  ]
440  */
441 void check_create_export_opts(struct Option *opt, char *element, FILE * fp)
442 {
443  int i = 0;
444  int has_file_export = 0;
445  char **tokens;
446 
447  tokens = G_tokenize(opt->answer, "+");
448  while (tokens[i]) {
449  G_chop(tokens[i]);
450  i++;
451  }
452 
453  fprintf(fp, " {");
454 
455  if (i > 1) {
456  if (G_strncasecmp("cell", element, 4) == 0) {
457  fprintf(fp,
458  "\"export\": {\"format\":\"%s\", \"type\":\"raster\"},\n ",
459  tokens[1]);
460  }
461  else if (G_strncasecmp("file", element, 4) == 0) {
462  fprintf(fp,
463  "\"export\": {\"format\":\"%s\", \"type\":\"file\"},\n ",
464  tokens[1]);
465  has_file_export = 1;
466  }
467  else if (G_strncasecmp("vector", element, 4) == 0) {
468  fprintf(fp,
469  "\"export\": {\"format\":\"%s\", \"type\":\"vector\"},\n ",
470  tokens[1]);
471  }
472  }
473 
474  fprintf(fp, "\"param\": \"%s\", ", opt->key);
475  if (has_file_export == 1) {
476  fprintf(fp, "\"value\": \"$file::%s\"",
477  check_mapset_in_layer_name(tokens[0], 1));
478  }
479  else {
480  fprintf(fp, "\"value\": \"%s\"",
481  check_mapset_in_layer_name(tokens[0], 1));
482  }
483  fprintf(fp, "}");
484 
485  G_free_tokens(tokens);
486 }
487 
488 /*
489  \brief Check if the current mapset is present in the layer name and remove it
490 
491  The flag always_remove tells this function to always remove all mapset names.
492 
493  \return pointer to the layer name without the current mapset
494 */
495 char *check_mapset_in_layer_name(char *layer_name, int always_remove)
496 {
497  int i = 0;
498  char **tokens;
499  const char *mapset;
500 
501  mapset = G_mapset();
502 
503  tokens = G_tokenize(layer_name, "@");
504 
505  while (tokens[i]) {
506  G_chop(tokens[i]);
507  /* fprintf(stderr, "Token %i: %s\n", i, tokens[i]); */
508  i++;
509  }
510 
511  if (always_remove == 1)
512  return tokens[0];
513 
514  if (i > 1 && G_strcasecmp(mapset, tokens[1]) == 0)
515  return tokens[0];
516 
517  return layer_name;
518 }
int G_strncasecmp(const char *, const char *, int)
String compare ignoring case (upper or lower) - limited number of characters.
Definition: strings.c:69
const char * G_program_name(void)
Return module name.
Definition: progrm_nme.c:28
void void void void G_fatal_error(const char *,...) __attribute__((format(printf
char * G__json(void)
This function generates actinia JSON process chain building blocks from the command line arguments th...
Definition: parser_json.c:186
char answer
Definition: gis.h:563
void check_create_import_opts(struct Option *, char *, FILE *)
Definition: parser_json.c:360
char ** G_tokenize(const char *, const char *)
Tokenize string.
Definition: gis/token.c:48
#define NULL
Definition: ccmath.h:32
Definition: lidar.h:86
struct state * st
Definition: parser.c:104
int int G_strcasecmp(const char *, const char *)
String compare ignoring case (upper or lower)
Definition: strings.c:47
char * answer
Definition: gis.h:544
char * G_tempfile(void)
Returns a temporary file name.
Definition: tempfile.c:62
Structure that stores flag info.
Definition: gis.h:560
char key
Definition: gis.h:562
struct Flag * next_flag
Definition: gis.h:569
char * G_chop(char *)
Chop leading and trailing white spaces.
Definition: strings.c:328
void G__split_gisprompt(const char *gisprompt, char *age, char *element, char *desc)
Definition: parser.c:1653
const char * G_mapset(void)
Get current mapset name.
Definition: gis/mapset.c:33
void G_free_tokens(char **)
Free memory allocated to tokens.
Definition: gis/token.c:204
char * check_mapset_in_layer_name(char *, int)
Definition: parser_json.c:495
Structure that stores option information.
Definition: gis.h:531
#define _(str)
Definition: glocale.h:10
const char * key
Definition: gis.h:533
const char * gisprompt
Definition: gis.h:548
int G_debug(int, const char *,...) __attribute__((format(printf
void check_create_export_opts(struct Option *, char *, FILE *)
Definition: parser_json.c:441
struct Option * next_opt
Definition: gis.h:547