diff --git a/raster/r.report/Makefile b/raster/r.report/Makefile index 34d6942690f..0bab2b40c4a 100644 --- a/raster/r.report/Makefile +++ b/raster/r.report/Makefile @@ -2,7 +2,7 @@ MODULE_TOPDIR = ../.. PGM = r.report -LIBES = $(RASTERLIB) $(GISLIB) +LIBES = $(RASTERLIB) $(GISLIB) $(PARSONLIB) DEPENDENCIES = $(RASTERDEP) $(GISDEP) include $(MODULE_TOPDIR)/include/Make/Module.make diff --git a/raster/r.report/format.c b/raster/r.report/format.c index 8319fe747ed..41cc3ca6991 100644 --- a/raster/r.report/format.c +++ b/raster/r.report/format.c @@ -73,3 +73,93 @@ int format_double(double v, char *buf, int n, int dp) return 0; } + +void compute_unit_format(int unit1, int unit2, enum OutputFormat format) +{ + int i, ns, len; + char num[100]; + int need_format; + + /* examine units, determine output format */ + for (i = unit1; i <= unit2; i++) { + if (format == PLAIN) { + need_format = 1; + } + else { + need_format = 0; + } + unit[i].label[0] = ""; + unit[i].label[1] = ""; + + switch (unit[i].type) { + case CELL_COUNTS: + unit[i].label[0] = " cell"; + unit[i].label[1] = "count"; + + if (need_format) { + need_format = 0; + unit[i].len = 5; + ns = 0; + snprintf(num, sizeof(num), "%ld", count_sum(&ns, -1)); + len = strlen(num); + if (len > unit[i].len) + unit[i].len = len; + } + break; + + case PERCENT_COVER: + unit[i].label[0] = " % "; + unit[i].label[1] = "cover"; + + if (need_format) { + need_format = 0; + unit[i].dp = 2; + unit[i].len = 6; + unit[i].eformat = 0; + } + break; + + case SQ_METERS: + unit[i].label[0] = "square"; + unit[i].label[1] = "meters"; + unit[i].factor = 1.0; + break; + + case SQ_KILOMETERS: + unit[i].label[0] = " square "; + unit[i].label[1] = "kilometers"; + unit[i].factor = 1.0e-6; + break; + + case ACRES: + unit[i].label[0] = ""; + unit[i].label[1] = "acres"; + unit[i].factor = 2.47105381467165e-4; /* 640 acres in a sq mile */ + break; + + case HECTARES: + unit[i].label[0] = ""; + unit[i].label[1] = "hectares"; + unit[i].factor = 1.0e-4; + break; + + case SQ_MILES: + unit[i].label[0] = "square"; + unit[i].label[1] = " miles"; + unit[i].factor = 3.86102158542446e-7; /* 1 / ( (0.0254m/in * 12in/ft + * 5280ft/mi)^2 ) */ + break; + + default: + G_fatal_error("Unit %d not yet supported", unit[i].type); + } + if (need_format) { + unit[i].dp = 6; + unit[i].len = 10; + unit[i].eformat = 0; + ns = 0; + format_parms(area_sum(&ns, -1) * unit[i].factor, &unit[i].len, + &unit[i].dp, &(unit[i].eformat), e_format); + } + } +} diff --git a/raster/r.report/global.h b/raster/r.report/global.h index 1b23ab6b813..a05ac1ae1d5 100644 --- a/raster/r.report/global.h +++ b/raster/r.report/global.h @@ -6,6 +6,7 @@ #endif #include +#include #define SORT_DEFAULT 0 #define SORT_ASC 1 @@ -52,6 +53,8 @@ extern int nunits; #define DEFAULT_PAGE_LENGTH "0" #define DEFAULT_PAGE_WIDTH "79" +enum OutputFormat { PLAIN, JSON }; + extern int page_width; extern int page_length; extern int masking; @@ -67,6 +70,7 @@ extern char *stats_file; extern char *no_data_str; extern int stats_flag; extern int nsteps, cat_ranges, as_int; +extern enum OutputFormat format; extern int *is_fp; extern DCELL *DMAX, *DMIN; @@ -81,6 +85,7 @@ extern struct Categories *labels; int format_parms(double, int *, int *, int *, int); int scient_format(double, char *, int, int); int format_double(double, char *, int, int); +void compute_unit_format(int, int, enum OutputFormat); /* header.c */ int header(int, int); @@ -112,6 +117,12 @@ char *construct_cat_label(int, CELL); /* prt_unit.c */ int print_unit(int, int, int); +/* prt_json.c */ +JSON_Value *make_units(int, int); +JSON_Value *make_category(int, int, JSON_Value *); +JSON_Value *make_categories(int, int, int); +void print_json(); + /* report.c */ int report(void); diff --git a/raster/r.report/main.c b/raster/r.report/main.c index bb04db163c2..5b13e23411e 100644 --- a/raster/r.report/main.c +++ b/raster/r.report/main.c @@ -52,6 +52,8 @@ int maskfd; CELL *mask; CELL NULL_CELL; +enum OutputFormat format; + char fs[2]; struct Categories *labels; diff --git a/raster/r.report/parse.c b/raster/r.report/parse.c index 2b18491b8a0..44e7bc5dbd3 100644 --- a/raster/r.report/parse.c +++ b/raster/r.report/parse.c @@ -17,6 +17,7 @@ int parse_command_line(int argc, char *argv[]) struct Option *nv; struct Option *nsteps; struct Option *sort; + struct Option *format; } parms; struct { struct Flag *f; @@ -103,6 +104,9 @@ int parse_command_line(int argc, char *argv[]) _("Sort by cell counts in descending order")); parms.sort->guisection = _("Formatting"); + parms.format = G_define_standard_option(G_OPT_F_FORMAT); + parms.format->guisection = _("Formatting"); + flags.h = G_define_flag(); flags.h->key = 'h'; flags.h->description = _("Suppress page headers"); @@ -172,6 +176,13 @@ int parse_command_line(int argc, char *argv[]) cat_ranges = flags.C->answer; as_int = flags.i->answer; + if (strcmp(parms.format->answer, "json") == 0) { + format = JSON; + } + else { + format = PLAIN; + } + for (i = 0; parms.cell->answers[i]; i++) parse_layer(parms.cell->answers[i]); if (parms.units->answers) diff --git a/raster/r.report/prt_json.c b/raster/r.report/prt_json.c new file mode 100644 index 00000000000..94590ea7b9f --- /dev/null +++ b/raster/r.report/prt_json.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include "global.h" +#include +#include + +JSON_Value *make_units(int ns, int nl) +{ + JSON_Value *units_value = json_value_init_array(); + JSON_Array *units_array = json_array(units_value); + for (int i = 0; i < nunits; i++) { + int _ns = ns; + + JSON_Value *unit_value = json_value_init_object(); + JSON_Object *unit_object = json_object(unit_value); + + if (unit[i].type == CELL_COUNTS) { + json_object_set_string(unit_object, "unit", "cell counts"); + json_object_set_number(unit_object, "value", count_sum(&_ns, nl)); + } + else if (unit[i].type == PERCENT_COVER) { + json_object_set_string(unit_object, "unit", "% cover"); + int k = ns - 1; + while (k >= 0 && same_cats(k, ns, nl - 1)) + k--; + k++; + double area = area_sum(&k, nl - 1); + area = 100.0 * area_sum(&_ns, nl) / area; + json_object_set_number(unit_object, "value", area); + } + else { + char *unit_name = NULL; + if (unit[i].type == ACRES) { + unit_name = "acres"; + } + else if (unit[i].type == HECTARES) { + unit_name = "hectares"; + } + else if (unit[i].type == SQ_MILES) { + unit_name = "square miles"; + } + else if (unit[i].type == SQ_METERS) { + unit_name = "square meters"; + } + else if (unit[i].type == SQ_KILOMETERS) { + unit_name = "square kilometers"; + } + json_object_set_string(unit_object, "unit", unit_name); + json_object_set_number(unit_object, "value", + area_sum(&_ns, nl) * unit[i].factor); + } + json_array_append_value(units_array, unit_value); + } + return units_value; +} + +JSON_Value *make_category(int ns, int nl, JSON_Value *sub_categories) +{ + JSON_Value *object_value = json_value_init_object(); + JSON_Object *object = json_object(object_value); + + CELL *cats = Gstats[ns].cats; + json_object_set_number(object, "category", cats[nl]); + + DCELL dLow, dHigh; + + if (!is_fp[nl] || as_int) + json_object_set_string(object, "label", + Rast_get_c_cat(&cats[nl], &layers[nl].labels)); + else { + /* find or construct the label for floating point range to print */ + if (Rast_is_c_null_value(&cats[nl])) + json_object_set_null(object, "label"); + else if (cat_ranges) { + json_object_set_string(object, "label", + Rast_get_ith_d_cat(&layers[nl].labels, + cats[nl], &dLow, &dHigh)); + } + else { + dLow = (DMAX[nl] - DMIN[nl]) / (double)nsteps * + (double)(cats[nl] - 1) + + DMIN[nl]; + dHigh = (DMAX[nl] - DMIN[nl]) / (double)nsteps * (double)cats[nl] + + DMIN[nl]; + + json_object_set_string(object, "label", "from to"); + + JSON_Value *range_value = json_value_init_object(); + JSON_Object *range_object = json_object(range_value); + json_object_set_number(range_object, "from", dLow); + json_object_set_number(range_object, "to", dHigh); + json_object_set_value(object, "range", range_value); + } + } + + JSON_Value *units_value = make_units(ns, nl); + json_object_set_value(object, "units", units_value); + + if (sub_categories != NULL) { + json_object_set_value(object, "categories", sub_categories); + } + return object_value; +} + +JSON_Value *make_categories(int start, int end, int level) +{ + JSON_Value *array_value = json_value_init_array(); + JSON_Array *array = json_array(array_value); + if (level == nlayers - 1) { + for (int i = start; i < end; i++) { + JSON_Value *category = make_category(i, level, NULL); + json_array_append_value(array, category); + } + } + else { + while (start < end) { + int curr = start; + while ((curr < end) && same_cats(start, curr, level)) { + curr++; + } + JSON_Value *sub_categories = + make_categories(start, curr, level + 1); + JSON_Value *category = make_category(start, level, sub_categories); + json_array_append_value(array, category); + start = curr; + } + } + return array_value; +} + +void print_json() +{ + compute_unit_format(0, nunits - 1, JSON); + + JSON_Value *root_value = json_value_init_object(); + JSON_Object *root_object = json_object(root_value); + + json_object_set_string(root_object, "location", G_location()); + + char date[64]; + time_t now; + struct tm *tm_info; + + time(&now); + tm_info = localtime(&now); + strftime(date, 64, "%Y-%m-%dT%H:%M:%S%z", tm_info); + json_object_set_string(root_object, "created", date); + + JSON_Value *region_value = json_value_init_object(); + JSON_Object *region_object = json_object(region_value); + json_object_set_number(region_object, "north", window.north); + json_object_set_number(region_object, "south", window.south); + json_object_set_number(region_object, "east", window.east); + json_object_set_number(region_object, "west", window.west); + json_object_set_number(region_object, "ew_res", window.ew_res); + json_object_set_number(region_object, "ns_res", window.ns_res); + json_object_set_value(root_object, "region", region_value); + + char *mask = maskinfo(); + if (strcmp(mask, "none") == 0) { + json_object_set_null(root_object, "mask"); + } + else { + json_object_set_string(root_object, "mask", mask); + } + + JSON_Value *maps_value = json_value_init_array(); + JSON_Array *maps_array = json_array(maps_value); + + for (int i = 0; i < nlayers; i++) { + JSON_Value *map_value = json_value_init_object(); + JSON_Object *map_object = json_object(map_value); + json_object_set_string(map_object, "name", layers[i].name); + + char *label; + label = Rast_get_cats_title(&(layers[i].labels)); + if (label == NULL || *label == 0) { + json_object_set_null(map_object, "label"); + } + else { + G_strip(label); + json_object_set_string(map_object, "label", label); + } + + json_object_set_string(map_object, "type", "raster"); + json_array_append_value(maps_array, map_value); + } + json_object_set_value(root_object, "maps", maps_value); + + JSON_Value *root_categories_value = make_categories(0, nstats, 0); + json_object_set_value(root_object, "categories", root_categories_value); + + JSON_Value *totals = make_units(0, -1); + json_object_set_value(root_object, "totals", totals); + + 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); +} diff --git a/raster/r.report/prt_report.c b/raster/r.report/prt_report.c index 8edb0ded88b..d8eba602a34 100644 --- a/raster/r.report/prt_report.c +++ b/raster/r.report/prt_report.c @@ -13,83 +13,12 @@ int print_report(int unit1, int unit2) int i; int divider_level; int after_header; - int need_format; int with_stats; char *cp; int spacing; char dot; - /* examine units, determine output format */ - for (i = unit1; i <= unit2; i++) { - need_format = 1; - unit[i].label[0] = ""; - unit[i].label[1] = ""; - - switch (unit[i].type) { - case CELL_COUNTS: - need_format = 0; - unit[i].len = 5; - unit[i].label[0] = " cell"; - unit[i].label[1] = "count"; - ns = 0; - sprintf(num, "%ld", count_sum(&ns, -1)); - len = strlen(num); - if (len > unit[i].len) - unit[i].len = len; - break; - - case PERCENT_COVER: - need_format = 0; - unit[i].dp = 2; - unit[i].len = 6; - unit[i].label[0] = " % "; - unit[i].label[1] = "cover"; - unit[i].eformat = 0; - break; - - case SQ_METERS: - unit[i].label[0] = "square"; - unit[i].label[1] = "meters"; - unit[i].factor = 1.0; - break; - - case SQ_KILOMETERS: - unit[i].label[0] = " square "; - unit[i].label[1] = "kilometers"; - unit[i].factor = 1.0e-6; - break; - - case ACRES: - unit[i].label[0] = ""; - unit[i].label[1] = "acres"; - unit[i].factor = 2.47105381467165e-4; /* 640 acres in a sq mile */ - break; - - case HECTARES: - unit[i].label[0] = ""; - unit[i].label[1] = "hectares"; - unit[i].factor = 1.0e-4; - break; - - case SQ_MILES: - unit[i].label[0] = "square"; - unit[i].label[1] = " miles"; - unit[i].factor = 3.86102158542446e-7; /* 1 / ( (0.0254m/in * 12in/ft - * 5280ft/mi)^2 ) */ - break; - - default: - G_fatal_error("Unit %d not yet supported", unit[i].type); - } - if (need_format) { - unit[i].dp = 6; - unit[i].len = 10; - unit[i].eformat = 0; - ns = 0; - format_parms(area_sum(&ns, -1) * unit[i].factor, &unit[i].len, - &unit[i].dp, &(unit[i].eformat), e_format); - } - } + compute_unit_format(unit1, unit2, PLAIN); /* figure out how big the category numbers are when printed */ for (nl = 0; nl < nlayers; nl++) diff --git a/raster/r.report/r.report.html b/raster/r.report/r.report.html index d4706471e3e..f4779f27310 100644 --- a/raster/r.report/r.report.html +++ b/raster/r.report/r.report.html @@ -132,6 +132,744 @@

EXAMPLE

+-----------------------------------------------------------------------------+ +The output from r.report can be output in JSON by passing the format=json option. + +
+r.report -n -a map=towns,elevation units=miles,meters,kilometers,acres,hectares,cells,percent nsteps=2 format=json
+
+ +
+{
+    "location": "nc_spm_08_grass7",
+    "created": "2024-07-24T14:59:09+0530",
+    "region": {
+        "north": 320000,
+        "south": 10000,
+        "east": 935000,
+        "west": 120000,
+        "ew_res": 500,
+        "ns_res": 500
+    },
+    "mask": null,
+    "maps": [
+        {
+            "name": "towns",
+            "label": "South West Wake: Cities and towns derived from zipcodes",
+            "type": "raster",
+        },
+        {
+            "name": "zipcodes",
+            "label": "South West Wake: Zipcode areas derived from vector map",
+            "type": "raster",
+        }
+    ],
+    "categories": [
+        {
+            "category": 1,
+            "label": "CARY",
+            "units": [
+                {
+                    "unit": "square miles",
+                    "value": 10.231707201374819
+                },
+                {
+                    "unit": "square meters",
+                    "value": 26500000
+                },
+                {
+                    "unit": "square kilometers",
+                    "value": 26.5
+                },
+                {
+                    "unit": "acres",
+                    "value": 6548.2926088798722
+                },
+                {
+                    "unit": "hectares",
+                    "value": 2650
+                },
+                {
+                    "unit": "cell counts",
+                    "value": 106
+                },
+                {
+                    "unit": "% cover",
+                    "value": 13.086419753086419
+                }
+            ],
+            "categories": [
+                {
+                    "category": 1,
+                    "label": "from to",
+                    "range": {
+                        "from": 55.578792572021484,
+                        "to": 105.9543285369873
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 0.8687298567205034
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 2250000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 2.25
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 555.98710830112122
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 225
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 9
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 8.4905660377358494
+                        }
+                    ]
+                },
+                {
+                    "category": 2,
+                    "label": "from to",
+                    "range": {
+                        "from": 105.9543285369873,
+                        "to": 156.32986450195312
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 9.3629773446543147
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 24250000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 24.25
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 5992.305500578751
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 2425
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 97
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 91.509433962264154
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "category": 2,
+            "label": "GARNER",
+            "units": [
+                {
+                    "unit": "square miles",
+                    "value": 5.5019557592298556
+                },
+                {
+                    "unit": "square meters",
+                    "value": 14250000
+                },
+                {
+                    "unit": "square kilometers",
+                    "value": 14.25
+                },
+                {
+                    "unit": "acres",
+                    "value": 3521.2516859071011
+                },
+                {
+                    "unit": "hectares",
+                    "value": 1425
+                },
+                {
+                    "unit": "cell counts",
+                    "value": 57
+                },
+                {
+                    "unit": "% cover",
+                    "value": 7.0370370370370372
+                }
+            ],
+            "categories": [
+                {
+                    "category": 1,
+                    "label": "from to",
+                    "range": {
+                        "from": 55.578792572021484,
+                        "to": 105.9543285369873
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 4.3436492836025176
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 11250000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 11.25
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 2779.9355415056061
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 1125
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 45
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 78.94736842105263
+                        }
+                    ]
+                },
+                {
+                    "category": 2,
+                    "label": "from to",
+                    "range": {
+                        "from": 105.9543285369873,
+                        "to": 156.32986450195312
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 1.158306475627338
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 3000000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 3
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 741.31614440149497
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 300
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 12
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 21.05263157894737
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "category": 3,
+            "label": "APEX",
+            "units": [
+                {
+                    "unit": "square miles",
+                    "value": 0.9652553963561149
+                },
+                {
+                    "unit": "square meters",
+                    "value": 2500000
+                },
+                {
+                    "unit": "square kilometers",
+                    "value": 2.5
+                },
+                {
+                    "unit": "acres",
+                    "value": 617.76345366791247
+                },
+                {
+                    "unit": "hectares",
+                    "value": 250
+                },
+                {
+                    "unit": "cell counts",
+                    "value": 10
+                },
+                {
+                    "unit": "% cover",
+                    "value": 1.2345679012345678
+                }
+            ],
+            "categories": [
+                {
+                    "category": 1,
+                    "label": "from to",
+                    "range": {
+                        "from": 55.578792572021484,
+                        "to": 105.9543285369873
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 0.096525539635611488
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 250000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 0.25
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 61.776345366791247
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 25
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 1
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 10
+                        }
+                    ]
+                },
+                {
+                    "category": 2,
+                    "label": "from to",
+                    "range": {
+                        "from": 105.9543285369873,
+                        "to": 156.32986450195312
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 0.8687298567205034
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 2250000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 2.25
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 555.98710830112122
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 225
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 9
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 90
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "category": 4,
+            "label": "RALEIGH-CITY",
+            "units": [
+                {
+                    "unit": "square miles",
+                    "value": 6.0811089970435237
+                },
+                {
+                    "unit": "square meters",
+                    "value": 15750000
+                },
+                {
+                    "unit": "square kilometers",
+                    "value": 15.75
+                },
+                {
+                    "unit": "acres",
+                    "value": 3891.9097581078486
+                },
+                {
+                    "unit": "hectares",
+                    "value": 1575
+                },
+                {
+                    "unit": "cell counts",
+                    "value": 63
+                },
+                {
+                    "unit": "% cover",
+                    "value": 7.7777777777777777
+                }
+            ],
+            "categories": [
+                {
+                    "category": 1,
+                    "label": "from to",
+                    "range": {
+                        "from": 55.578792572021484,
+                        "to": 105.9543285369873
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 5.3089046799586326
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 13750000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 13.75
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 3397.6989951735186
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 1375
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 55
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 87.301587301587304
+                        }
+                    ]
+                },
+                {
+                    "category": 2,
+                    "label": "from to",
+                    "range": {
+                        "from": 105.9543285369873,
+                        "to": 156.32986450195312
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 0.7722043170848919
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 2000000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 2
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 494.21076293432998
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 200
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 8
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 12.698412698412698
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "category": 5,
+            "label": "RALEIGH-SOUTH",
+            "units": [
+                {
+                    "unit": "square miles",
+                    "value": 47.394039961085241
+                },
+                {
+                    "unit": "square meters",
+                    "value": 122750000
+                },
+                {
+                    "unit": "square kilometers",
+                    "value": 122.75
+                },
+                {
+                    "unit": "acres",
+                    "value": 30332.185575094503
+                },
+                {
+                    "unit": "hectares",
+                    "value": 12275
+                },
+                {
+                    "unit": "cell counts",
+                    "value": 491
+                },
+                {
+                    "unit": "% cover",
+                    "value": 60.617283950617285
+                }
+            ],
+            "categories": [
+                {
+                    "category": 1,
+                    "label": "from to",
+                    "range": {
+                        "from": 55.578792572021484,
+                        "to": 105.9543285369873
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 25.579268003437047
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 66250000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 66.25
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 16370.731522199681
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 6625
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 265
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 53.971486761710793
+                        }
+                    ]
+                },
+                {
+                    "category": 2,
+                    "label": "from to",
+                    "range": {
+                        "from": 105.9543285369873,
+                        "to": 156.32986450195312
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 21.814771957648198
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 56500000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 56.5
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 13961.454052894822
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 5650
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 226
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 46.028513238289207
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "category": 6,
+            "label": "RALEIGH-WEST",
+            "units": [
+                {
+                    "unit": "square miles",
+                    "value": 8.0116197897557537
+                },
+                {
+                    "unit": "square meters",
+                    "value": 20750000
+                },
+                {
+                    "unit": "square kilometers",
+                    "value": 20.75
+                },
+                {
+                    "unit": "acres",
+                    "value": 5127.4366654436735
+                },
+                {
+                    "unit": "hectares",
+                    "value": 2075
+                },
+                {
+                    "unit": "cell counts",
+                    "value": 83
+                },
+                {
+                    "unit": "% cover",
+                    "value": 10.246913580246913
+                }
+            ],
+            "categories": [
+                {
+                    "category": 1,
+                    "label": "from to",
+                    "range": {
+                        "from": 55.578792572021484,
+                        "to": 105.9543285369873
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 0.096525539635611488
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 250000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 0.25
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 61.776345366791247
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 25
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 1
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 1.2048192771084338
+                        }
+                    ]
+                },
+                {
+                    "category": 2,
+                    "label": "from to",
+                    "range": {
+                        "from": 105.9543285369873,
+                        "to": 156.32986450195312
+                    },
+                    "units": [
+                        {
+                            "unit": "square miles",
+                            "value": 7.9150942501201422
+                        },
+                        {
+                            "unit": "square meters",
+                            "value": 20500000
+                        },
+                        {
+                            "unit": "square kilometers",
+                            "value": 20.5
+                        },
+                        {
+                            "unit": "acres",
+                            "value": 5065.6603200768823
+                        },
+                        {
+                            "unit": "hectares",
+                            "value": 2050
+                        },
+                        {
+                            "unit": "cell counts",
+                            "value": 82
+                        },
+                        {
+                            "unit": "% cover",
+                            "value": 98.795180722891573
+                        }
+                    ]
+                }
+            ]
+        }
+    ],
+    "totals": [
+        {
+            "unit": "square miles",
+            "value": 78.185687104845314
+        },
+        {
+            "unit": "square meters",
+            "value": 202500000
+        },
+        {
+            "unit": "square kilometers",
+            "value": 202.5
+        },
+        {
+            "unit": "acres",
+            "value": 50038.839747100916
+        },
+        {
+            "unit": "hectares",
+            "value": 20250
+        },
+        {
+            "unit": "cell counts",
+            "value": 810
+        },
+        {
+            "unit": "% cover",
+            "value": 100
+        }
+    ]
+}
+
+

SEE ALSO

diff --git a/raster/r.report/report.c b/raster/r.report/report.c index a10a3fc34bd..1e97f33c5e2 100644 --- a/raster/r.report/report.c +++ b/raster/r.report/report.c @@ -4,18 +4,25 @@ int report(void) { int unit1, unit2; - if (stats_flag == STATS_ONLY) - return 1; + switch (format) { + case JSON: + print_json(); + break; + case PLAIN: + if (stats_flag == STATS_ONLY) + return 1; - if (nunits == 0) - print_report(0, -1); - else - for (unit1 = 0; unit1 < nunits; unit1 = unit2 + 1) { - unit2 = unit1 + 2; - if (unit2 >= nunits) - unit2 = nunits - 1; - print_report(unit1, unit2); - } + if (nunits == 0) + print_report(0, -1); + else + for (unit1 = 0; unit1 < nunits; unit1 = unit2 + 1) { + unit2 = unit1 + 2; + if (unit2 >= nunits) + unit2 = nunits - 1; + print_report(unit1, unit2); + } + break; + } return 0; } diff --git a/raster/r.report/testsuite/test_r_report.py b/raster/r.report/testsuite/test_r_report.py index 7d872e17ff8..53e49fdc174 100644 --- a/raster/r.report/testsuite/test_r_report.py +++ b/raster/r.report/testsuite/test_r_report.py @@ -9,9 +9,15 @@ for details. """ +import json import os +from itertools import zip_longest +from datetime import datetime + from grass.gunittest.case import TestCase +from grass.gunittest.gmodules import SimpleModule + class TestRasterreport(TestCase): outfile = "test_out.csv" @@ -58,6 +64,609 @@ def test_output(self): self.assertModule("r.report", map="lakes", output=self.outfile) self.assertFileExists(self.outfile) + def _assert_report_equal(self, reference, data): + keys = ["location", "region", "mask", "maps", "totals"] + for key in keys: + self.assertEqual(reference[key], data[key]) + + for category1, category2 in zip_longest( + reference["categories"], data["categories"] + ): + self.assertEqual(category1["category"], category2["category"]) + self.assertEqual(category1["label"], category2["label"]) + + for unit1, unit2 in zip_longest(category1["units"], category2["units"]): + self.assertEqual(unit1["unit"], unit2["unit"]) + self.assertAlmostEqual(unit1["value"], unit2["value"], places=6) + + for sub_category1, sub_category2 in zip_longest( + category1["categories"], category2["categories"] + ): + self.assertEqual(sub_category1["category"], sub_category2["category"]) + self.assertEqual(sub_category1["label"], sub_category2["label"]) + + for sub_unit1, sub_unit2 in zip_longest( + sub_category1["units"], sub_category2["units"] + ): + self.assertEqual(sub_unit1["unit"], sub_unit2["unit"]) + self.assertAlmostEqual( + sub_unit1["value"], sub_unit2["value"], places=6 + ) + + def test_json(self): + """Test JSON format""" + reference = { + "location": "nc_spm_full_v2alpha2", + "region": { + "north": 228500, + "south": 215000, + "east": 645000, + "west": 630000, + "ew_res": 10, + "ns_res": 10, + }, + "mask": None, + "maps": [ + { + "name": "towns", + "label": "South West Wake: Cities and towns derived from zipcodes", + "type": "raster", + }, + { + "name": "zipcodes", + "label": "South West Wake: Zipcode areas derived from vector map", + "type": "raster", + }, + ], + "categories": [ + { + "category": 1, + "label": "CARY", + "units": [ + {"unit": "cell counts", "value": 260849}, + {"unit": "% cover", "value": 12.881432098765432}, + ], + "categories": [ + { + "category": 27511, + "label": "CARY", + "units": [ + {"unit": "cell counts", "value": 105800}, + {"unit": "% cover", "value": 40.559864135956055}, + ], + }, + { + "category": 27513, + "label": "CARY", + "units": [ + {"unit": "cell counts", "value": 20530}, + {"unit": "% cover", "value": 7.8704537874402432}, + ], + }, + { + "category": 27518, + "label": "CARY", + "units": [ + {"unit": "cell counts", "value": 134519}, + {"unit": "% cover", "value": 51.569682076603705}, + ], + }, + ], + }, + { + "category": 2, + "label": "GARNER", + "units": [ + {"unit": "cell counts", "value": 141572}, + {"unit": "% cover", "value": 6.99120987654321}, + ], + "categories": [ + { + "category": 27529, + "label": "GARNER", + "units": [ + {"unit": "cell counts", "value": 141572}, + {"unit": "% cover", "value": 100}, + ], + } + ], + }, + { + "category": 3, + "label": "APEX", + "units": [ + {"unit": "cell counts", "value": 25444}, + {"unit": "% cover", "value": 1.2564938271604937}, + ], + "categories": [ + { + "category": 27539, + "label": "APEX", + "units": [ + {"unit": "cell counts", "value": 25444}, + {"unit": "% cover", "value": 100}, + ], + } + ], + }, + { + "category": 4, + "label": "RALEIGH-CITY", + "units": [ + {"unit": "cell counts", "value": 160514}, + {"unit": "% cover", "value": 7.926617283950617}, + ], + "categories": [ + { + "category": 27601, + "label": "RALEIGH", + "units": [ + {"unit": "cell counts", "value": 45468}, + {"unit": "% cover", "value": 28.326501115167524}, + ], + }, + { + "category": 27604, + "label": "RALEIGH", + "units": [ + {"unit": "cell counts", "value": 47389}, + {"unit": "% cover", "value": 29.523281458315161}, + ], + }, + { + "category": 27605, + "label": "RALEIGH", + "units": [ + {"unit": "cell counts", "value": 23677}, + {"unit": "% cover", "value": 14.750738253361078}, + ], + }, + { + "category": 27608, + "label": "RALEIGH", + "units": [ + {"unit": "cell counts", "value": 43980}, + {"unit": "% cover", "value": 27.399479173156237}, + ], + }, + ], + }, + { + "category": 5, + "label": "RALEIGH-SOUTH", + "units": [ + {"unit": "cell counts", "value": 1227632}, + {"unit": "% cover", "value": 60.623802469135804}, + ], + "categories": [ + { + "category": 27603, + "label": "RALEIGH", + "units": [ + {"unit": "cell counts", "value": 429179}, + {"unit": "% cover", "value": 34.959906551800536}, + ], + }, + { + "category": 27606, + "label": "RALEIGH", + "units": [ + {"unit": "cell counts", "value": 662642}, + {"unit": "% cover", "value": 53.977250511553954}, + ], + }, + { + "category": 27610, + "label": "RALEIGH", + "units": [ + {"unit": "cell counts", "value": 135811}, + {"unit": "% cover", "value": 11.062842936645509}, + ], + }, + ], + }, + { + "category": 6, + "label": "RALEIGH-WEST", + "units": [ + {"unit": "cell counts", "value": 208989}, + {"unit": "% cover", "value": 10.320444444444444}, + ], + "categories": [ + { + "category": 27607, + "label": "RALEIGH", + "units": [ + {"unit": "cell counts", "value": 208989}, + {"unit": "% cover", "value": 100}, + ], + } + ], + }, + ], + "totals": [ + {"unit": "cell counts", "value": 2025000}, + {"unit": "% cover", "value": 100}, + ], + } + module = SimpleModule("r.report", map="towns,zipcodes", format="json") + self.runModule(module) + data = json.loads(module.outputs.stdout) + self._assert_report_equal(reference, data) + + def test_json2(self): + """Test JSON format with more options""" + reference = { + "location": "nc_spm_full_v2alpha2", + "created": "2024-07-24T14:59:09+0530", + "region": { + "north": 228500, + "south": 215000, + "east": 645000, + "west": 630000, + "ew_res": 10, + "ns_res": 10, + }, + "mask": None, + "maps": [ + { + "name": "towns", + "label": "South West Wake: Cities and towns derived from zipcodes", + "type": "raster", + }, + { + "name": "elevation", + "label": "South-West Wake county: Elevation NED 10m", + "type": "raster", + }, + ], + "categories": [ + { + "category": 1, + "label": "CARY", + "units": [ + {"unit": "square miles", "value": 10.07143619536385}, + {"unit": "square meters", "value": 26084900}, + {"unit": "square kilometers", "value": 26.084899999999998}, + {"unit": "acres", "value": 6445.719165032852}, + {"unit": "hectares", "value": 2608.4900000000002}, + {"unit": "cell counts", "value": 260849}, + {"unit": "% cover", "value": 12.881432098765432}, + ], + "categories": [ + { + "category": 1, + "label": "from to", + "range": { + "from": 55.578792572021484, + "to": 105.9543285369873, + }, + "units": [ + {"unit": "square miles", "value": 0.9655642780829489}, + {"unit": "square meters", "value": 2500800}, + {"unit": "square kilometers", "value": 2.5008}, + {"unit": "acres", "value": 617.9611379730862}, + {"unit": "hectares", "value": 250.08}, + {"unit": "cell counts", "value": 25008}, + {"unit": "% cover", "value": 9.58715578744791}, + ], + }, + { + "category": 2, + "label": "from to", + "range": { + "from": 105.9543285369873, + "to": 156.32986450195312, + }, + "units": [ + {"unit": "square miles", "value": 9.1058719172809}, + {"unit": "square meters", "value": 23584100}, + {"unit": "square kilometers", "value": 23.5841}, + {"unit": "acres", "value": 5827.758027059766}, + {"unit": "hectares", "value": 2358.4100000000003}, + {"unit": "cell counts", "value": 235841}, + {"unit": "% cover", "value": 90.41284421255209}, + ], + }, + ], + }, + { + "category": 2, + "label": "GARNER", + "units": [ + {"unit": "square miles", "value": 5.4661254789171165}, + {"unit": "square meters", "value": 14157200}, + {"unit": "square kilometers", "value": 14.1572}, + {"unit": "acres", "value": 3498.3203065069483}, + {"unit": "hectares", "value": 1415.72}, + {"unit": "cell counts", "value": 141572}, + {"unit": "% cover", "value": 6.99120987654321}, + ], + "categories": [ + { + "category": 1, + "label": "from to", + "range": { + "from": 55.578792572021484, + "to": 105.9543285369873, + }, + "units": [ + {"unit": "square miles", "value": 4.24917008540718}, + {"unit": "square meters", "value": 11005300}, + {"unit": "square kilometers", "value": 11.0053}, + {"unit": "acres", "value": 2719.4688546605908}, + {"unit": "hectares", "value": 1100.53}, + {"unit": "cell counts", "value": 110053}, + {"unit": "% cover", "value": 77.73641680558302}, + ], + }, + { + "category": 2, + "label": "from to", + "range": { + "from": 105.9543285369873, + "to": 156.32986450195312, + }, + "units": [ + {"unit": "square miles", "value": 1.2169553935099355}, + {"unit": "square meters", "value": 3151900}, + {"unit": "square kilometers", "value": 3.1519}, + {"unit": "acres", "value": 778.8514518463573}, + {"unit": "hectares", "value": 315.19}, + {"unit": "cell counts", "value": 31519}, + {"unit": "% cover", "value": 22.263583194416974}, + ], + }, + ], + }, + { + "category": 3, + "label": "APEX", + "units": [ + {"unit": "square miles", "value": 0.9823983321953995}, + {"unit": "square meters", "value": 2544400}, + {"unit": "square kilometers", "value": 2.5444}, + {"unit": "acres", "value": 628.7349326050546}, + {"unit": "hectares", "value": 254.44000000000003}, + {"unit": "cell counts", "value": 25444}, + {"unit": "% cover", "value": 1.2564938271604937}, + ], + "categories": [ + { + "category": 1, + "label": "from to", + "range": { + "from": 55.578792572021484, + "to": 105.9543285369873, + }, + "units": [ + {"unit": "square miles", "value": 0.03262563239683668}, + {"unit": "square meters", "value": 84500}, + { + "unit": "square kilometers", + "value": 0.08449999999999999, + }, + {"unit": "acres", "value": 20.880404733975443}, + {"unit": "hectares", "value": 8.450000000000001}, + {"unit": "cell counts", "value": 845}, + {"unit": "% cover", "value": 3.321018707750354}, + ], + }, + { + "category": 2, + "label": "from to", + "range": { + "from": 105.9543285369873, + "to": 156.32986450195312, + }, + "units": [ + {"unit": "square miles", "value": 0.9497726997985628}, + {"unit": "square meters", "value": 2459900}, + { + "unit": "square kilometers", + "value": 2.4598999999999998, + }, + {"unit": "acres", "value": 607.8545278710792}, + {"unit": "hectares", "value": 245.99}, + {"unit": "cell counts", "value": 24599}, + {"unit": "% cover", "value": 96.67898129224965}, + ], + }, + ], + }, + { + "category": 4, + "label": "RALEIGH-CITY", + "units": [ + {"unit": "square miles", "value": 6.1974801876282175}, + {"unit": "square meters", "value": 16051400}, + {"unit": "square kilometers", "value": 16.0514}, + {"unit": "acres", "value": 3966.387320082052}, + {"unit": "hectares", "value": 1605.14}, + {"unit": "cell counts", "value": 160514}, + {"unit": "% cover", "value": 7.926617283950617}, + ], + "categories": [ + { + "category": 1, + "label": "from to", + "range": { + "from": 55.578792572021484, + "to": 105.9543285369873, + }, + "units": [ + {"unit": "square miles", "value": 5.062455672160989}, + {"unit": "square meters", "value": 13111700}, + { + "unit": "square kilometers", + "value": 13.111699999999999, + }, + {"unit": "acres", "value": 3239.971630183027}, + {"unit": "hectares", "value": 1311.17}, + {"unit": "cell counts", "value": 131117}, + {"unit": "% cover", "value": 81.68570965772456}, + ], + }, + { + "category": 2, + "label": "from to", + "range": { + "from": 105.9543285369873, + "to": 156.32986450195312, + }, + "units": [ + {"unit": "square miles", "value": 1.1350245154672285}, + {"unit": "square meters", "value": 2939700}, + { + "unit": "square kilometers", + "value": 2.9396999999999998, + }, + {"unit": "acres", "value": 726.415689899025}, + {"unit": "hectares", "value": 293.97}, + {"unit": "cell counts", "value": 29397}, + {"unit": "% cover", "value": 18.31429034227544}, + ], + }, + ], + }, + { + "category": 5, + "label": "RALEIGH-SOUTH", + "units": [ + {"unit": "square miles", "value": 47.39913650957801}, + {"unit": "square meters", "value": 122763200}, + {"unit": "square kilometers", "value": 122.7632}, + {"unit": "acres", "value": 30335.44736612987}, + {"unit": "hectares", "value": 12276.32}, + {"unit": "cell counts", "value": 1227632}, + {"unit": "% cover", "value": 60.6238024691358}, + ], + "categories": [ + { + "category": 1, + "label": "from to", + "range": { + "from": 55.578792572021484, + "to": 105.9543285369873, + }, + "units": [ + {"unit": "square miles", "value": 24.823086925931666}, + {"unit": "square meters", "value": 64291500}, + {"unit": "square kilometers", "value": 64.2915}, + {"unit": "acres", "value": 15886.775632596238}, + {"unit": "hectares", "value": 6429.150000000001}, + {"unit": "cell counts", "value": 642915}, + {"unit": "% cover", "value": 52.37033573579053}, + ], + }, + { + "category": 2, + "label": "from to", + "range": { + "from": 105.9543285369873, + "to": 156.32986450195312, + }, + "units": [ + {"unit": "square miles", "value": 22.576049583646338}, + {"unit": "square meters", "value": 58471700}, + {"unit": "square kilometers", "value": 58.4717}, + {"unit": "acres", "value": 14448.671733533633}, + {"unit": "hectares", "value": 5847.17}, + {"unit": "cell counts", "value": 584717}, + {"unit": "% cover", "value": 47.62966426420947}, + ], + }, + ], + }, + { + "category": 6, + "label": "RALEIGH-WEST", + "units": [ + {"unit": "square miles", "value": 8.069110401162725}, + {"unit": "square meters", "value": 20898900}, + {"unit": "square kilometers", "value": 20.898899999999998}, + {"unit": "acres", "value": 5164.230656744135}, + {"unit": "hectares", "value": 2089.8900000000003}, + {"unit": "cell counts", "value": 208989}, + {"unit": "% cover", "value": 10.320444444444444}, + ], + "categories": [ + { + "category": 1, + "label": "from to", + "range": { + "from": 55.578792572021484, + "to": 105.9543285369873, + }, + "units": [ + {"unit": "square miles", "value": 0.23822503182068916}, + {"unit": "square meters", "value": 617000}, + {"unit": "square kilometers", "value": 0.617}, + {"unit": "acres", "value": 152.4640203652408}, + {"unit": "hectares", "value": 61.7}, + {"unit": "cell counts", "value": 6170}, + {"unit": "% cover", "value": 2.9523084947054627}, + ], + }, + { + "category": 2, + "label": "from to", + "range": { + "from": 105.9543285369873, + "to": 156.32986450195312, + }, + "units": [ + {"unit": "square miles", "value": 7.8308853693420355}, + {"unit": "square meters", "value": 20281900}, + {"unit": "square kilometers", "value": 20.2819}, + {"unit": "acres", "value": 5011.766636378894}, + {"unit": "hectares", "value": 2028.19}, + {"unit": "cell counts", "value": 202819}, + {"unit": "% cover", "value": 97.04769150529454}, + ], + }, + ], + }, + ], + "totals": [ + {"unit": "square miles", "value": 78.18568710484531}, + {"unit": "square meters", "value": 202500000}, + {"unit": "square kilometers", "value": 202.5}, + {"unit": "acres", "value": 50038.839747100916}, + {"unit": "hectares", "value": 20250}, + {"unit": "cell counts", "value": 2025000}, + {"unit": "% cover", "value": 100}, + ], + } + module = SimpleModule( + "r.report", + map="towns,elevation", + units=[ + "miles", + "meters", + "kilometers", + "acres", + "hectares", + "cells", + "percent", + ], + nsteps=2, + format="json", + ) + self.runModule(module) + data = json.loads(module.outputs.stdout) + + # created field represents the time of running the command. Therefore, its exact value + # cannot be tested. We only check that it is present and in the ISO8601 datetime format + self.assertIn("created", data) + try: + # on Python 3.11 and below, datetime.fromisoformat doesn't support zone info with offset + datetime.strptime(data["created"], "%Y-%m-%dT%H:%M:%S%z") + except ValueError: + self.fail("created field is not in isoformat: %s" % (data["created"],)) + + self._assert_report_equal(reference, data) + if __name__ == "__main__": from grass.gunittest.main import test