diff --git a/raster/r.what/Makefile b/raster/r.what/Makefile index 8cf21eb01bf..e30ba7d7003 100644 --- a/raster/r.what/Makefile +++ b/raster/r.what/Makefile @@ -2,7 +2,7 @@ MODULE_TOPDIR = ../.. PGM = r.what -LIBES = $(RASTERLIB) $(GISLIB) $(VECTORLIB) +LIBES = $(PARSONLIB) $(RASTERLIB) $(GISLIB) $(VECTORLIB) DEPENDENCIES = $(RASTERDEP) $(GISDEP) $(VECTORDEP) EXTRA_INC = $(VECT_INC) EXTRA_CFLAGS = $(VECT_CFLAGS) diff --git a/raster/r.what/main.c b/raster/r.what/main.c index 7bb50721e5d..0698b6ffbd6 100644 --- a/raster/r.what/main.c +++ b/raster/r.what/main.c @@ -29,6 +29,7 @@ #include #include #include +#include struct order { int point; @@ -49,6 +50,8 @@ static int by_point(const void *, const void *); static int tty = 0; +enum OutputFormat { PLAIN, JSON }; + int main(int argc, char *argv[]) { int i, j; @@ -74,7 +77,8 @@ int main(int argc, char *argv[]) char buffer[1024]; char **ptr; struct _opt { - struct Option *input, *cache, *null, *coords, *fs, *points, *output; + struct Option *input, *cache, *null, *coords, *fs, *points, *output, + *format; } opt; struct _flg { struct Flag *label, *cache, *cat_int, *color, *header, *cat; @@ -93,6 +97,12 @@ int main(int argc, char *argv[]) int red, green, blue; struct GModule *module; + JSON_Value *root_value = NULL, *point_value, *layer_value; + JSON_Array *root_array; + JSON_Object *point_object, *layer_object; + + enum OutputFormat format; + G_gisinit(argv[0]); /* Set description */ @@ -130,6 +140,17 @@ int main(int argc, char *argv[]) opt.fs = G_define_standard_option(G_OPT_F_SEP); opt.fs->guisection = _("Print"); + opt.format = G_define_option(); + opt.format->key = "format"; + opt.format->type = TYPE_STRING; + opt.format->required = NO; + opt.format->label = _("Output format"); + opt.format->options = "plain,json"; + opt.format->descriptions = "plain;Plain text output;" + "json;JSON (JavaScript Object Notation);"; + opt.format->answer = "plain"; + opt.format->guisection = _("Print"); + opt.cache = G_define_option(); opt.cache->key = "cache"; opt.cache->type = TYPE_INTEGER; @@ -255,8 +276,21 @@ int main(int argc, char *argv[]) Cats = Vect_new_cats_struct(); G_get_window(&window); + if (strcmp(opt.format->answer, "json") == 0) + format = JSON; + else + format = PLAIN; + + if (format == JSON) { + root_value = json_value_init_array(); + if (root_value == NULL) { + G_fatal_error(_("Failed to initialize JSON array. Out of memory?")); + } + root_array = json_array(root_value); + } + /* print header row */ - if (flg.header->answer) { + if (format == PLAIN && flg.header->answer) { if (flg.cat->answer) { fprintf(stdout, "cat%s", fs); } @@ -466,56 +500,112 @@ int main(int argc, char *argv[]) qsort(cache, point_cnt, sizeof(struct order), by_point); /* report data from re-ordered cache */ - for (point = 0; point < point_cnt; point++) { G_debug(1, "%s|%s at col %d, row %d\n", cache[point].east_buf, cache[point].north_buf, cache[point].col, cache[point].row); - if (flg.cat->answer) { - fprintf(stdout, "%d%s", cache[point].cat, fs); + if (format == PLAIN) { + + if (flg.cat->answer) { + fprintf(stdout, "%d%s", cache[point].cat, fs); + } + fprintf(stdout, "%s%s%s%s%s", cache[point].east_buf, fs, + cache[point].north_buf, fs, cache[point].lab_buf); + + for (i = 0; i < nfiles; i++) { + if (out_type[i] == CELL_TYPE) { + if (Rast_is_c_null_value(&cache[point].value[i])) { + fprintf(stdout, "%s%s", fs, null_str); + if (flg.label->answer) + fprintf(stdout, "%s", fs); + if (flg.color->answer) + fprintf(stdout, "%s", fs); + continue; + } + fprintf(stdout, "%s%ld", fs, + (long)cache[point].value[i]); + cache[point].dvalue[i] = cache[point].value[i]; + } + else { /* FCELL or DCELL */ + + if (Rast_is_d_null_value(&cache[point].dvalue[i])) { + fprintf(stdout, "%s%s", fs, null_str); + if (flg.label->answer) + fprintf(stdout, "%s", fs); + if (flg.color->answer) + fprintf(stdout, "%s", fs); + continue; + } + if (out_type[i] == FCELL_TYPE) + sprintf(tmp_buf, "%.7g", cache[point].dvalue[i]); + else /* DCELL */ + sprintf(tmp_buf, "%.15g", cache[point].dvalue[i]); + G_trim_decimal(tmp_buf); /* not needed with %g? */ + fprintf(stdout, "%s%s", fs, tmp_buf); + } + if (flg.label->answer) + fprintf(stdout, "%s%s", fs, + Rast_get_d_cat(&(cache[point].dvalue[i]), + &cats[i])); + if (flg.color->answer) + fprintf(stdout, "%s%s", fs, cache[point].clr_buf[i]); + } + fprintf(stdout, "\n"); } - fprintf(stdout, "%s%s%s%s%s", cache[point].east_buf, fs, - cache[point].north_buf, fs, cache[point].lab_buf); + else { + point_value = json_value_init_object(); + point_object = json_object(point_value); - for (i = 0; i < nfiles; i++) { - if (out_type[i] == CELL_TYPE) { - if (Rast_is_c_null_value(&cache[point].value[i])) { - fprintf(stdout, "%s%s", fs, null_str); + if (flg.cat->answer) { + json_object_set_number(point_object, "cat", + cache[point].cat); + } + + json_object_set_number(point_object, "easting", + atof(cache[point].east_buf)); + json_object_set_number(point_object, "northing", + atof(cache[point].north_buf)); + json_object_set_string(point_object, "site_name", + cache[point].lab_buf); + + for (i = 0; i < nfiles; i++) { + layer_value = json_value_init_object(); + layer_object = json_object(layer_value); + + if (Rast_is_c_null_value(&cache[point].value[i]) || + Rast_is_d_null_value(&cache[point].dvalue[i])) { + json_object_set_null(layer_object, "value"); if (flg.label->answer) - fprintf(stdout, "%s", fs); + json_object_set_null(layer_object, "label"); if (flg.color->answer) - fprintf(stdout, "%s", fs); - continue; + json_object_set_null(layer_object, "color"); } - fprintf(stdout, "%s%ld", fs, (long)cache[point].value[i]); - cache[point].dvalue[i] = cache[point].value[i]; - } - else { /* FCELL or DCELL */ - - if (Rast_is_d_null_value(&cache[point].dvalue[i])) { - fprintf(stdout, "%s%s", fs, null_str); + else { + if (out_type[i] == CELL_TYPE) { + json_object_set_number(layer_object, "value", + (long)cache[point].value[i]); + cache[point].dvalue[i] = cache[point].value[i]; + } + else { /* FCELL or DCELL */ + json_object_set_number(layer_object, "value", + cache[point].dvalue[i]); + } if (flg.label->answer) - fprintf(stdout, "%s", fs); + json_object_set_string( + layer_object, "label", + Rast_get_d_cat(&(cache[point].dvalue[i]), + &cats[i])); if (flg.color->answer) - fprintf(stdout, "%s", fs); - continue; + json_object_set_string(layer_object, "color", + cache[point].clr_buf[i]); } - if (out_type[i] == FCELL_TYPE) - sprintf(tmp_buf, "%.7g", cache[point].dvalue[i]); - else /* DCELL */ - sprintf(tmp_buf, "%.15g", cache[point].dvalue[i]); - G_trim_decimal(tmp_buf); /* not needed with %g? */ - fprintf(stdout, "%s%s", fs, tmp_buf); + + json_object_set_value(point_object, opt.input->answers[i], + layer_value); } - if (flg.label->answer) - fprintf( - stdout, "%s%s", fs, - Rast_get_d_cat(&(cache[point].dvalue[i]), &cats[i])); - if (flg.color->answer) - fprintf(stdout, "%s%s", fs, cache[point].clr_buf[i]); + json_array_append_value(root_array, point_value); } - fprintf(stdout, "\n"); } if (cache_report & !tty) @@ -527,6 +617,17 @@ int main(int argc, char *argv[]) cache_hit = cache_miss = 0; } + if (format == JSON) { + char *serialized_string = NULL; + serialized_string = json_serialize_to_string_pretty(root_value); + if (serialized_string == NULL) { + G_fatal_error(_("Failed to initialize pretty JSON string.")); + } + puts(serialized_string); + json_free_serialized_string(serialized_string); + json_value_free(root_value); + } + if (!opt.coords->answers && !opt.points->answers && tty) fprintf(stderr, "\n"); if (cache_report & !tty) diff --git a/raster/r.what/testsuite/test_r_what.py b/raster/r.what/testsuite/test_r_what.py index 12a8be2f90d..fcc312e6284 100644 --- a/raster/r.what/testsuite/test_r_what.py +++ b/raster/r.what/testsuite/test_r_what.py @@ -12,6 +12,7 @@ from grass.gunittest.case import TestCase from grass.gunittest.gmodules import SimpleModule import os +import json class TestRasterWhat(TestCase): @@ -437,6 +438,25 @@ class TestRasterWhat(TestCase): 332533.5941495|242831.139883875||121 """ + @staticmethod + def convert_plain_to_json(plain): + data = [] + lines = plain.split("\n") + for line in lines: + line = line.strip() + if line: + parts = line.split("|") + item = { + "easting": float(parts[0]), + "northing": float(parts[1]), + "site_name": parts[2], + "boundary_county_500m": {"value": int(parts[3])}, + } + if len(parts) == 5: + item["boundary_county_500m"]["color"] = parts[4] + data.append(item) + return data + @classmethod def setUpClass(cls): cls.use_temp_region() @@ -541,6 +561,32 @@ def test_raster_what_cache(self): msg="test_raster_what_cats did't run successfully", ) + def test_raster_what_json(self): + """Testing r.what runs successfully with input coordinates given as a vector points map and JSON output""" + reference = self.convert_plain_to_json(self.refrence_points) + module = SimpleModule( + "r.what", map=self.map1, points=self.points, format="json" + ) + module.run() + self.assertListEqual( + json.loads(str(module.outputs.stdout)), + reference, + "test_raster_what_points did't run successfully", + ) + + def test_raster_what_points_flag_r_json(self): + """Testing r.what runs successfully with flag r and json output""" + reference = self.convert_plain_to_json(self.refrence_flag_r) + module = SimpleModule( + "r.what", map=self.map1, points=self.points, flags="r", format="json" + ) + module.run() + self.assertListEqual( + json.loads(str(module.outputs.stdout)), + reference, + "test_raster_what_cats did't run successfully", + ) + if __name__ == "__main__": from grass.gunittest.main import test