Skip to content

Commit

Permalink
r.colors.out: added json output
Browse files Browse the repository at this point in the history
Signed-off-by: Nishant Bansal <[email protected]>
  • Loading branch information
NishantBansal2003 committed Oct 20, 2024
1 parent 1a803b4 commit 8e92475
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 10 deletions.
8 changes: 4 additions & 4 deletions raster/r.colors.out/Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
MODULE_TOPDIR = ../..

LIBES2 = $(RASTERLIB) $(GISLIB)
LIBES3 = $(RASTER3DLIB) $(RASTERLIB) $(GISLIB)
LIBES2 = $(RASTERLIB) $(GISLIB) $(PARSONLIB)
LIBES3 = $(RASTER3DLIB) $(RASTERLIB) $(GISLIB) $(PARSONLIB)
DEPENDENCIES = $(RASTER3DDEP) $(GISDEP) $(RASTERDEP)

PROGRAMS = r.colors.out r3.colors.out

r_colors_out_OBJS = raster_main.o
r3_colors_out_OBJS = raster3d_main.o
r_colors_out_OBJS = raster_main.o prt_json.o
r3_colors_out_OBJS = raster3d_main.o prt_json.o

include $(MODULE_TOPDIR)/include/Make/Multi.make

Expand Down
10 changes: 10 additions & 0 deletions raster/r.colors.out/local_proto.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <grass/gis.h>
#include <grass/parson.h>

enum OutputFormat { PLAIN, JSON };

void write_json_rule(DCELL *val, DCELL *min, DCELL *max, int r, int g, int b,
FILE *fp, JSON_Array *root_array, int perc);

void Rast_json_print_colors(struct Colors *colors, DCELL min, DCELL max,
FILE *fp, int perc);
107 changes: 107 additions & 0 deletions raster/r.colors.out/prt_json.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/glocale.h>
#include <grass/parson.h>

void write_json_rule(DCELL *val, DCELL *min, DCELL *max, int r, int g, int b,
FILE *fp, JSON_Array *root_array, int perc)
{
static DCELL v0;
static int r0 = -1, g0 = -1, b0 = -1;

if (v0 == *val && r0 == r && g0 == g && b0 == b)
return;
v0 = *val, r0 = r, g0 = g, b0 = b;
JSON_Value *color_value = json_value_init_object();
JSON_Object *color_object = json_object(color_value);

char rgb_string[20];
snprintf(rgb_string, sizeof(rgb_string), "rgb(%d, %d, %d)", r, g, b);

if (perc)
json_object_set_number(color_object, "value",
100 * (*val - *min) / (*max - *min));
else
json_object_set_number(color_object, "value", *val);

json_object_set_string(color_object, "rgb", rgb_string);

json_array_append_value(root_array, color_value);
}

void Rast_json_print_colors(struct Colors *colors, DCELL min, DCELL max,
FILE *fp, int perc)
{
JSON_Value *root_value = json_value_init_array();
JSON_Array *root_array = json_array(root_value);

int i, count;

count = 0;
if (colors->version < 0) {
CELL lo, hi;

Rast_get_c_color_range(&lo, &hi, colors);

for (i = lo; i <= hi; i++) {
unsigned char r, g, b, set;
DCELL val = (DCELL)i;

Rast_lookup_c_colors(&i, &r, &g, &b, &set, 1, colors);
write_json_rule(&val, &min, &max, r, g, b, fp, root_array, perc);
}
}
else {
count = Rast_colors_count(colors);

for (i = 0; i < count; i++) {
DCELL val1, val2;
unsigned char r1, g1, b1, r2, g2, b2;

Rast_get_fp_color_rule(&val1, &r1, &g1, &b1, &val2, &r2, &g2, &b2,
colors, count - 1 - i);

write_json_rule(&val1, &min, &max, r1, g1, b1, fp, root_array,
perc);
write_json_rule(&val2, &min, &max, r2, g2, b2, fp, root_array,
perc);
}
}

{
int r, g, b;

Rast_get_null_value_color(&r, &g, &b, colors);
JSON_Value *nv_value = json_value_init_object();
JSON_Object *nv_object = json_object(nv_value);
char nv_rgb_string[20];
snprintf(nv_rgb_string, sizeof(nv_rgb_string), "rgb(%d, %d, %d)", r, g,
b);
json_object_set_string(nv_object, "value", "nv");
json_object_set_string(nv_object, "rgb", nv_rgb_string);
json_array_append_value(root_array, nv_value);

Rast_get_default_color(&r, &g, &b, colors);
JSON_Value *default_value = json_value_init_object();
JSON_Object *default_object = json_object(default_value);
char default_rgb_string[20];
snprintf(default_rgb_string, sizeof(default_rgb_string),
"rgb(%d, %d, %d)", r, g, b);
json_object_set_string(default_object, "value", "default");
json_object_set_string(default_object, "rgb", default_rgb_string);
json_array_append_value(root_array, default_value);
}

char *json_string = json_serialize_to_string_pretty(root_value);
if (!json_string) {
G_fatal_error(_("Failed to serialize JSON to pretty format."));
}

fputs(json_string, fp);

json_free_serialized_string(json_string);
json_value_free(root_value);

if (fp != stdout)
fclose(fp);
}
27 changes: 24 additions & 3 deletions raster/r.colors.out/raster3d_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
#include <grass/raster.h>
#include <grass/raster3d.h>
#include <grass/glocale.h>
#include <grass/parson.h>

#include "local_proto.h"

/* Run in raster3d mode */
int main(int argc, char **argv)
{
struct GModule *module;
struct {
struct Option *map, *file;
struct Option *map, *file, *format;
} opt;
struct {
struct Flag *p;
Expand All @@ -38,6 +41,8 @@ int main(int argc, char **argv)
struct Colors colors;
struct FPRange range;

enum OutputFormat format;

G_gisinit(argv[0]);

module = G_define_module();
Expand All @@ -55,6 +60,9 @@ int main(int argc, char **argv)
opt.file->description = _("If not given write to standard output");
opt.file->required = NO;

opt.format = G_define_standard_option(G_OPT_F_FORMAT);
opt.format->guisection = _("Print");

flag.p = G_define_flag();
flag.p->key = 'p';
flag.p->description = _("Output values as percentages");
Expand All @@ -78,8 +86,21 @@ int main(int argc, char **argv)
G_fatal_error(_("Unable to open output file <%s>"), file);
}

Rast_print_colors(&colors, range.min, range.max, fp,
flag.p->answer ? 1 : 0);
if (strcmp(opt.format->answer, "json") == 0) {
format = JSON;
}
else {
format = PLAIN;
}

if (format == JSON) {
Rast_json_print_colors(&colors, range.min, range.max, fp,
flag.p->answer ? 1 : 0);
}
else {
Rast_print_colors(&colors, range.min, range.max, fp,
flag.p->answer ? 1 : 0);
}

exit(EXIT_SUCCESS);
}
27 changes: 24 additions & 3 deletions raster/r.colors.out/raster_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/glocale.h>
#include <grass/parson.h>

#include "local_proto.h"

/* Run in raster mode */
int main(int argc, char **argv)
{
struct GModule *module;
struct {
struct Option *map, *file;
struct Option *map, *file, *format;
} opt;
struct {
struct Flag *p;
Expand All @@ -37,6 +40,8 @@ int main(int argc, char **argv)
struct Colors colors;
struct FPRange range;

enum OutputFormat format;

G_gisinit(argv[0]);

module = G_define_module();
Expand All @@ -54,6 +59,9 @@ int main(int argc, char **argv)
opt.file->description = _("If not given write to standard output");
opt.file->required = NO;

opt.format = G_define_standard_option(G_OPT_F_FORMAT);
opt.format->guisection = _("Print");

flag.p = G_define_flag();
flag.p->key = 'p';
flag.p->description = _("Output values as percentages");
Expand All @@ -77,8 +85,21 @@ int main(int argc, char **argv)
G_fatal_error(_("Unable to open output file <%s>"), file);
}

Rast_print_colors(&colors, range.min, range.max, fp,
flag.p->answer ? 1 : 0);
if (strcmp(opt.format->answer, "json") == 0) {
format = JSON;
}
else {
format = PLAIN;
}

if (format == JSON) {
Rast_json_print_colors(&colors, range.min, range.max, fp,
flag.p->answer ? 1 : 0);
}
else {
Rast_print_colors(&colors, range.min, range.max, fp,
flag.p->answer ? 1 : 0);
}

exit(EXIT_SUCCESS);
}
83 changes: 83 additions & 0 deletions raster/r.colors.out/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Fixture for r.colors.out and r3.colors.out test"""

from types import SimpleNamespace

import os
import pytest
import grass.script as gs

def setup_grass_location(tmp_path, location_name):
"""Initialize a new GRASS location."""
gs.core._create_location_xy(tmp_path, location_name)

Check failure on line 11 in raster/r.colors.out/tests/conftest.py

View workflow job for this annotation

GitHub Actions / Python Code Quality Checks (ubuntu-22.04)

Ruff (W291)

raster/r.colors.out/tests/conftest.py:11:57: W291 Trailing whitespace
location_path = tmp_path / location_name
return location_path

Check failure on line 13 in raster/r.colors.out/tests/conftest.py

View workflow job for this annotation

GitHub Actions / Python Code Quality Checks (ubuntu-22.04)

Ruff (RET504)

raster/r.colors.out/tests/conftest.py:13:12: RET504 Unnecessary assignment to `location_path` before `return` statement

def configure_grass_region(session_env):
"""Configure the GRASS region for testing with defined bounds and resolution."""
gs.run_command(
"g.region",
s=0, n=90, w=0, e=100, b=0, t=50, res=10, res3=10, env=session_env
)

def create_test_rasters(session_env):
"""Generate raster layers with specific values for testing."""
gs.run_command("r.mapcalc", expression="test_elev_int_1 = int(rand(-15.0, 5.0))", seed=1, env=session_env)
gs.run_command("r.mapcalc", expression="test_elev_int_2 = int(rand(0.0, 10.0))", seed=1, env=session_env)
gs.run_command("r.mapcalc", expression="test_elev_int_3 = int(rand(5.0, 15.0))", seed=1, env=session_env)

return "test_elev_int_1,test_elev_int_2,test_elev_int_3"

def apply_random_color_to_rasters(raster_names, session_env):
"""Apply random colors to the specified rasters."""
gs.run_command("r.colors", map=raster_names, color="random", env=session_env)

def create_test_rasters3(session_env):
"""Generate raster3 layers with specific values for testing."""
gs.run_command("r3.mapcalc", expression="volume_double = double(col() + row() + depth())", env=session_env)
gs.run_command("r3.mapcalc", expression="volume_double_null = if(row() == 1 || row() == 5, null(), volume_double)", env=session_env)

return "volume_double_null"

def apply_random_color_to_rasters3(raster3_names, session_env):
"""Apply elevation colors to the specified raster3."""
gs.run_command("r3.colors", map=raster3_names, color="elevation", env=session_env)

@pytest.fixture
def raster_color_dataset(tmp_path_factory):
"""Set up a GRASS session and create test rasters with color rules."""

tmp_path = tmp_path_factory.mktemp("raster_color_test")
location_name = "test_location"
location_path = setup_grass_location(tmp_path, location_name)

with gs.setup.init(location_path, env=os.environ.copy()) as session:
configure_grass_region(session.env)

raster_names = create_test_rasters(session.env)
apply_random_color_to_rasters(raster_names, session.env)

yield SimpleNamespace(
session=session,
raster_names="test_elev_int_3",

Check failure on line 61 in raster/r.colors.out/tests/conftest.py

View workflow job for this annotation

GitHub Actions / Python Code Quality Checks (ubuntu-22.04)

Ruff (W291)

raster/r.colors.out/tests/conftest.py:61:44: W291 Trailing whitespace
env=session.env,
)

@pytest.fixture
def raster3_color_dataset(tmp_path_factory):
"""Set up a GRASS session and create test raster3 with color rules."""

tmp_path = tmp_path_factory.mktemp("raster3_color_test")
location_name = "test_location"
location_path = setup_grass_location(tmp_path, location_name)

with gs.setup.init(location_path, env=os.environ.copy()) as session:
configure_grass_region(session.env)

raster3_names = create_test_rasters3(session.env)
apply_random_color_to_rasters3(raster3_names, session.env)

yield SimpleNamespace(
session=session,
raster3_names=raster3_names,

Check failure on line 81 in raster/r.colors.out/tests/conftest.py

View workflow job for this annotation

GitHub Actions / Python Code Quality Checks (ubuntu-22.04)

Ruff (W291)

raster/r.colors.out/tests/conftest.py:81:41: W291 Trailing whitespace
env=session.env,
)

Check failure on line 83 in raster/r.colors.out/tests/conftest.py

View workflow job for this annotation

GitHub Actions / Python Code Quality Checks (ubuntu-22.04)

Ruff (W292)

raster/r.colors.out/tests/conftest.py:83:10: W292 No newline at end of file
23 changes: 23 additions & 0 deletions raster/r.colors.out/tests/r3_colors_out_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Tests of r3.colors.out"""

import pytest
import json
import grass.script as gs

def validate_json_structure(data):
"""Validate the structure and content of the JSON output."""
assert isinstance(data, list), "Output data should be a list of entries."
assert len(data) == 8, "The length of the output JSON does not match the expected value of 8."

assert all("value" in entry for entry in data), "Not all entries contain the 'value' key."
assert all("rgb" in entry for entry in data), "Not all entries contain the 'rgb' key."

assert any(entry.get("value") == "nv" for entry in data), "No entry contains 'nv' as the value."
assert any(entry.get("value") == "default" for entry in data), "No entry contains 'default' as the value."

def test_r3_colors_out_json(raster3_color_dataset):
"""Test r3.colors.out command for JSON output format."""
session = raster3_color_dataset
data = gs.parse_command("r3.colors.out", map=session.raster3_names, format="json", env=session.env)

validate_json_structure(data)

Check failure on line 23 in raster/r.colors.out/tests/r3_colors_out_test.py

View workflow job for this annotation

GitHub Actions / Python Code Quality Checks (ubuntu-22.04)

Ruff (W292)

raster/r.colors.out/tests/r3_colors_out_test.py:23:34: W292 No newline at end of file
23 changes: 23 additions & 0 deletions raster/r.colors.out/tests/r_colors_out_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Tests of r.colors.out"""

import pytest
import json
import grass.script as gs

def validate_json_structure(data):
"""Validate the structure and content of the JSON output."""
assert isinstance(data, list), "Output data should be a list of entries."
assert len(data) == 31, "The length of the output JSON does not match the expected value of 31."

assert all("value" in entry for entry in data), "Not all entries contain the 'value' key."
assert all("rgb" in entry for entry in data), "Not all entries contain the 'rgb' key."

assert any(entry.get("value") == "nv" for entry in data), "No entry contains 'nv' as the value."
assert any(entry.get("value") == "default" for entry in data), "No entry contains 'default' as the value."

def test_r_colors_out_json(raster_color_dataset):
"""Test r.colors.out command for JSON output format."""
session = raster_color_dataset
data = gs.parse_command("r.colors.out", map=session.raster_names, format="json", env=session.env)

validate_json_structure(data)

0 comments on commit 8e92475

Please sign in to comment.