diff --git a/raster/r.info/Makefile b/raster/r.info/Makefile index 3cfdfce125a..a04fa7e4749 100644 --- a/raster/r.info/Makefile +++ b/raster/r.info/Makefile @@ -2,7 +2,7 @@ MODULE_TOPDIR = ../.. PGM = r.info -LIBES = $(RASTERLIB) $(GISLIB) +LIBES = $(RASTERLIB) $(GISLIB) $(PARSONLIB) DEPENDENCIES = $(RASTERDEP) $(GISDEP) include $(MODULE_TOPDIR)/include/Make/Module.make diff --git a/raster/r.info/main.c b/raster/r.info/main.c index b70135225a7..ed06f72eba7 100644 --- a/raster/r.info/main.c +++ b/raster/r.info/main.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "local_proto.h" #define printline(x) fprintf(out, " | %-74.74s |\n", x) @@ -34,6 +35,8 @@ fprintf(out, "-"); \ fprintf(out, "%c\n", x) +enum OutputFormat { PLAIN, JSON }; + /* local prototypes */ static void format_double(const double, char *); static void compose_line(FILE *, const char *, ...); @@ -42,7 +45,7 @@ int main(int argc, char **argv) { const char *name, *mapset; const char *title; - char tmp1[100], tmp2[100], tmp3[100]; + char tmp1[100], tmp2[100], tmp3[100], tmp4[100]; char timebuff[256]; char *units, *vdatum, *semantic_label; int i; @@ -61,8 +64,12 @@ int main(int argc, char **argv) RASTER_MAP_TYPE data_type; struct Reclass reclass; struct GModule *module; - struct Option *opt1; + struct Option *opt1, *fopt; struct Flag *gflag, *rflag, *eflag, *hflag, *sflag; + enum OutputFormat format; + + JSON_Value *root_value; + JSON_Object *root_object; /* Initialize GIS Engine */ G_gisinit(argv[0]); @@ -98,6 +105,9 @@ int main(int argc, char **argv) hflag->key = 'h'; hflag->description = _("Print raster history instead of info"); + fopt = G_define_standard_option(G_OPT_F_FORMAT); + fopt->guisection = _("Print"); + if (G_parser(argc, argv)) exit(EXIT_FAILURE); @@ -110,6 +120,15 @@ int main(int argc, char **argv) if ((mapset = G_find_raster2(name, "")) == NULL) G_fatal_error(_("Raster map <%s> not found"), name); + if (strcmp(fopt->answer, "json") == 0) { + format = JSON; + root_value = json_value_init_object(); + root_object = json_value_get_object(root_value); + } + else { + format = PLAIN; + } + Rast_get_cellhd(name, "", &cellhd); cats_ok = Rast_read_cats(name, "", &cats) >= 0; title = Rast_get_cats_title(&cats); @@ -152,7 +171,7 @@ int main(int argc, char **argv) } if (!gflag->answer && !rflag->answer && !sflag->answer && !eflag->answer && - !hflag->answer) { + !hflag->answer && format == PLAIN) { divider('+'); compose_line(out, "Map: %-29.29s Date: %s", name, @@ -363,7 +382,7 @@ int main(int argc, char **argv) else { /* g, r, s, e, or h flags */ int need_range, have_range, need_stats, have_stats; - need_range = rflag->answer; + need_range = rflag->answer || format == JSON; need_stats = sflag->answer; if (need_stats) need_range = 1; @@ -440,52 +459,96 @@ int main(int argc, char **argv) } } - if (gflag->answer) { - G_format_northing(cellhd.north, tmp1, -1); - G_format_northing(cellhd.south, tmp2, -1); - fprintf(out, "north=%s\n", tmp1); - fprintf(out, "south=%s\n", tmp2); - - G_format_easting(cellhd.east, tmp1, -1); - G_format_easting(cellhd.west, tmp2, -1); - fprintf(out, "east=%s\n", tmp1); - fprintf(out, "west=%s\n", tmp2); - - G_format_resolution(cellhd.ns_res, tmp3, -1); - fprintf(out, "nsres=%s\n", tmp3); - - G_format_resolution(cellhd.ew_res, tmp3, -1); - fprintf(out, "ewres=%s\n", tmp3); - - fprintf(out, "rows=%d\n", cellhd.rows); - fprintf(out, "cols=%d\n", cellhd.cols); - - fprintf(out, "cells=%" PRId64 "\n", - (grass_int64)cellhd.rows * cellhd.cols); - - fprintf(out, "datatype=%s\n", - (data_type == CELL_TYPE - ? "CELL" - : (data_type == DCELL_TYPE - ? "DCELL" - : (data_type == FCELL_TYPE ? "FCELL" : "??")))); - if (cats_ok) - format_double((double)cats.num, tmp1); - fprintf(out, "ncats=%s\n", cats_ok ? tmp1 : "??"); + if (gflag->answer || format == JSON) { + const char *data_type_f = + (data_type == CELL_TYPE + ? "CELL" + : (data_type == DCELL_TYPE + ? "DCELL" + : (data_type == FCELL_TYPE ? "FCELL" : "??"))); + grass_int64 total_cells = (grass_int64)cellhd.rows * cellhd.cols; + + switch (format) { + case PLAIN: + G_format_northing(cellhd.north, tmp1, -1); + G_format_northing(cellhd.south, tmp2, -1); + fprintf(out, "north=%s\n", tmp1); + fprintf(out, "south=%s\n", tmp2); + + G_format_easting(cellhd.east, tmp1, -1); + G_format_easting(cellhd.west, tmp2, -1); + fprintf(out, "east=%s\n", tmp1); + fprintf(out, "west=%s\n", tmp2); + + G_format_resolution(cellhd.ns_res, tmp3, -1); + fprintf(out, "nsres=%s\n", tmp3); + + G_format_resolution(cellhd.ew_res, tmp3, -1); + fprintf(out, "ewres=%s\n", tmp3); + + fprintf(out, "rows=%d\n", cellhd.rows); + fprintf(out, "cols=%d\n", cellhd.cols); + + fprintf(out, "cells=%" PRId64 "\n", total_cells); + + fprintf(out, "datatype=%s\n", data_type_f); + + if (cats_ok) + format_double((double)cats.num, tmp4); + fprintf(out, "ncats=%s\n", cats_ok ? tmp4 : "??"); + break; + case JSON: + json_object_set_number(root_object, "north", cellhd.north); + json_object_set_number(root_object, "south", cellhd.south); + json_object_set_number(root_object, "nsres", cellhd.ns_res); + + json_object_set_number(root_object, "east", cellhd.east); + json_object_set_number(root_object, "west", cellhd.west); + json_object_set_number(root_object, "ewres", cellhd.ew_res); + + json_object_set_number(root_object, "rows", cellhd.rows); + json_object_set_number(root_object, "cols", cellhd.cols); + json_object_set_number(root_object, "cells", total_cells); + + json_object_set_string(root_object, "datatype", data_type_f); + if (cats_ok) { + json_object_set_number(root_object, "ncats", cats.num); + } + else { + json_object_set_null(root_object, "ncats"); + } + break; + } } - if (rflag->answer || sflag->answer) { + if (rflag->answer || sflag->answer || format == JSON) { if (data_type == CELL_TYPE) { CELL min, max; Rast_get_range_min_max(&crange, &min, &max); if (Rast_is_c_null_value(&min)) { - fprintf(out, "min=NULL\n"); - fprintf(out, "max=NULL\n"); + switch (format) { + case PLAIN: + fprintf(out, "min=NULL\n"); + fprintf(out, "max=NULL\n"); + break; + case JSON: + json_object_set_null(root_object, "min"); + json_object_set_null(root_object, "max"); + break; + } } else { - fprintf(out, "min=%i\n", min); - fprintf(out, "max=%i\n", max); + switch (format) { + case PLAIN: + fprintf(out, "min=%i\n", min); + fprintf(out, "max=%i\n", max); + break; + case JSON: + json_object_set_number(root_object, "min", min); + json_object_set_number(root_object, "max", max); + break; + } } } else { @@ -493,17 +556,33 @@ int main(int argc, char **argv) Rast_get_fp_range_min_max(&range, &min, &max); if (Rast_is_d_null_value(&min)) { - fprintf(out, "min=NULL\n"); - fprintf(out, "max=NULL\n"); + switch (format) { + case PLAIN: + fprintf(out, "min=NULL\n"); + fprintf(out, "max=NULL\n"); + break; + case JSON: + json_object_set_null(root_object, "min"); + json_object_set_null(root_object, "max"); + break; + } } else { - if (data_type == FCELL_TYPE) { - fprintf(out, "min=%.7g\n", min); - fprintf(out, "max=%.7g\n", max); - } - else { - fprintf(out, "min=%.15g\n", min); - fprintf(out, "max=%.15g\n", max); + switch (format) { + case PLAIN: + if (data_type == FCELL_TYPE) { + fprintf(out, "min=%.7g\n", min); + fprintf(out, "max=%.7g\n", max); + } + else { + fprintf(out, "min=%.15g\n", min); + fprintf(out, "max=%.15g\n", max); + } + break; + case JSON: + json_object_set_number(root_object, "min", min); + json_object_set_number(root_object, "max", max); + break; } } } @@ -512,9 +591,17 @@ int main(int argc, char **argv) if (sflag->answer) { if (!gflag->answer) { + grass_int64 total_cells = + (grass_int64)cellhd.rows * cellhd.cols; /* always report total number of cells */ - fprintf(out, "cells=%" PRId64 "\n", - (grass_int64)cellhd.rows * cellhd.cols); + switch (format) { + case PLAIN: + fprintf(out, "cells=%" PRId64 "\n", total_cells); + break; + case JSON: + json_object_set_number(root_object, "cells", total_cells); + break; + } } if (rstats.count > 0) { @@ -542,83 +629,227 @@ int main(int argc, char **argv) } } - fprintf(out, "n=%" PRId64 "\n", rstats.count); - fprintf(out, "mean=%.15g\n", mean); - fprintf(out, "stddev=%.15g\n", sd); - fprintf(out, "sum=%.15g\n", rstats.sum); + switch (format) { + case PLAIN: + fprintf(out, "n=%" PRId64 "\n", rstats.count); + fprintf(out, "mean=%.15g\n", mean); + fprintf(out, "stddev=%.15g\n", sd); + fprintf(out, "sum=%.15g\n", rstats.sum); + break; + case JSON: + json_object_set_number(root_object, "n", rstats.count); + json_object_set_number(root_object, "mean", mean); + json_object_set_number(root_object, "stddev", sd); + json_object_set_number(root_object, "sum", rstats.sum); + break; + } } else { - fprintf(out, "n=0\n"); - fprintf(out, "mean=NULL\n"); - fprintf(out, "stddev=NULL\n"); - fprintf(out, "sum=NULL\n"); + switch (format) { + case PLAIN: + fprintf(out, "n=0\n"); + fprintf(out, "mean=NULL\n"); + fprintf(out, "stddev=NULL\n"); + fprintf(out, "sum=NULL\n"); + break; + case JSON: + json_object_set_number(root_object, "n", 0); + json_object_set_null(root_object, "mean"); + json_object_set_null(root_object, "stddev"); + json_object_set_null(root_object, "sum"); + break; + } } } - if (eflag->answer) { + if (eflag->answer || format == JSON) { char xname[GNAME_MAX], xmapset[GMAPSET_MAX]; + const char *maptype, *date, *creator; G_unqualified_name(name, mapset, xname, xmapset); - fprintf(out, "map=%s\n", xname); - fprintf(out, "maptype=%s\n", - hist_ok ? Rast_get_history(&hist, HIST_MAPTYPE) : "??"); - fprintf(out, "mapset=%s\n", mapset); - fprintf(out, "location=%s\n", G_location()); - fprintf(out, "project=%s\n", G_location()); - fprintf(out, "database=%s\n", G_gisdbase()); - fprintf(out, "date=\"%s\"\n", - hist_ok ? Rast_get_history(&hist, HIST_MAPID) : "??"); - fprintf(out, "creator=\"%s\"\n", - hist_ok ? Rast_get_history(&hist, HIST_CREATOR) : "??"); - fprintf(out, "title=\"%s\"\n", title); + maptype = hist_ok ? Rast_get_history(&hist, HIST_MAPTYPE) : "??"; + date = hist_ok ? Rast_get_history(&hist, HIST_MAPID) : "??"; + creator = hist_ok ? Rast_get_history(&hist, HIST_CREATOR) : "??"; + + switch (format) { + case PLAIN: + fprintf(out, "map=%s\n", xname); + fprintf(out, "maptype=%s\n", maptype); + fprintf(out, "mapset=%s\n", mapset); + fprintf(out, "location=%s\n", G_location()); + fprintf(out, "project=%s\n", G_location()); + fprintf(out, "database=%s\n", G_gisdbase()); + fprintf(out, "date=\"%s\"\n", date); + fprintf(out, "creator=\"%s\"\n", creator); + fprintf(out, "title=\"%s\"\n", title); + break; + case JSON: + json_object_set_string(root_object, "map", name); + json_object_set_string(root_object, "maptype", maptype); + json_object_set_string(root_object, "mapset", mapset); + json_object_set_string(root_object, "location", G_location()); + json_object_set_string(root_object, "project", G_location()); + json_object_set_string(root_object, "database", G_gisdbase()); + json_object_set_string(root_object, "date", date); + json_object_set_string(root_object, "creator", creator); + json_object_set_string(root_object, "title", title); + break; + } if (time_ok && (first_time_ok || second_time_ok)) { G_format_timestamp(&ts, timebuff); - - /*Create the r.info timestamp string */ - fprintf(out, "timestamp=\"%s\"\n", timebuff); + switch (format) { + case PLAIN: + /*Create the r.info timestamp string */ + fprintf(out, "timestamp=\"%s\"\n", timebuff); + break; + case JSON: + json_object_set_string(root_object, "timestamp", timebuff); + break; + } } else { - fprintf(out, "timestamp=\"none\"\n"); + switch (format) { + case PLAIN: + fprintf(out, "timestamp=\"none\"\n"); + break; + case JSON: + json_object_set_null(root_object, "timestamp"); + break; + } } - fprintf(out, "units=%s\n", units ? units : "\"none\""); - fprintf(out, "vdatum=%s\n", vdatum ? vdatum : "\"none\""); - fprintf(out, "semantic_label=%s\n", - semantic_label ? semantic_label : "\"none\""); - fprintf(out, "source1=\"%s\"\n", - hist_ok ? Rast_get_history(&hist, HIST_DATSRC_1) - : "\"none\""); - fprintf(out, "source2=\"%s\"\n", - hist_ok ? Rast_get_history(&hist, HIST_DATSRC_2) - : "\"none\""); - fprintf(out, "description=\"%s\"\n", - hist_ok ? Rast_get_history(&hist, HIST_KEYWRD) - : "\"none\""); - if (Rast_history_length(&hist)) { - fprintf(out, "comments=\""); - for (i = 0; i < Rast_history_length(&hist); i++) - fprintf(out, "%s", Rast_history_line(&hist, i)); - fprintf(out, "\"\n"); + + switch (format) { + case PLAIN: + fprintf(out, "units=%s\n", units ? units : "\"none\""); + fprintf(out, "vdatum=%s\n", vdatum ? vdatum : "\"none\""); + fprintf(out, "semantic_label=%s\n", + semantic_label ? semantic_label : "\"none\""); + fprintf(out, "source1=\"%s\"\n", + hist_ok ? Rast_get_history(&hist, HIST_DATSRC_1) + : "\"none\""); + fprintf(out, "source2=\"%s\"\n", + hist_ok ? Rast_get_history(&hist, HIST_DATSRC_2) + : "\"none\""); + fprintf(out, "description=\"%s\"\n", + hist_ok ? Rast_get_history(&hist, HIST_KEYWRD) + : "\"none\""); + if (Rast_history_length(&hist)) { + fprintf(out, "comments=\""); + for (i = 0; i < Rast_history_length(&hist); i++) + fprintf(out, "%s", Rast_history_line(&hist, i)); + fprintf(out, "\"\n"); + } + break; + case JSON: + if (units) { + json_object_set_string(root_object, "units", units); + } + else { + json_object_set_null(root_object, "units"); + } + if (vdatum) { + json_object_set_string(root_object, "vdatum", vdatum); + } + else { + json_object_set_null(root_object, "vdatum"); + } + if (semantic_label) { + json_object_set_string(root_object, "semantic_label", + semantic_label); + } + else { + json_object_set_null(root_object, "semantic_label"); + } + + if (hist_ok) { + json_object_set_string( + root_object, "source1", + Rast_get_history(&hist, HIST_DATSRC_1)); + json_object_set_string( + root_object, "source2", + Rast_get_history(&hist, HIST_DATSRC_2)); + json_object_set_string( + root_object, "description", + Rast_get_history(&hist, HIST_KEYWRD)); + JSON_Value *comments_value = json_value_init_array(); + JSON_Array *comments = json_array(comments_value); + if (Rast_history_length(&hist)) { + for (i = 0; i < Rast_history_length(&hist); i++) { + json_array_append_string( + comments, Rast_history_line(&hist, i)); + } + } + json_object_set_value(root_object, "comments", + comments_value); + } + else { + json_object_set_null(root_object, "source1"); + json_object_set_null(root_object, "source2"); + json_object_set_null(root_object, "description"); + json_object_set_null(root_object, "comments"); + } + break; } } - if (hflag->answer) { + if (hflag->answer || format == JSON) { if (hist_ok) { - fprintf(out, "Data Source:\n"); - fprintf(out, " %s\n", Rast_get_history(&hist, HIST_DATSRC_1)); - fprintf(out, " %s\n", Rast_get_history(&hist, HIST_DATSRC_2)); - fprintf(out, "Data Description:\n"); - fprintf(out, " %s\n", Rast_get_history(&hist, HIST_KEYWRD)); - if (Rast_history_length(&hist)) { - fprintf(out, "Comments:\n"); - for (i = 0; i < Rast_history_length(&hist); i++) - fprintf(out, " %s\n", Rast_history_line(&hist, i)); + switch (format) { + case PLAIN: + fprintf(out, "Data Source:\n"); + fprintf(out, " %s\n", + Rast_get_history(&hist, HIST_DATSRC_1)); + fprintf(out, " %s\n", + Rast_get_history(&hist, HIST_DATSRC_2)); + fprintf(out, "Data Description:\n"); + fprintf(out, " %s\n", + Rast_get_history(&hist, HIST_KEYWRD)); + if (Rast_history_length(&hist)) { + fprintf(out, "Comments:\n"); + for (i = 0; i < Rast_history_length(&hist); i++) + fprintf(out, " %s\n", + Rast_history_line(&hist, i)); + } + break; + case JSON: + json_object_set_string( + root_object, "source1", + Rast_get_history(&hist, HIST_DATSRC_1)); + json_object_set_string( + root_object, "source2", + Rast_get_history(&hist, HIST_DATSRC_2)); + json_object_set_string( + root_object, "description", + Rast_get_history(&hist, HIST_KEYWRD)); + JSON_Value *comments_value = json_value_init_array(); + JSON_Array *comments = json_array(comments_value); + if (Rast_history_length(&hist)) { + for (i = 0; i < Rast_history_length(&hist); i++) { + json_array_append_string( + comments, Rast_history_line(&hist, i)); + } + } + json_object_set_value(root_object, "comments", + comments_value); + break; } } } } /* else rflag or sflag or tflag or gflag or hflag or mflag */ + 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); + } + return EXIT_SUCCESS; } diff --git a/raster/r.info/r.info.html b/raster/r.info/r.info.html index e0ce6ff5bb3..8cc7b1a429f 100644 --- a/raster/r.info/r.info.html +++ b/raster/r.info/r.info.html @@ -124,6 +124,51 @@

EXAMPLES

comments="slope map elev = elev_ned10mzfactor = 1.00 format = degreesmin_slp_allowed = 0.000000" +

+Finally, the output from r.info can be output in JSON by passing the format=json option. + +

+r.info slope format=json
+
+
+{
+    "north": 228500,
+    "south": 215000,
+    "nsres": 10,
+    "east": 645000,
+    "west": 630000,
+    "ewres": 10,
+    "rows": 1350,
+    "cols": 1500,
+    "cells": 2025000,
+    "datatype": "FCELL",
+    "ncats": 255,
+    "min": 0,
+    "max": 38.68939208984375,
+    "map": "slope",
+    "maptype": "raster",
+    "mapset": "PERMANENT",
+    "location": "nc_spm_08_grass7",
+    "project": "nc_spm_08_grass7",
+    "database": "/grassdata",
+    "date": "Tue Nov  7 01:11:23 2006",
+    "creator": "helena",
+    "title": "South-West Wake county: slope in degrees",
+    "timestamp": null,
+    "units": null,
+    "vdatum": null,
+    "semantic_label": null,
+    "source1": "raster elevation file elev_ned10m",
+    "source2": "",
+    "description": "generated by r.slope.aspect",
+    "comments": [
+        "slope map elev = elev_ned10m",
+        "zfactor = 1.00 format = degrees",
+        "min_slp_allowed = 0.000000"
+    ]
+}
+
+

SEE ALSO

diff --git a/raster/r.info/testsuite/test_r_info.py b/raster/r.info/testsuite/test_r_info.py index a4d554232dd..49f91a5723f 100644 --- a/raster/r.info/testsuite/test_r_info.py +++ b/raster/r.info/testsuite/test_r_info.py @@ -5,10 +5,12 @@ Author: Sunveer Singh, Google Code-in 2017 Copyright: (C) 2017 by Sunveer Singh and the GRASS Development Team Licence: This program is free software under the GNU General Public - License (>=v2). Read the file COPYING that comes with GRASS - for details. + License (>=v2). Read the file COPYING that comes with GRASS + for details. """ +import json + from grass.gunittest.case import TestCase from grass.gunittest.gmodules import SimpleModule @@ -18,6 +20,35 @@ class TestReport(TestCase): def setUpClass(cls): """Use temporary region settings""" cls.use_temp_region() + cls.json_format_expected_no_stats = { + "north": 228500, + "south": 215000, + "east": 645000, + "west": 630000, + "nsres": 10, + "ewres": 10, + "rows": 1350, + "cols": 1500, + "min": 34300, + "max": 43600, + "cells": 2025000, + "datatype": "CELL", + "ncats": 43600, + "map": "lakes", + "maptype": "raster", + "mapset": "PERMANENT", + "date": "Fri Jan 19 23:49:34 2007", + "creator": "helena", + "title": "South-West Wake county: Wake county lakes", + "timestamp": None, + "units": None, + "vdatum": None, + "semantic_label": None, + "source1": "", + "source2": "", + "description": "generated by r.mapcalc", + "comments": ["1 * lakes_large"], + } @classmethod def tearDownClass(cls): @@ -67,6 +98,43 @@ def test_flagh(self): """Testing flag h with map zipcodes""" self.assertModule("r.info", map="lakes", flags="h") + def _test_format_json_helper(self, module, expected): + self.runModule(module) + result = json.loads(module.outputs.stdout) + + # the following fields vary with the Grass sample data's path + # therefore only check for their presence in the JSON output + # and not exact values + remove_fields = ["location", "project", "database"] + for field in remove_fields: + self.assertIn(field, result) + result.pop(field) + + self.assertCountEqual(list(expected.keys()), list(result.keys())) + + for key, value in expected.items(): + if isinstance(value, float): + self.assertAlmostEqual(value, result[key]) + else: + self.assertEqual(value, result[key]) + + def test_format_json(self): + """Testing using simple module in json format""" + module = SimpleModule("r.info", map="lakes", flags="g", format="json") + self._test_format_json_helper(module, self.json_format_expected_no_stats) + + def test_sflag_format_json(self): + """Testing using simple module in json format with sflag""" + expected_json_with_stats = { + **self.json_format_expected_no_stats, + "n": 36011, + "sum": 1404513600, + "stddev": 739.7965366431155, + "mean": 39002.349282163785, + } + module = SimpleModule("r.info", map="lakes", flags="gs", format="json") + self._test_format_json_helper(module, expected_json_with_stats) + if __name__ == "__main__": from grass.gunittest.main import test