[GRASS-SVN] r73525 - grass/trunk/lib/gis
svn_grass at osgeo.org
svn_grass at osgeo.org
Fri Oct 12 11:38:26 PDT 2018
Author: huhabla
Date: 2018-10-12 11:38:25 -0700 (Fri, 12 Oct 2018)
New Revision: 73525
Added:
grass/trunk/lib/gis/parser_json.c
Modified:
grass/trunk/lib/gis/parser.c
grass/trunk/lib/gis/parser_local_proto.h
Log:
Added actinia JSON compatible output to the GRASS parser for any module
Modified: grass/trunk/lib/gis/parser.c
===================================================================
--- grass/trunk/lib/gis/parser.c 2018-10-12 09:22:17 UTC (rev 73524)
+++ grass/trunk/lib/gis/parser.c 2018-10-12 18:38:25 UTC (rev 73525)
@@ -94,7 +94,6 @@
REPLACED = 5
};
-#define KEYLENGTH 64
#define MAX_MATCHES 50
@@ -120,7 +119,6 @@
static void check_multiple_opts(void);
static int check_overwrite(void);
static void define_keywords(void);
-static void split_gisprompt(const char *, char *, char *, char *);
static int module_gui_wx(void);
static void append_error(const char *);
static const char *get_renamed_option(const char *);
@@ -326,6 +324,7 @@
int i;
struct Option *opt;
char force_gui = FALSE;
+ int print_json = 0;
err = NULL;
need_first_opt = 1;
@@ -520,6 +519,11 @@
st->overwrite = 1;
}
+ /* JSON print option */
+ if (strcmp(ptr, "--json") == 0) {
+ print_json = 1;
+ }
+
/* Verbose option */
else if (strcmp(ptr, "--v") == 0 || strcmp(ptr, "--verbose") == 0) {
char buff[32];
@@ -631,6 +635,12 @@
return -1;
}
+ /* Print the JSON definition of the command and exit */
+ if(print_json == 1) {
+ G__json();
+ exit(EXIT_SUCCESS);
+ }
+
if (!st->suppress_overwrite) {
if (check_overwrite())
return -1;
@@ -874,7 +884,7 @@
opt = &st->first_option;
while (opt) {
if (opt->gisprompt) {
- split_gisprompt(opt->gisprompt, age, element, desc);
+ G__split_gisprompt(opt->gisprompt, age, element, desc);
if (strcmp(age, "new") == 0)
return 1;
}
@@ -1568,7 +1578,7 @@
opt = &st->first_option;
while (opt) {
if (opt->answer && opt->gisprompt) {
- split_gisprompt(opt->gisprompt, age, element, desc);
+ G__split_gisprompt(opt->gisprompt, age, element, desc);
if (strcmp(age, "new") == 0) {
int i;
@@ -1620,7 +1630,7 @@
return (error);
}
-void split_gisprompt(const char *gisprompt, char *age, char *element,
+void G__split_gisprompt(const char *gisprompt, char *age, char *element,
char *desc)
{
const char *ptr1;
Added: grass/trunk/lib/gis/parser_json.c
===================================================================
--- grass/trunk/lib/gis/parser_json.c (rev 0)
+++ grass/trunk/lib/gis/parser_json.c 2018-10-12 18:38:25 UTC (rev 73525)
@@ -0,0 +1,464 @@
+/*!
+ \file lib/gis/parser_json.c
+
+ \brief GIS Library - converts the command line arguments into actinia JSON process
+ chain building blocks
+
+ (C) 2018 by the GRASS Development Team
+
+ This program is free software under the GNU General Public License
+ (>=v2). Read the file COPYING that comes with GRASS for details.
+
+ \author Soeren Gebbert
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <grass/gis.h>
+
+#include "parser_local_proto.h"
+
+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);
+
+/*!
+ \brief This function generates actinia JSON process chain building blocks
+ from the command line arguments that can be used in the actinia processing API.
+
+ The following commands will create according JSON output:
+
+ r.slope.aspect elevation="elevation+https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif" slope="slope+GTiff" aspect="aspect+GTiff" --json
+
+ {
+ "module": "r.slope.aspect",
+ "id": "r.slope.aspect_1804289383",
+ "inputs":[
+ {"import_descr": {"source":"https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif", "type":"raster"},
+ "param": "elevation", "value": "elevation"},
+ {"param": "format", "value": "degrees"},
+ {"param": "precision", "value": "FCELL"},
+ {"param": "zscale", "value": "1.0"},
+ {"param": "min_slope", "value": "0.0"}
+ ],
+ "outputs":[
+ {"export": {"format":"GTiff", "type":"raster"},
+ "param": "slope", "value": "slope"},
+ {"export": {"format":"GTiff", "type":"raster"},
+ "param": "aspect", "value": "aspect"}
+ ]
+ }
+
+ v.out.ascii input="hospitals at PERMANENT" output="myfile+TXT" --json
+
+ {
+ "module": "v.out.ascii",
+ "id": "v.out.ascii_1804289383",
+ "inputs":[
+ {"param": "input", "value": "hospitals at PERMANENT"},
+ {"param": "layer", "value": "1"},
+ {"param": "type", "value": "point,line,boundary,centroid,area,face,kernel"},
+ {"param": "format", "value": "point"},
+ {"param": "separator", "value": "pipe"},
+ {"param": "precision", "value": "8"}
+ ],
+ "outputs":[
+ {"export": {"format":"TXT", "type":"file"},
+ "param": "output", "value": "$file::myfile"}
+ ]
+ }
+
+ v.info map="hospitals at PERMANENT" -c --json
+
+ {
+ "module": "v.info",
+ "id": "v.info_1804289383",
+ "flags":"c",
+ "inputs":[
+ {"param": "map", "value": "hospitals at PERMANENT"},
+ {"param": "layer", "value": "1"}
+ ]
+ }
+
+
+ A process chain has the following form
+
+{
+ 'list': [{
+ 'module': 'g.region',
+ 'id': 'g_region_1',
+ 'inputs': [{'import_descr': {'source': 'https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif',
+ 'type': 'raster'},
+ 'param': 'raster',
+ 'value': 'elev_ned_30m_new'}],
+ 'flags': 'p'
+ },
+ {
+ 'module': 'r.slope.aspect',
+ 'id': 'r_slope_aspect_1',
+ 'inputs': [{'param': 'elevation',
+ 'value': 'elev_ned_30m_new'}],
+ 'outputs': [{'export': {'format': 'GTiff',
+ 'type': 'raster'},
+ 'param': 'slope',
+ 'value': 'elev_ned_30m_new_slope'}],
+ 'flags': 'a'},
+ {
+ 'module': 'r.univar',
+ 'id': 'r_univar_1',
+ 'inputs': [{"import_descr": {"source": "LT52170762005240COA00",
+ "type": "landsat",
+ "landsat_atcor": "dos1"},
+ 'param': 'map',
+ 'value': 'LT52170762005240COA00_dos1.1'}],
+ 'stdout': {'id': 'stats', 'format': 'kv', 'delimiter': '='},
+ 'flags': 'a'
+ },
+ {
+ 'module': 'exporter',
+ 'id': 'exporter_1',
+ 'outputs': [{'export': {'format': 'GTiff',
+ 'type': 'raster'},
+ 'param': 'map',
+ 'value': 'LT52170762005240COA00_dos1.1'}]
+ },
+ {
+ "id": "ascii_out",
+ "module": "r.out.ascii",
+ "inputs": [{"param": "input",
+ "value": "elevation at PERMANENT"},
+ {"param": "precision", "value": "0"}],
+ "stdout": {"id": "elev_1", "format": "table", "delimiter": " "},
+ "flags": "h"
+ },
+ {
+ "id": "ascii_export",
+ "module": "r.out.ascii",
+ "inputs": [{"param": "input",
+ "value": "elevation at PERMANENT"}],
+ "outputs": [
+ {"export": {"type": "file", "format": "TXT"},
+ "param": "output",
+ "value": "$file::out1"}
+ ]
+ },
+ {
+ "id": "raster_list",
+ "module": "g.list",
+ "inputs": [{"param": "type",
+ "value": "raster"}],
+ "stdout": {"id": "raster", "format": "list", "delimiter": "\n"}
+ },
+ {
+ "module": "r.what",
+ "id": "r_what_1",
+ "verbose": True,
+ "flags": "nfic",
+ "inputs": [
+ {
+ "param": "map",
+ "value": "landuse96_28m at PERMANENT"
+ },
+ {
+ "param": "coordinates",
+ "value": "633614.08,224125.12,632972.36,225382.87"
+ },
+ {
+ "param": "null_value",
+ "value": "null"
+ },
+ {
+ "param": "separator",
+ "value": "pipe"
+ }
+ ],
+ "stdout": {"id": "sample", "format": "table", "delimiter": "|"}
+ }
+ ],
+ 'webhooks': {'update': 'http://business-logic.company.com/api/v1/actinia-update-webhook',
+ 'finished': 'http://business-logic.company.com/api/v1/actinia-finished-webhook'},
+ 'version': '1'
+}
+
+*/
+char *G__json(void)
+{
+ FILE *fp = stdout;
+ /*FILE *fp = NULL;*/
+ char *type;
+ char *file_name = NULL;
+ char c;
+ int random_int = rand();
+ int num_flags = 0;
+ int num_inputs = 0;
+ int num_outputs = 0;
+ int i = 0;
+
+ char age[KEYLENGTH];
+ char element[KEYLENGTH]; /*cell, file, grid3, vector */
+ char desc[KEYLENGTH];
+
+ file_name = G_tempfile();
+
+ fprintf(stderr, "Filename: %s\n", file_name);
+ fp = fopen(file_name, "w+");
+ if (fp == NULL)
+ {
+ fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
+ exit(EXIT_FAILURE);
+ }
+
+ if (st->n_flags) {
+ struct Flag *flag;
+ for (flag = &st->first_flag; flag; flag = flag->next_flag) {
+ if(flag->answer)
+ num_flags += 1;;
+ }
+ }
+
+ /* Count input and output options */
+ if (st->n_opts) {
+ struct Option *opt;
+ for (opt = &st->first_option; opt; opt = opt->next_opt) {
+ if (opt->answer) {
+ if (opt->gisprompt) {
+ G__split_gisprompt(opt->gisprompt, age, element, desc);
+ /* fprintf(stderr, "age: %s element: %s desc: %s\n", age, element, desc); */
+ if (G_strncasecmp("new", age, 3) == 0) {
+ /*fprintf(fp, "new: %s\n", opt->gisprompt);*/
+ num_outputs += 1;
+ }
+ else {
+ /*fprintf(fp, "%s\n", opt->gisprompt);*/
+ num_inputs += 1;
+ }
+ } else {
+ num_inputs += 1;
+ }
+ }
+ }
+ }
+
+ fprintf(fp, "{\n");
+ fprintf(fp, " \"module\": \"%s\",\n", G_program_name());
+ fprintf(fp, " \"id\": \"%s_%i\"", G_program_name(), random_int);
+
+ if (st->n_flags && num_flags > 0) {
+ struct Flag *flag;
+ fprintf(fp, ",\n");
+ fprintf(fp, " \"flags\":\"");
+
+ for (flag = &st->first_flag; flag; flag = flag->next_flag) {
+ if(flag->answer)
+ fprintf(fp, "%c", flag->key);
+ }
+ fprintf(fp, "\"");
+ }
+
+ /* Print the input options
+
+ TODO: Check for URLs in the answer to create import options
+ TODO: Check for mapset names in the answer and remove the current mapset name from the string
+ */
+ if (st->n_opts && num_inputs > 0) {
+ struct Option *opt;
+ i = 0;
+ fprintf(fp, ",\n");
+ fprintf(fp, " \"inputs\":[\n");
+ for (opt = &st->first_option; opt; opt = opt->next_opt) {
+ if (opt->gisprompt) {
+ G__split_gisprompt(opt->gisprompt, age, element, desc);
+ if (G_strncasecmp("new", age, 3) != 0) {
+ if (opt->answer) {
+ check_create_import_opts(opt, element, fp);
+ i++;
+ if (i < num_inputs) {fprintf(fp, ",\n");}
+ else {fprintf(fp, "\n");}
+ }
+ }
+ } else if (opt->answer) {
+ /* Check for input options */
+ fprintf(fp, " {\"param\": \"%s\", ", opt->key);
+ fprintf(fp, "\"value\": \"%s\"}", opt->answer);
+ i++;
+ if (i < num_inputs) {fprintf(fp, ",\n");}
+ else {fprintf(fp, "\n");}
+ }
+ }
+ fprintf(fp, " ]");
+ }
+
+ /* Print the output options
+
+ TODO: Check export options in the answer to create export options
+ TODO: Check for mapset names in the answer and remove the mapset name from the string
+ */
+ if (st->n_opts && num_outputs > 0) {
+ struct Option *opt;
+ i = 0;
+ fprintf(fp, ",\n");
+ fprintf(fp, " \"outputs\":[\n");
+ for (opt = &st->first_option; opt; opt = opt->next_opt) {
+ if (opt->gisprompt) {
+ G__split_gisprompt(opt->gisprompt, age, element, desc);
+ if (G_strncasecmp("new", age, 3) == 0) {
+ if (opt->answer) {
+ check_create_export_opts(opt, element, fp);
+ i++;
+ if (i < num_outputs) {fprintf(fp, ",\n");}
+ else {fprintf(fp, "\n");}
+ }
+ }
+ }
+ }
+ fprintf(fp, " ]\n");
+ }
+
+ fprintf(fp, "}\n");
+ fclose(fp);
+
+ /* Print the file content to stdout */
+ fp = fopen(file_name, "r");
+ if (fp == NULL)
+ {
+ fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
+ exit(EXIT_FAILURE);
+ }
+
+ c = fgetc(fp);
+ while (c != EOF)
+ {
+ fprintf (stdout, "%c", c);
+ c = fgetc(fp);
+ }
+ fclose(fp);
+
+ return file_name;
+}
+
+/* \brief Check the provided answer and generate the import statement
+ dependent on the element type (cell, vector, grid3, file)
+
+ {'import_descr': {'source': 'https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif',
+ 'type': 'raster'},
+ 'param': 'map',
+ 'value': 'elevation'}
+ */
+void check_create_import_opts(struct Option *opt, char *element, FILE *fp)
+{
+ int i = 0;
+ int has_import = 0;
+ char **tokens;
+
+ tokens = G_tokenize(opt->answer, "+");
+ while (tokens[i]) {
+ G_chop(tokens[i]);
+ i++;
+ }
+
+ fprintf(fp, " {");
+
+ if (i > 1) {
+ if (G_strncasecmp("cell", element, 4) == 0) {
+ fprintf(fp, "\"import_descr\": {\"source\":\"%s\", \"type\":\"raster\"},\n ", tokens[1]);
+ has_import = 1;
+ }
+ else if (G_strncasecmp("file", element, 4) == 0) {
+ fprintf(fp, "\"import_descr\": {\"source\":\"%s\", \"type\":\"file\"},\n ", tokens[1]);
+ has_import = 1;
+ }
+ else if (G_strncasecmp("vector", element, 4) == 0) {
+ fprintf(fp, "\"import_descr\": {\"source\":\"%s\", \"type\":\"vector\"},\n ", tokens[1]);
+ has_import = 1;
+ }
+ }
+
+ fprintf(fp, "\"param\": \"%s\", ", opt->key);
+ /* In case of import the mapset must be removed always */
+ fprintf(fp, "\"value\": \"%s\"", check_mapset_in_layer_name(tokens[0], has_import));
+ fprintf(fp, "}");
+
+ G_free_tokens(tokens);
+}
+
+/* \brief Check the provided answer and generate the export statement
+ dependent on the element type (cell, vector, grid3, file)
+
+ "outputs": [
+ {"export": {"type": "file", "format": "TXT"},
+ "param": "output",
+ "value": "$file::out1"},
+ {'export': {'format': 'GTiff', 'type': 'raster'},
+ 'param': 'map',
+ 'value': 'LT52170762005240COA00_dos1.1'}
+ ]
+ */
+void check_create_export_opts(struct Option *opt, char *element, FILE *fp)
+{
+ int i = 0;
+ int has_file_export = 0;
+ char **tokens;
+
+ tokens = G_tokenize(opt->answer, "+");
+ while (tokens[i]) {
+ G_chop(tokens[i]);
+ i++;
+ }
+
+ fprintf(fp, " {");
+
+ if (i > 1) {
+ if (G_strncasecmp("cell", element, 4) == 0) {
+ fprintf(fp, "\"export\": {\"format\":\"%s\", \"type\":\"raster\"},\n ", tokens[1]);
+ }
+ else if (G_strncasecmp("file", element, 4) == 0) {
+ fprintf(fp, "\"export\": {\"format\":\"%s\", \"type\":\"file\"},\n ", tokens[1]);
+ has_file_export = 1;
+ }
+ else if (G_strncasecmp("vector", element, 4) == 0) {
+ fprintf(fp, "\"export\": {\"format\":\"%s\", \"type\":\"vector\"},\n ", tokens[1]);
+ }
+ }
+
+ fprintf(fp, "\"param\": \"%s\", ", opt->key);
+ if (has_file_export == 1) {
+ fprintf(fp, "\"value\": \"$file::%s\"", check_mapset_in_layer_name(tokens[0], 1));
+ } else {
+ fprintf(fp, "\"value\": \"%s\"", check_mapset_in_layer_name(tokens[0], 1));
+ }
+ fprintf(fp, "}");
+
+ G_free_tokens(tokens);
+}
+
+/*
+ \brief Check if the current mapset is present in the layer name and remove it
+
+ The flag always_remove tells this function to always remove all mapset names.
+
+ \return pointer to the layer name without the current mapset
+*/
+char *check_mapset_in_layer_name(char *layer_name, int always_remove)
+{
+ int i = 0;
+ char **tokens;
+ char *mapset;
+
+ mapset = G_mapset();
+
+ tokens = G_tokenize(layer_name, "@");
+
+ while (tokens[i]) {
+ G_chop(tokens[i]);
+ /* fprintf(stderr, "Token %i: %s\n", i, tokens[i]); */
+ i++;
+ }
+
+ if (always_remove == 1)
+ return tokens[0];
+
+ if (i > 1 && G_strcasecmp(mapset, tokens[1]) == 0)
+ return tokens[0];
+
+ return layer_name;
+}
Modified: grass/trunk/lib/gis/parser_local_proto.h
===================================================================
--- grass/trunk/lib/gis/parser_local_proto.h 2018-10-12 09:22:17 UTC (rev 73524)
+++ grass/trunk/lib/gis/parser_local_proto.h 2018-10-12 18:38:25 UTC (rev 73525)
@@ -4,6 +4,8 @@
#include <stdio.h>
#include <grass/gis.h>
+#define KEYLENGTH 64
+
struct Item
{
struct Option *option;
@@ -53,9 +55,11 @@
void G__usage_rest(void);
void G__usage_text(void);
void G__script(void);
+char *G__json(void);
void G__wps_print_process_description(void);
int G__uses_new_gisprompt(void);
void G__print_keywords(FILE *, void (*)(FILE *, const char *));
+void G__split_gisprompt(const char *, char *, char *, char *);
void G__check_option_rules(void);
void G__describe_option_rules(void);
More information about the grass-commit
mailing list