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 */
G__json(void)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 */
check_create_import_opts(struct Option * opt,char * element,FILE * fp)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 */
check_create_export_opts(struct Option * opt,char * element,FILE * fp)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 */
check_mapset_in_layer_name(char * layer_name,int always_remove)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 }
519