GRASS GIS 8 Programmer's Manual 8.3.2(2024)-exported
Loading...
Searching...
No Matches
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
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
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 */
190char *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 */
364void 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 */
449void 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 */
506char *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
int G_debug(int level, const char *msg,...)
Print debugging message.
Definition debug.c:66
void G_fatal_error(const char *msg,...)
Print a fatal error message to stderr.
Definition gis/error.c:159
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:1679
void check_create_import_opts(struct Option *, char *, FILE *)
void check_create_export_opts(struct Option *, char *, FILE *)
char * check_mapset_in_layer_name(char *, int)
char * G__json(void)
This function generates actinia JSON process chain building blocks from the command line arguments th...
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:331
int G_strcasecmp(const char *x, const char *y)
String compare ignoring case (upper or lower)
Definition strings.c:47
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:198
char ** G_tokenize(const char *buf, const char *delim)
Tokenize string.
Definition token.c:47