GRASS GIS 8 Programmer's Manual  8.2.2dev(2023)-3d2c704037
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 *file_name = NULL;
192  int c;
193  int random_int = rand();
194  int num_flags = 0;
195  int num_inputs = 0;
196  int num_outputs = 0;
197  int i = 0;
198 
199  char age[KEYLENGTH];
200  char element[KEYLENGTH]; /*cell, file, grid3, vector */
201  char desc[KEYLENGTH];
202 
203  file_name = G_tempfile();
204 
205  /* fprintf(stderr, "Filename: %s\n", file_name); */
206  fp = fopen(file_name, "w+");
207  if (fp == NULL) {
208  fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
209  exit(EXIT_FAILURE);
210  }
211 
212  if (st->n_flags) {
213  struct Flag *flag;
214 
215  for (flag = &st->first_flag; flag; flag = flag->next_flag) {
216  if (flag->answer)
217  num_flags += 1;;
218  }
219  }
220 
221  /* Count input and output options */
222  if (st->n_opts) {
223  struct Option *opt;
224 
225  for (opt = &st->first_option; opt; opt = opt->next_opt) {
226  if (opt->answer) {
227  if (opt->gisprompt) {
228  G__split_gisprompt(opt->gisprompt, age, element, desc);
229  /* fprintf(stderr, "age: %s element: %s desc: %s\n", age, element, desc); */
230  if (G_strncasecmp("new", age, 3) == 0) {
231  /*fprintf(fp, "new: %s\n", opt->gisprompt); */
232  num_outputs += 1;
233  }
234  else {
235  /*fprintf(fp, "%s\n", opt->gisprompt); */
236  num_inputs += 1;
237  }
238  }
239  else {
240  num_inputs += 1;
241  }
242  }
243  }
244  }
245 
246  fprintf(fp, "{\n");
247  fprintf(fp, " \"module\": \"%s\",\n", G_program_name());
248  fprintf(fp, " \"id\": \"%s_%i\"", G_program_name(), random_int);
249 
250  if (st->n_flags && num_flags > 0) {
251  struct Flag *flag;
252 
253  fprintf(fp, ",\n");
254  fprintf(fp, " \"flags\":\"");
255 
256  for (flag = &st->first_flag; flag; flag = flag->next_flag) {
257  if (flag->answer)
258  fprintf(fp, "%c", flag->key);
259  }
260  fprintf(fp, "\"");
261  }
262 
263  /* Print the input options
264  */
265  if (st->n_opts && num_inputs > 0) {
266  struct Option *opt;
267 
268  i = 0;
269  fprintf(fp, ",\n");
270  fprintf(fp, " \"inputs\":[\n");
271  for (opt = &st->first_option; opt; opt = opt->next_opt) {
272  if (opt->gisprompt) {
273  G__split_gisprompt(opt->gisprompt, age, element, desc);
274  if (G_strncasecmp("new", age, 3) != 0) {
275  if (opt->answer) {
276  check_create_import_opts(opt, element, fp);
277  i++;
278  if (i < num_inputs) {
279  fprintf(fp, ",\n");
280  }
281  else {
282  fprintf(fp, "\n");
283  }
284  }
285  }
286  }
287  else if (opt->answer) {
288  /* Check for input options */
289  fprintf(fp, " {\"param\": \"%s\", ", opt->key);
290  fprintf(fp, "\"value\": \"%s\"}", opt->answer);
291  i++;
292  if (i < num_inputs) {
293  fprintf(fp, ",\n");
294  }
295  else {
296  fprintf(fp, "\n");
297  }
298  }
299  }
300  fprintf(fp, " ]");
301  }
302 
303  /* Print the output options
304  */
305  if (st->n_opts && num_outputs > 0) {
306  struct Option *opt;
307 
308  i = 0;
309  fprintf(fp, ",\n");
310  fprintf(fp, " \"outputs\":[\n");
311  for (opt = &st->first_option; opt; opt = opt->next_opt) {
312  if (opt->gisprompt) {
313  G__split_gisprompt(opt->gisprompt, age, element, desc);
314  if (G_strncasecmp("new", age, 3) == 0) {
315  if (opt->answer) {
316  check_create_export_opts(opt, element, fp);
317  i++;
318  if (i < num_outputs) {
319  fprintf(fp, ",\n");
320  }
321  else {
322  fprintf(fp, "\n");
323  }
324  }
325  }
326  }
327  }
328  fprintf(fp, " ]\n");
329  }
330 
331  fprintf(fp, "}\n");
332  fclose(fp);
333 
334  /* Print the file content to stdout */
335  fp = fopen(file_name, "r");
336  if (fp == NULL) {
337  fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
338  exit(EXIT_FAILURE);
339  }
340 
341  c = fgetc(fp);
342  while (c != EOF) {
343  fprintf(stdout, "%c", c);
344  c = fgetc(fp);
345  }
346  fclose(fp);
347 
348  return file_name;
349 }
350 
351 /* \brief Check the provided answer and generate the import statement
352  dependent on the element type (cell, vector, grid3, file)
353 
354  {'import_descr': {'source': 'https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif',
355  'type': 'raster'},
356  'param': 'map',
357  'value': 'elevation'}
358  */
359 void check_create_import_opts(struct Option *opt, char *element, FILE * fp)
360 {
361  int i = 0, urlfound = 0;
362  int has_import = 0;
363  char **tokens;
364 
365  G_debug(2, "tokenize opt string: <%s> with '@'", opt->answer);
366  tokens = G_tokenize(opt->answer, "@");
367  while (tokens[i]) {
368  G_chop(tokens[i]);
369  i++;
370  }
371  if (i > 2)
372  G_fatal_error(_("Input string not understood: <%s>. Multiple '@' chars?"),
373  opt->answer);
374 
375  if (i > 1) {
376  /* check if tokens[1] starts with an URL or name@mapset */
377  G_debug(2, "tokens[1]: <%s>", tokens[1]);
378  if (strncmp(tokens[1], "http://", 7) == 0 ||
379  strncmp(tokens[1], "https://", 8) == 0 ||
380  strncmp(tokens[1], "ftp://", 6) == 0) {
381  urlfound = 1;
382  G_debug(2, "URL found");
383  }
384  else {
385  urlfound = 0;
386  G_debug(2, "name@mapset found");
387  }
388  }
389 
390  fprintf(fp, " {");
391 
392  if (i > 1 && urlfound == 1) {
393  if (G_strncasecmp("cell", element, 4) == 0) {
394  fprintf(fp,
395  "\"import_descr\": {\"source\":\"%s\", \"type\":\"raster\"},\n ",
396  tokens[1]);
397  has_import = 1;
398  }
399  else if (G_strncasecmp("file", element, 4) == 0) {
400  fprintf(fp,
401  "\"import_descr\": {\"source\":\"%s\", \"type\":\"file\"},\n ",
402  tokens[1]);
403  has_import = 1;
404  }
405  else if (G_strncasecmp("vector", element, 4) == 0) {
406  fprintf(fp,
407  "\"import_descr\": {\"source\":\"%s\", \"type\":\"vector\"},\n ",
408  tokens[1]);
409  has_import = 1;
410  }
411  }
412 
413  fprintf(fp, "\"param\": \"%s\", ", opt->key);
414  /* In case of import the mapset must be removed always */
415  if (urlfound == 1) {
416  fprintf(fp, "\"value\": \"%s\"",
417  check_mapset_in_layer_name(tokens[0], has_import));
418  }
419  else {
420  fprintf(fp, "\"value\": \"%s\"",
421  check_mapset_in_layer_name(opt->answer, has_import));
422  };
423  fprintf(fp, "}");
424 
425  G_free_tokens(tokens);
426 }
427 
428 /* \brief Check the provided answer and generate the export statement
429  dependent on the element type (cell, vector, grid3, file)
430 
431  "outputs": [
432  {"export": {"type": "file", "format": "TXT"},
433  "param": "output",
434  "value": "$file::out1"},
435  {'export': {'format': 'GTiff', 'type': 'raster'},
436  'param': 'map',
437  'value': 'LT52170762005240COA00_dos1.1'}
438  ]
439  */
440 void check_create_export_opts(struct Option *opt, char *element, FILE * fp)
441 {
442  int i = 0;
443  int has_file_export = 0;
444  char **tokens;
445 
446  tokens = G_tokenize(opt->answer, "+");
447  while (tokens[i]) {
448  G_chop(tokens[i]);
449  i++;
450  }
451 
452  fprintf(fp, " {");
453 
454  if (i > 1) {
455  if (G_strncasecmp("cell", element, 4) == 0) {
456  fprintf(fp,
457  "\"export\": {\"format\":\"%s\", \"type\":\"raster\"},\n ",
458  tokens[1]);
459  }
460  else if (G_strncasecmp("file", element, 4) == 0) {
461  fprintf(fp,
462  "\"export\": {\"format\":\"%s\", \"type\":\"file\"},\n ",
463  tokens[1]);
464  has_file_export = 1;
465  }
466  else if (G_strncasecmp("vector", element, 4) == 0) {
467  fprintf(fp,
468  "\"export\": {\"format\":\"%s\", \"type\":\"vector\"},\n ",
469  tokens[1]);
470  }
471  }
472 
473  fprintf(fp, "\"param\": \"%s\", ", opt->key);
474  if (has_file_export == 1) {
475  fprintf(fp, "\"value\": \"$file::%s\"",
476  check_mapset_in_layer_name(tokens[0], 1));
477  }
478  else {
479  fprintf(fp, "\"value\": \"%s\"",
480  check_mapset_in_layer_name(tokens[0], 1));
481  }
482  fprintf(fp, "}");
483 
484  G_free_tokens(tokens);
485 }
486 
487 /*
488  \brief Check if the current mapset is present in the layer name and remove it
489 
490  The flag always_remove tells this function to always remove all mapset names.
491 
492  \return pointer to the layer name without the current mapset
493 */
494 char *check_mapset_in_layer_name(char *layer_name, int always_remove)
495 {
496  int i = 0;
497  char **tokens;
498  const char *mapset;
499 
500  mapset = G_mapset();
501 
502  tokens = G_tokenize(layer_name, "@");
503 
504  while (tokens[i]) {
505  G_chop(tokens[i]);
506  /* fprintf(stderr, "Token %i: %s\n", i, tokens[i]); */
507  i++;
508  }
509 
510  if (always_remove == 1)
511  return tokens[0];
512 
513  if (i > 1 && G_strcasecmp(mapset, tokens[1]) == 0)
514  return tokens[0];
515 
516  return layer_name;
517 }
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:574
void check_create_import_opts(struct Option *, char *, FILE *)
Definition: parser_json.c:359
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:555
char * G_tempfile(void)
Returns a temporary file name.
Definition: tempfile.c:62
Structure that stores flag info.
Definition: gis.h:571
char key
Definition: gis.h:573
struct Flag * next_flag
Definition: gis.h:580
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:494
Structure that stores option information.
Definition: gis.h:542
#define _(str)
Definition: glocale.h:10
const char * key
Definition: gis.h:544
const char * gisprompt
Definition: gis.h:559
int G_debug(int, const char *,...) __attribute__((format(printf
void check_create_export_opts(struct Option *, char *, FILE *)
Definition: parser_json.c:440
struct Option * next_opt
Definition: gis.h:558