Skip to content

Commit

Permalink
r.report: add JSON support (OSGeo#3935)
Browse files Browse the repository at this point in the history
* r.report: add json support

* address PR feedback

* fix segfault

* address PR feedback

* update tests to use CI dataset

* change created field to iso8601 datetime format

* fix created field test

* fix map description field

* Apply suggestions from code review

Co-authored-by: Edouard Choinière <[email protected]>

* update documentation

* Update raster/r.report/format.c

Co-authored-by: Nicklas Larsson <[email protected]>

---------

Co-authored-by: Edouard Choinière <[email protected]>
Co-authored-by: Nicklas Larsson <[email protected]>
  • Loading branch information
3 people authored and landam committed Aug 22, 2024
1 parent b9cd6fb commit 56c4736
Show file tree
Hide file tree
Showing 10 changed files with 1,686 additions and 84 deletions.
2 changes: 1 addition & 1 deletion raster/r.report/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
90 changes: 90 additions & 0 deletions raster/r.report/format.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
11 changes: 11 additions & 0 deletions raster/r.report/global.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#endif

#include <grass/raster.h>
#include <grass/parson.h>

#define SORT_DEFAULT 0
#define SORT_ASC 1
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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);
Expand Down Expand Up @@ -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);

Expand Down
2 changes: 2 additions & 0 deletions raster/r.report/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ int maskfd;
CELL *mask;
CELL NULL_CELL;

enum OutputFormat format;

char fs[2];
struct Categories *labels;

Expand Down
11 changes: 11 additions & 0 deletions raster/r.report/parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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)
Expand Down
205 changes: 205 additions & 0 deletions raster/r.report/prt_json.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "global.h"
#include <grass/parson.h>
#include <grass/glocale.h>

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);
}
Loading

0 comments on commit 56c4736

Please sign in to comment.