Skip to content
4 changes: 2 additions & 2 deletions vector/v.net/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ MODULE_TOPDIR = ../..

PGM=v.net

LIBES = $(VECTORLIB) $(GISLIB) $(DBMILIB)
DEPENDENCIES = $(VECTORDEP) $(GISDEP) $(DBMIDEP)
LIBES = $(VECTORLIB) $(GISLIB) $(DBMILIB) $(PARSONLIB)
DEPENDENCIES = $(VECTORDEP) $(GISDEP) $(DBMIDEP) $(PARSONDEP)
EXTRA_INC = $(VECT_INC)
EXTRA_CFLAGS = $(VECT_CFLAGS)

Expand Down
8 changes: 8 additions & 0 deletions vector/v.net/args.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ void define_options(struct opt *opt)
opt->tucfield->key = "turn_cat_layer";
opt->tucfield->required = NO;
opt->tucfield->guisection = _("Turntable");

opt->format = G_define_standard_option(G_OPT_F_FORMAT);
opt->format->key = "format";
opt->format->type = TYPE_STRING;
opt->format->required = NO;
opt->format->options = "plain,json";
opt->format->answer = "plain";
opt->format->description = _("Output");
Comment on lines +124 to +130
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are already part of the standard option definition.

Suggested change
opt->format = G_define_standard_option(G_OPT_F_FORMAT);
opt->format->key = "format";
opt->format->type = TYPE_STRING;
opt->format->required = NO;
opt->format->options = "plain,shell,json";
opt->format->answer = "plain";
opt->format->description = _("Output");
opt->format = G_define_standard_option(G_OPT_F_FORMAT);
opt->format->required = NO;
opt->format->options = "plain,shell,json";
opt->format->answer = "plain";
opt->format->description = _("Output");

}

void parse_arguments(const struct opt *opt, int *afield, int *nfield,
Expand Down
3 changes: 2 additions & 1 deletion vector/v.net/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <grass/vector.h>
#include <grass/glocale.h>
#include "proto.h"
#include <grass/gjson.h>

int main(int argc, char **argv)
{
Expand Down Expand Up @@ -172,7 +173,7 @@ int main(int argc, char **argv)
turntable(&opt);
}
else { /* report */
report(In, afield, nfield, act);
report(In, afield, nfield, act, opt.format->answer);
}

if (In)
Expand Down
3 changes: 2 additions & 1 deletion vector/v.net/proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct opt {
struct Option *file;
struct Option *type;
struct Flag *cats_flag, *snap_flag;
struct Option *format;
};

/* arcs.c */
Expand All @@ -30,6 +31,6 @@ int connect_arcs(struct Map_info *, struct Map_info *, struct Map_info *, int,
int nodes(struct Map_info *, struct Map_info *, int, int);

/* report.c */
int report(struct Map_info *, int, int, int);
int report(struct Map_info *, int, int, int, const char *);

void turntable(struct opt *);
89 changes: 78 additions & 11 deletions vector/v.net/report.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
#include <grass/vector.h>
#include <grass/glocale.h>
#include "proto.h"
#include <grass/gjson.h>

int report(struct Map_info *In, int afield, int nfield, int action)
int report(struct Map_info *In, int afield, int nfield, int action,
const char *format)
{
int i, j, line, nlines, ltype, node, nnodes;
int cat_line, cat_node[2];
Expand All @@ -23,6 +25,12 @@ int report(struct Map_info *In, int afield, int nfield, int action)

if (action == TOOL_REPORT) {
struct boxlist *List;
G_JSON_Value *root_value = NULL;
G_JSON_Array *root_array = NULL;
if (format && strcmp(format, "json") == 0) {
root_value = G_json_value_init_array();
root_array = G_json_array(root_value);
}

List = Vect_new_boxlist(0);

Expand Down Expand Up @@ -67,8 +75,32 @@ int report(struct Map_info *In, int afield, int nfield, int action)
G_warning(_("%d points found: %g %g %g line category: %d"),
nnodes, x, y, z, cat_line);
}
fprintf(stdout, "%d %d %d\n", cat_line, cat_node[0], cat_node[1]);
if (root_array) {
G_JSON_Value *item_value = G_json_value_init_object();
G_JSON_Object *item_obj = G_json_object(item_value);

G_json_object_set_number(item_obj, "line_cat", cat_line);
G_json_object_set_number(item_obj, "start_node_cat",
cat_node[0]);
G_json_object_set_number(item_obj, "end_node_cat", cat_node[1]);

G_json_array_append_value(root_array, item_value);
}
else {
fprintf(stdout, "%d %d %d\n", cat_line, cat_node[0],
cat_node[1]);
}
}

if (root_value) {
char *json_str = G_json_serialize_to_string_pretty(root_value);
if (json_str) {
fprintf(stdout, "%s\n", json_str);
}
G_json_free_serialized_string(json_str);
G_json_value_free(root_value);
}

Vect_destroy_boxlist(List);
}
else { /* node report */
Expand All @@ -77,6 +109,11 @@ int report(struct Map_info *In, int afield, int nfield, int action)

List = Vect_new_list();

G_JSON_Value *root_val = (format && strcmp(format, "json") == 0)
? G_json_value_init_array()
: NULL;
G_JSON_Array *root_arr = G_json_array(root_val);

for (i = 1; i <= nlines; i++) {

if (Vect_get_line_type(In, i) != GV_POINT)
Expand All @@ -102,9 +139,19 @@ int report(struct Map_info *In, int afield, int nfield, int action)
for (j = 0; j < Cats->n_cats; j++) {
if (Cats->field[j] == nfield) {
int count = 0;

fprintf(stdout, "%d ", Cats->cat[j]);

G_JSON_Value *item_val =
root_arr ? G_json_value_init_object() : NULL;
G_JSON_Value *lines_val =
root_arr ? G_json_value_init_array() : NULL;

if (root_arr) {
G_json_object_set_number(
G_json_value_get_object(item_val), "node_cat",
Cats->cat[j]);
}
else {
fprintf(stdout, "%d ", Cats->cat[j]);
}
/* Loop through all lines */
for (k = 0; k < nelem; k++) {
elem = abs(Vect_get_node_line(In, node, k));
Expand All @@ -115,19 +162,39 @@ int report(struct Map_info *In, int afield, int nfield, int action)
/* Loop through all cats of line */
for (l = 0; l < Cats2->n_cats; l++) {
if (Cats2->field[l] == afield) {
if (count > 0)
fprintf(stdout, ",");

fprintf(stdout, "%d", Cats2->cat[l]);
count++;
if (root_arr)
G_json_array_append_number(
G_json_array(lines_val),
Cats2->cat[l]);
else {
if (count > 0)
fprintf(stdout, ",");
fprintf(stdout, "%d", Cats2->cat[l]);
count++;
}
}
}
}
fprintf(stdout, "\n");
if (root_arr) {
G_json_object_set_value(
G_json_value_get_object(item_val), "lines",
lines_val);
G_json_array_append_value(root_arr, item_val);
}
else
fprintf(stdout, "\n");
}
}
}
}
if (root_val) {
char *s = G_json_serialize_to_string_pretty(root_val);
if (s) {
fprintf(stdout, "%s\n", s);
G_json_free_serialized_string(s);
}
G_json_value_free(root_val);
}
Vect_destroy_list(List);
}
Vect_destroy_cats_struct(Cats);
Expand Down
54 changes: 52 additions & 2 deletions vector/v.net/testsuite/test_v_net.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,66 @@
from grass.gunittest.case import TestCase
from grass.gunittest.main import test
from grass.script.core import read_command

from grass.tools import Tools
import json

class TestVNet(TestCase):
network = "test_vnet"

def tearDown(self):
"""Remove viewshed map after each test method"""
# TODO: eventually, removing maps should be handled through testing framework functions
self.runModule("g.remove", flags="f", type="vector", name=self.network)

def test_nreport_json(self):
self.runModule("v.net", input="streets", points="schools",
output=self.network, operation="connect",
threshold=400, flags="c")

tools = Tools(quiet=True)
result = tools.v_net(
input=self.network,
operation="nreport",
node_layer=2,
format="json"
)

try:
data = json.loads(result.text)
except json.JSONDecodeError:
self.fail(f"nreport produced invalid JSON: {result.text}")

self.assertIsInstance(data, list)
self.assertGreater(len(data), 0, "nreport output is empty")

first_row = data[0]
self.assertIn("node_cat", first_row)
self.assertIn("lines", first_row)

self.assertIsInstance(first_row["node_cat"], int)
self.assertIsInstance(first_row["lines"], list)
self.assertGreater(len(first_row["lines"]), 0)

def test_report_json(self):
tools = Tools(quiet=True)
result = tools.v_net(
input="streets",
operation="report",
arc_layer=1,
node_layer=2,
format="json"
)

try:
data = json.loads(result.text)
except json.JSONDecodeError:
self.fail(f"report produced invalid JSON: {result.text}")

self.assertIsInstance(data, list)
self.assertGreater(len(data), 0, "report output is empty")

self.assertIn("line_cat", data[0])
self.assertIsInstance(data[0]["line_cat"], int)

def test_nodes(self):
"""Test"""
self.assertModule(
Expand Down
65 changes: 65 additions & 0 deletions vector/v.net/v.net.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,71 @@ Following example generates a vector map with turntable:
v.net operation=turntable in=railroads out=railroads_ttb
```

### Produce node count in JSON format

The **format** option allows choosing the output style.
By default, **plain** text is used.
Choosing **json** will produce a structured output,
which is particularly useful for automated scripts and testing.

Generating nodes and getting the result in JSON:

```sh
v.net input=streets points=schools output=streets_net operation=connect threshold=400 -c

# verify result
v.net input=streets_net operation=nreport format=json
```

#### Example Output

```json
[
{
"node_cat": 166,
"lines": [
49912
]
},
{
"node_cat": 167,
"lines": [
49913
]
}
]
```

### Produce Report in JSON format

The **report** operation provides details about the arcs in the network.
Using the **-c** flag ensures that new categories are assigned to the nodes,
providing a complete connectivity report in JSON format.

```sh
v.net input=streets operation=nodes output=streets_nodes -c

# verify result
v.net input=streets_nodes operation=report format=json
```

#### Example output

```json
[
{
"line_cat": 49745,
"start_node_cat": 41812,
"end_node_cat": 19875
},
{
"line_cat": 49746,
"start_node_cat": 15789,
"end_node_cat": 41813
}
]
```

## SEE ALSO

*[g.gui.vdigit](g.gui.vdigit.md), [v.edit](v.edit.md), [Vector Network
Expand Down
Loading