Skip to content

Commit

Permalink
r.horizon: add json output (#3534)
Browse files Browse the repository at this point in the history
  • Loading branch information
petrasovaa committed Mar 26, 2024
1 parent 071e8db commit 2869b8a
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 9 deletions.
2 changes: 1 addition & 1 deletion raster/r.horizon/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ MODULE_TOPDIR = ../..

PGM = r.horizon

LIBES = $(GPROJLIB) $(RASTERLIB) $(GISLIB) $(MATHLIB) $(PROJLIB)
LIBES = $(PARSONLIB) $(GPROJLIB) $(RASTERLIB) $(GISLIB) $(MATHLIB) $(PROJLIB)
DEPENDENCIES = $(GPROJDEP) $(RASTERDEP) $(GISDEP)
EXTRA_INC = $(PROJINC) $(GDALCFLAGS)

Expand Down
84 changes: 76 additions & 8 deletions raster/r.horizon/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Program was refactored by Anna Petrasova to remove most global variables.
#include <grass/raster.h>
#include <grass/gprojects.h>
#include <grass/glocale.h>
#include <grass/parson.h>

#define WHOLE_RASTER 1
#define SINGLE_POINT 0
Expand Down Expand Up @@ -112,6 +113,8 @@ typedef struct {
const char *horizon_basename;
} Settings;

enum OutputFormat { PLAIN, JSON };

int INPUT(Geometry *geometry, const char *elevin);
int OUTGR(const Settings *settings, char *shad_filename,
struct Cell_head *cellhd);
Expand All @@ -120,7 +123,8 @@ void com_par(const Geometry *geometry, OriginAngle *origin_angle, double angle,
double horizon_height(const Geometry *geometry, const OriginPoint *origin_point,
const OriginAngle *origin_angle);
void calculate_point_mode(const Settings *settings, const Geometry *geometry,
double xcoord, double ycoord, FILE *fp);
double xcoord, double ycoord, FILE *fp,
enum OutputFormat format);
int new_point(const Geometry *geometry, const OriginPoint *origin_point,
const OriginAngle *origin_angle, SearchPoint *search_point,
HorizonProperties *horizon);
Expand Down Expand Up @@ -149,12 +153,13 @@ double distance(double x1, double x2, double y1, double y2, double coslatsq)
int main(int argc, char *argv[])
{
double xcoord, ycoord;
enum OutputFormat format;

struct GModule *module;
struct {
struct Option *elevin, *dist, *coord, *direction, *horizon, *step,
*start, *end, *bufferzone, *e_buff, *w_buff, *n_buff, *s_buff,
*maxdistance, *output;
*maxdistance, *format, *output;
} parm;

struct {
Expand Down Expand Up @@ -288,6 +293,17 @@ int main(int argc, char *argv[])
parm.dist->description = _("Sampling distance step coefficient (0.5-1.5)");
parm.dist->guisection = _("Optional");

parm.format = G_define_option();
parm.format->key = "format";
parm.format->type = TYPE_STRING;
parm.format->required = YES;
parm.format->label = _("Output format used for point mode");
parm.format->options = "plain,json";
parm.format->descriptions = "plain;Plain text output;"
"json;JSON (JavaScript Object Notation);";
parm.format->answer = "plain";
parm.format->guisection = _("Point mode");

parm.output = G_define_standard_option(G_OPT_F_OUTPUT);
parm.output->key = "file";
parm.output->required = NO;
Expand Down Expand Up @@ -356,6 +372,10 @@ int main(int argc, char *argv[])
else {
G_debug(1, "Setting mode: SINGLE_POINT");
mode = SINGLE_POINT;
if (strcmp(parm.format->answer, "json") == 0)
format = JSON;
else
format = PLAIN;
if (sscanf(parm.coord->answer, "%lf,%lf", &xcoord, &ycoord) != 2) {
G_fatal_error(_(
"Can't read the coordinates from the \"coordinate\" option."));
Expand Down Expand Up @@ -556,7 +576,7 @@ int main(int argc, char *argv[])
INPUT(&geometry, elevin);
if (mode == SINGLE_POINT) {
/* Calculate the horizon for one single point */
calculate_point_mode(&settings, &geometry, xcoord, ycoord, fp);
calculate_point_mode(&settings, &geometry, xcoord, ycoord, fp, format);
}
else {
calculate_raster_mode(&settings, &geometry, &cellhd, &new_cellhd,
Expand Down Expand Up @@ -733,7 +753,8 @@ void com_par(const Geometry *geometry, OriginAngle *origin_angle, double angle,
}

void calculate_point_mode(const Settings *settings, const Geometry *geometry,
double xcoord, double ycoord, FILE *fp)
double xcoord, double ycoord, FILE *fp,
enum OutputFormat format)
{
/*
xg0 = xx0 = (double)xcoord * stepx;
Expand Down Expand Up @@ -774,8 +795,29 @@ void calculate_point_mode(const Settings *settings, const Geometry *geometry,
double printangle = settings->single_direction;

origin_point.maxlength = settings->fixedMaxLength;
fprintf(fp, "azimuth,horizon_height\n");

/* JSON variables and formating */
JSON_Value *root_value, *origin_value, *azimuths_value, *horizons_value;
JSON_Array *coordinates, *azimuths, *horizons;
JSON_Object *origin;
json_set_float_serialization_format("%lf");

switch (format) {
case PLAIN:
fprintf(fp, "azimuth,horizon_height\n");
break;
case JSON:
root_value = json_value_init_array();
coordinates = json_value_get_array(root_value);
origin_value = json_value_init_object();
origin = json_value_get_object(origin_value);
json_object_set_number(origin, "x", xcoord);
json_object_set_number(origin, "y", ycoord);
azimuths_value = json_value_init_array();
azimuths = json_value_get_array(azimuths_value);
horizons_value = json_value_init_array();
horizons = json_value_get_array(horizons_value);
break;
}
for (int i = 0; i < printCount; i++) {
OriginAngle origin_angle;
com_par(geometry, &origin_angle, angle, xp, yp);
Expand All @@ -793,10 +835,26 @@ void calculate_point_mode(const Settings *settings, const Geometry *geometry,
tmpangle = 360. - printangle + 90.;
if (tmpangle >= 360.)
tmpangle = tmpangle - 360.;
fprintf(fp, "%lf,%lf\n", tmpangle, shadow_angle);
switch (format) {
case PLAIN:
fprintf(fp, "%lf,%lf\n", tmpangle, shadow_angle);
break;
case JSON:
json_array_append_number(azimuths, tmpangle);
json_array_append_number(horizons, shadow_angle);
break;
}
}
else {
fprintf(fp, "%lf,%lf\n", printangle, shadow_angle);
switch (format) {
case PLAIN:
fprintf(fp, "%lf,%lf\n", printangle, shadow_angle);
break;
case JSON:
json_array_append_number(azimuths, printangle);
json_array_append_number(horizons, shadow_angle);
break;
}
}

angle += dfr_rad;
Expand All @@ -812,6 +870,16 @@ void calculate_point_mode(const Settings *settings, const Geometry *geometry,
else if (printangle > 360.)
printangle -= 360;
} /* end of for loop over angles */

if (format == JSON) {
json_object_set_value(origin, "azimuth", azimuths_value);
json_object_set_value(origin, "horizon_height", horizons_value);
json_array_append_value(coordinates, origin_value);
char *json_string = json_serialize_to_string_pretty(root_value);
fprintf(fp, "%s\n", json_string);
json_free_serialized_string(json_string);
json_value_free(root_value);
}
fclose(fp);
}

Expand Down
28 changes: 28 additions & 0 deletions raster/r.horizon/testsuite/test_r_horizon.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
License (>=v2). Read the file COPYING that comes with GRASS
for details.
"""
import json

from grass.gunittest.case import TestCase
from grass.gunittest.main import test
Expand Down Expand Up @@ -154,6 +155,33 @@ def test_point_mode_multiple_direction(self):
stdout = module.outputs.stdout
self.assertMultiLineEqual(first=ref2, second=stdout)

def test_point_mode_multiple_direction_json(self):
"""Test mode with 1 point and multiple directions with JSON"""
module = SimpleModule(
"r.horizon",
elevation="elevation",
coordinates=(634720, 216180),
output=self.horizon,
direction=180,
step=20,
format="json",
)
self.assertModule(module)
stdout = json.loads(module.outputs.stdout)
azimuths = []
horizons = []
reference = {}
for line in ref2.splitlines()[1:]:
azimuth, horizon = line.split(",")
azimuths.append(float(azimuth))
horizons.append(float(horizon))
reference["x"] = 634720.0
reference["y"] = 216180.0
reference["azimuth"] = azimuths
reference["horizon_height"] = horizons

self.assertListEqual([reference], stdout)

def test_point_mode_multiple_direction_artificial(self):
"""Test mode with 1 point and multiple directions with artificial surface"""
module = SimpleModule(
Expand Down

0 comments on commit 2869b8a

Please sign in to comment.