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