Skip to content

Commit 4ff2e5c

Browse files
Abhimanyu GuptaAbhimanyu Gupta
authored andcommitted
r.geomorphon: use gjson/parson for JSON profile output
1 parent 83bfa50 commit 4ff2e5c

File tree

3 files changed

+154
-31
lines changed

3 files changed

+154
-31
lines changed

raster/r.geomorphon/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ MODULE_TOPDIR = ../..
22

33
PGM = r.geomorphon
44

5-
LIBES = $(RASTERLIB) $(GISLIB) $(MATHLIB)
6-
DEPENDENCIES = $(RASTERDEP) $(GISDEP)
5+
LIBES = $(RASTERLIB) $(GISLIB) $(MATHLIB) $(PARSONLIB)
6+
DEPENDENCIES = $(RASTERDEP) $(GISDEP) $(PARSONDEP)
77

88
include $(MODULE_TOPDIR)/include/Make/Module.make
99

raster/r.geomorphon/profile.c

Lines changed: 113 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <stdio.h>
22
#include "local_proto.h"
3+
#include <grass/gjson.h>
34

45
#define JSON_MIN_INDENT 1
56
#define YAML_MIN_INDENT 0
@@ -251,45 +252,128 @@ static const char *format_token_common(const struct token *t)
251252
*/
252253
static unsigned write_json(FILE *f)
253254
{
254-
unsigned i, indent = JSON_MIN_INDENT;
255+
unsigned i;
256+
G_JSON_Value *stack_vals[MAX_STACK_ELEMS];
257+
unsigned stack_top = 0;
258+
G_JSON_Value *root = G_json_value_init_object();
259+
if (!root)
260+
return 0;
261+
stack_vals[stack_top++] = root;
255262

256-
WRITE_VAL(f, "%s\n", "{");
257263
for (i = 0; i < size; i++) {
258-
const char *val;
259-
260-
/* Add a comma unless there is no data tokens immediately after. */
261-
const char *comma =
262-
(i + 1 == size) || (i + 1 < size && token[i + 1].type == T_ESO)
263-
? ""
264-
: ",";
264+
G_JSON_Object *parent = G_json_object(stack_vals[stack_top - 1]);
265+
if (!parent) {
266+
G_json_value_free(root);
267+
return 0;
268+
}
265269

266270
switch (token[i].type) {
267-
case T_SSO:
268-
WRITE_INDENT(f, indent);
269-
indent++;
270-
WRITE_VAL(f, "\"%s\": {\n", token[i].key);
271-
continue;
271+
case T_SSO: {
272+
if (stack_top >= MAX_STACK_ELEMS) {
273+
G_json_value_free(root);
274+
return 0;
275+
}
276+
G_JSON_Value *obj = G_json_value_init_object();
277+
if (!obj) {
278+
G_json_value_free(root);
279+
return 0;
280+
}
281+
if (G_json_object_set_value(parent, token[i].key, obj) !=
282+
G_JSONSuccess) {
283+
G_json_value_free(obj);
284+
G_json_value_free(root);
285+
return 0;
286+
}
287+
stack_vals[stack_top++] = obj;
288+
} break;
272289
case T_ESO:
273-
if (indent == JSON_MIN_INDENT)
290+
if (stack_top <= 1) {
291+
/* can't pop root */
292+
G_json_value_free(root);
274293
return 0;
275-
indent--;
276-
WRITE_INDENT(f, indent);
277-
WRITE_VAL(f, "}%s\n", comma);
278-
continue;
279-
default:
280-
val = quote_val(token[i].type, format_token_common(token + i));
294+
}
295+
stack_top--;
281296
break;
282-
}
283-
if (!val)
297+
case T_BLN:
298+
if (G_json_object_set_boolean(parent, token[i].key,
299+
token[i].int_val ? 1 : 0) !=
300+
G_JSONSuccess) {
301+
G_json_value_free(root);
302+
return 0;
303+
}
304+
break;
305+
case T_INT:
306+
if (G_json_object_set_number(parent, token[i].key,
307+
(double)token[i].int_val) !=
308+
G_JSONSuccess) {
309+
G_json_value_free(root);
310+
return 0;
311+
}
312+
break;
313+
case T_DBL:
314+
if (isnan(token[i].dbl_val)) {
315+
if (G_json_object_set_null(parent, token[i].key) !=
316+
G_JSONSuccess) {
317+
G_json_value_free(root);
318+
return 0;
319+
}
320+
}
321+
else {
322+
if (G_json_object_set_number(parent, token[i].key,
323+
token[i].dbl_val) !=
324+
G_JSONSuccess) {
325+
G_json_value_free(root);
326+
return 0;
327+
}
328+
}
329+
break;
330+
case T_MTR:
331+
if (isnan(token[i].dbl_val)) {
332+
if (G_json_object_set_null(parent, token[i].key) !=
333+
G_JSONSuccess) {
334+
G_json_value_free(root);
335+
return 0;
336+
}
337+
}
338+
else {
339+
if (G_json_object_set_number(parent, token[i].key,
340+
token[i].dbl_val) !=
341+
G_JSONSuccess) {
342+
G_json_value_free(root);
343+
return 0;
344+
}
345+
}
346+
break;
347+
case T_STR:
348+
if (G_json_object_set_string(parent, token[i].key,
349+
token[i].str_val) != G_JSONSuccess) {
350+
G_json_value_free(root);
351+
return 0;
352+
}
353+
break;
354+
default:
355+
G_json_value_free(root);
284356
return 0;
285-
WRITE_INDENT(f, indent);
286-
WRITE_VAL(f, "\"%s\": ", token[i].key);
287-
WRITE_VAL(f, "%s", val);
288-
WRITE_VAL(f, "%s\n", comma);
357+
}
289358
}
290-
if (indent != JSON_MIN_INDENT || overflow)
359+
360+
if (stack_top != 1 || overflow) {
361+
G_json_value_free(root);
291362
return 0;
292-
WRITE_VAL(f, "%s\n", "}");
363+
}
364+
365+
char *s = G_json_serialize_to_string_pretty(root);
366+
if (!s) {
367+
G_json_value_free(root);
368+
return 0;
369+
}
370+
if (fprintf(f, "%s\n", s) < 0) {
371+
G_json_free_serialized_string(s);
372+
G_json_value_free(root);
373+
return 0;
374+
}
375+
G_json_free_serialized_string(s);
376+
G_json_value_free(root);
293377
return 1;
294378
}
295379

raster/r.geomorphon/testsuite/test_r_geom.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
from grass.gunittest.case import TestCase
1414
from grass.gunittest.main import test
1515
from grass.script.core import read_command
16+
import json
17+
import os
18+
from grass.script import core as gcore
19+
import tempfile
20+
import pathlib
1621

1722
synth_out = """1 flat
1823
3 ridge
@@ -78,6 +83,40 @@ def test_sint(self):
7883
category = read_command("r.category", map=self.outsint)
7984
self.assertEqual(first=synth_out, second=category)
8085

86+
def test_profile_json(self):
87+
# ensure region is set to the test elevation
88+
self.runModule("g.region", raster=self.inele)
89+
region = gcore.region()
90+
east = float(region.get("east", region["e"]))
91+
west = float(region.get("west", region["w"]))
92+
north = float(region.get("north", region["n"]))
93+
south = float(region.get("south", region["s"]))
94+
e = (east + west) / 2.0
95+
n = (north + south) / 2.0
96+
fd, tmp = tempfile.mkstemp(prefix="rgeom_profile_", suffix=".json")
97+
os.close(fd)
98+
try:
99+
self.runModule(
100+
"r.geomorphon",
101+
elevation=self.inele,
102+
search=3,
103+
profiledata=tmp,
104+
profileformat="json",
105+
coords=f"{e},{n}",
106+
)
107+
self.assertTrue(pathlib.Path(tmp).exists())
108+
self.assertGreater(pathlib.Path(tmp).stat().st_size, 0)
109+
with open(tmp, encoding="utf-8") as fh:
110+
data = json.load(fh)
111+
# basic sanity checks on JSON structure
112+
self.assertIn("final_results", data)
113+
self.assertIn("computation_parameters", data)
114+
self.assertIsInstance(data["final_results"], dict)
115+
self.assertIsInstance(data["computation_parameters"], dict)
116+
finally:
117+
if pathlib.Path(tmp).exists():
118+
os.remove(tmp)
119+
81120

82121
if __name__ == "__main__":
83122
test()

0 commit comments

Comments
 (0)