GRASS GIS 8 Programmer's Manual 8.2.1(2023)-exported
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
23void check_create_import_opts(struct Option *, char *, FILE *);
24void check_create_export_opts(struct Option *, char *, FILE *);
25char *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*/
186char *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) {
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) {
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 */
359void 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 */
440void 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*/
494char *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}
#define NULL
Definition: ccmath.h:32
int G_debug(int level, const char *msg,...)
Print debugging message.
Definition: debug.c:65
void G_fatal_error(const char *msg,...)
Print a fatal error message to stderr.
Definition: gis/error.c:160
const char * G_mapset(void)
Get current mapset name.
Definition: mapset.c:33
struct state * st
Definition: parser.c:104
void G__split_gisprompt(const char *gisprompt, char *age, char *element, char *desc)
Definition: parser.c:1653
void check_create_import_opts(struct Option *, char *, FILE *)
Definition: parser_json.c:359
void check_create_export_opts(struct Option *, char *, FILE *)
Definition: parser_json.c:440
char * check_mapset_in_layer_name(char *, int)
Definition: parser_json.c:494
char * G__json(void)
This function generates actinia JSON process chain building blocks from the command line arguments th...
Definition: parser_json.c:186
const char * G_program_name(void)
Return module name.
Definition: progrm_nme.c:28
int G_strncasecmp(const char *x, const char *y, int n)
String compare ignoring case (upper or lower) - limited number of characters.
Definition: strings.c:69
char * G_chop(char *line)
Chop leading and trailing white spaces.
Definition: strings.c:328
int G_strcasecmp(const char *x, const char *y)
String compare ignoring case (upper or lower)
Definition: strings.c:47
Definition: lidar.h:87
char * G_tempfile(void)
Returns a temporary file name.
Definition: tempfile.c:62
void G_free_tokens(char **tokens)
Free memory allocated to tokens.
Definition: token.c:204
char ** G_tokenize(const char *buf, const char *delim)
Tokenize string.
Definition: token.c:48