From 378c2f310a0501567396c93ca6b5eb98d330f749 Mon Sep 17 00:00:00 2001 From: Kriti Birda Date: Fri, 12 Jul 2024 00:44:26 +0530 Subject: [PATCH] v.to.db: add JSON support --- vector/v.to.db/Makefile | 2 +- vector/v.to.db/global.h | 6 +- vector/v.to.db/main.c | 2 +- vector/v.to.db/parse.c | 11 + vector/v.to.db/report.c | 538 ++++++++++++++++++++++++++++++---------- 5 files changed, 420 insertions(+), 139 deletions(-) diff --git a/vector/v.to.db/Makefile b/vector/v.to.db/Makefile index 2ab35e1f278..356e2579984 100644 --- a/vector/v.to.db/Makefile +++ b/vector/v.to.db/Makefile @@ -3,7 +3,7 @@ MODULE_TOPDIR = ../.. PGM=v.to.db -LIBES = $(VECTORLIB) $(DBMILIB) $(GISLIB) $(MATHLIB) +LIBES = $(VECTORLIB) $(DBMILIB) $(GISLIB) $(MATHLIB) $(PARSONLIB) DEPENDENCIES = $(VECTORDEP) $(DBMIDEP) $(GISDEP) EXTRA_INC = $(VECT_INC) EXTRA_CFLAGS = $(VECT_CFLAGS) diff --git a/vector/v.to.db/global.h b/vector/v.to.db/global.h index 51d166f5912..025ea7047eb 100644 --- a/vector/v.to.db/global.h +++ b/vector/v.to.db/global.h @@ -1,5 +1,8 @@ #include #include +#include + +enum OutputFormat { PLAIN, JSON }; struct value { int cat; /* category */ @@ -33,6 +36,7 @@ struct options { int units; int qfield; /* query field */ char *fs; + enum OutputFormat format; }; extern struct options options; @@ -96,7 +100,7 @@ int parse_command_line(int, char *[]); int query(struct Map_info *); /* report.c */ -int report(void); +int report(enum OutputFormat format); int print_stat(void); /* units.c */ diff --git a/vector/v.to.db/main.c b/vector/v.to.db/main.c index 852d8fe9d14..d533c4e691f 100644 --- a/vector/v.to.db/main.c +++ b/vector/v.to.db/main.c @@ -406,7 +406,7 @@ int main(int argc, char *argv[]) conv_units(); if (options.print || options.total) { - report(); + report(options.format); } else { update(&Map); diff --git a/vector/v.to.db/parse.c b/vector/v.to.db/parse.c index 7a72c350d83..919b78f3787 100644 --- a/vector/v.to.db/parse.c +++ b/vector/v.to.db/parse.c @@ -22,6 +22,7 @@ int parse_command_line(int argc, char *argv[]) struct Option *units; struct Option *qcol; struct Option *fs; + struct Option *format; } parms; struct { struct Flag *h, *p, *s, *t; @@ -116,6 +117,9 @@ int parse_command_line(int argc, char *argv[]) parms.fs->label = _("Field separator for print mode"); parms.fs->guisection = _("Print"); + parms.format = G_define_standard_option(G_OPT_F_FORMAT); + parms.format->guisection = _("Print"); + flags.p = G_define_flag(); flags.p->key = 'p'; flags.p->description = _("Print only"); @@ -168,6 +172,13 @@ int parse_command_line(int argc, char *argv[]) options.fs = G_option_to_separator(parms.fs); + if (strcmp(parms.format->answer, "json") == 0) { + options.format = JSON; + } + else { + options.format = PLAIN; + } + /* Check number of columns */ ncols = 0; options.col[0] = NULL; diff --git a/vector/v.to.db/report.c b/vector/v.to.db/report.c index 41ed02bdf3c..4f8b5a3e504 100644 --- a/vector/v.to.db/report.c +++ b/vector/v.to.db/report.c @@ -3,10 +3,23 @@ #include #include "global.h" -int report(void) +int report(enum OutputFormat format) { + JSON_Value *records_value, *record_value, *root_value; + JSON_Array *records_array; + JSON_Object *record, *root_object; + + if (format == JSON) { + root_value = json_value_init_object(); + root_object = json_object(root_value); + records_value = json_value_init_array(); + records_array = json_array(records_value); + } + int i, print_header = G_verbose() > G_verbose_min() || options.print_header; char left[20], right[20]; + int sum = 0; + double fsum = 0.0; if (!options.print && !(options.option == O_COUNT || options.option == O_LENGTH || @@ -17,57 +30,114 @@ int report(void) switch (options.option) { case O_CAT: - if (print_header) - fprintf(stdout, "cat\n"); - for (i = 0; i < vstat.rcat; i++) - fprintf(stdout, "%d\n", Values[i].cat); - break; - - case O_COUNT: - if (options.print) { + switch (format) { + case PLAIN: if (print_header) - fprintf(stdout, "cat%scount\n", options.fs); + fprintf(stdout, "cat\n"); for (i = 0; i < vstat.rcat; i++) - fprintf(stdout, "%d%s%d\n", Values[i].cat, options.fs, - Values[i].count1); + fprintf(stdout, "%d\n", Values[i].cat); + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + json_array_append_value(records_array, record_value); + } } - if (options.total) { - int sum = 0; + break; + + case O_COUNT: + switch (format) { + case PLAIN: + if (options.print) { + if (print_header) + fprintf(stdout, "cat%scount\n", options.fs); + for (i = 0; i < vstat.rcat; i++) + fprintf(stdout, "%d%s%d\n", Values[i].cat, options.fs, + Values[i].count1); + } + if (options.total) { + int sum = 0; + for (i = 0; i < vstat.rcat; i++) { + sum += Values[i].count1; + } + fprintf(stdout, "total count%s%d\n", options.fs, sum); + } + break; + case JSON: for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + json_object_set_number(record, "count", Values[i].count1); + json_array_append_value(records_array, record_value); sum += Values[i].count1; } - fprintf(stdout, "total count%s%d\n", options.fs, sum); + json_object_dotset_number(root_object, "totals.count", sum); + break; } break; case O_AREA: - if (options.print) { - if (print_header) - fprintf(stdout, "cat%sarea\n", options.fs); - for (i = 0; i < vstat.rcat; i++) - fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, - Values[i].d1); - } - if (options.total) { - double sum = 0.0; + switch (format) { + case PLAIN: + if (options.print) { + if (print_header) + fprintf(stdout, "cat%sarea\n", options.fs); + for (i = 0; i < vstat.rcat; i++) + fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, + Values[i].d1); + } + if (options.total) { + double sum = 0.0; + for (i = 0; i < vstat.rcat; i++) { + sum += Values[i].d1; + } + fprintf(stdout, "total area%s%.15g\n", options.fs, sum); + } + break; + case JSON: for (i = 0; i < vstat.rcat; i++) { - sum += Values[i].d1; + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + json_object_set_number(record, "area", Values[i].d1); + json_array_append_value(records_array, record_value); + fsum += Values[i].d1; } - fprintf(stdout, "total area%s%.15g\n", options.fs, sum); + json_object_dotset_number(root_object, "totals.area", fsum); + break; } break; case O_COMPACT: /* perimeter / perimeter of equivalent circle * perimeter of equivalent circle: 2.0 * sqrt(M_PI * area) */ - if (print_header) - fprintf(stdout, "cat%scompact\n", options.fs); - for (i = 0; i < vstat.rcat; i++) { - Values[i].d1 = Values[i].d2 / (2.0 * sqrt(M_PI * Values[i].d1)); - fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, - Values[i].d1); + switch (format) { + case PLAIN: + if (print_header) + fprintf(stdout, "cat%scompact\n", options.fs); + for (i = 0; i < vstat.rcat; i++) { + Values[i].d1 = Values[i].d2 / (2.0 * sqrt(M_PI * Values[i].d1)); + fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, + Values[i].d1); + } + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + + Values[i].d1 = Values[i].d2 / (2.0 * sqrt(M_PI * Values[i].d1)); + json_object_set_number(record, "compact", Values[i].d1); + + json_array_append_value(records_array, record_value); + } + break; } break; @@ -82,154 +152,350 @@ int report(void) * * avoid division by zero: * 2.0 * log(1 + perimeter) / log(1 + area) */ - if (print_header) - fprintf(stdout, "cat%sfd\n", options.fs); - for (i = 0; i < vstat.rcat; i++) { - if (Values[i].d1 == 1) /* log(1) == 0 */ - Values[i].d1 += 0.000001; - Values[i].d1 = 2.0 * log(Values[i].d2) / log(Values[i].d1); - fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, - Values[i].d1); + switch (format) { + case PLAIN: + if (print_header) + fprintf(stdout, "cat%sfd\n", options.fs); + for (i = 0; i < vstat.rcat; i++) { + if (Values[i].d1 == 1) /* log(1) == 0 */ + Values[i].d1 += 0.000001; + Values[i].d1 = 2.0 * log(Values[i].d2) / log(Values[i].d1); + fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, + Values[i].d1); + } + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + + if (Values[i].d1 == 1) /* log(1) == 0 */ + Values[i].d1 += 0.000001; + Values[i].d1 = 2.0 * log(Values[i].d2) / log(Values[i].d1); + json_object_set_number(record, "fd", Values[i].d1); + + json_array_append_value(records_array, record_value); + } + break; } break; case O_PERIMETER: - if (print_header) - fprintf(stdout, "cat%sperimeter\n", options.fs); - for (i = 0; i < vstat.rcat; i++) - fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, - Values[i].d1); + switch (format) { + case PLAIN: + if (print_header) + fprintf(stdout, "cat%sperimeter\n", options.fs); + for (i = 0; i < vstat.rcat; i++) + fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, + Values[i].d1); + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + json_object_set_number(record, "perimeter", Values[i].d1); + json_array_append_value(records_array, record_value); + } + break; + } break; case O_BBOX: - if (print_header) - fprintf(stdout, "cat%sN%sS%sE%sW\n", options.fs, options.fs, - options.fs, options.fs); - for (i = 0; i < vstat.rcat; i++) { - fprintf(stdout, "%d%s%.15g%s%.15g%s%.15g%s%.15g\n", Values[i].cat, - options.fs, Values[i].d1, options.fs, Values[i].d2, - options.fs, Values[i].d3, options.fs, Values[i].d4); + switch (format) { + case PLAIN: + if (print_header) + fprintf(stdout, "cat%sN%sS%sE%sW\n", options.fs, options.fs, + options.fs, options.fs); + for (i = 0; i < vstat.rcat; i++) { + fprintf(stdout, "%d%s%.15g%s%.15g%s%.15g%s%.15g\n", + Values[i].cat, options.fs, Values[i].d1, options.fs, + Values[i].d2, options.fs, Values[i].d3, options.fs, + Values[i].d4); + } + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + json_object_set_number(record, "north", Values[i].d1); + json_object_set_number(record, "south", Values[i].d2); + json_object_set_number(record, "east", Values[i].d3); + json_object_set_number(record, "west", Values[i].d4); + json_array_append_value(records_array, record_value); + } + break; } break; case O_LENGTH: - if (options.print) { + switch (format) { + case PLAIN: + if (options.print) { + if (print_header) + fprintf(stdout, "cat%slength\n", options.fs); + for (i = 0; i < vstat.rcat; i++) + fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, + Values[i].d1); + } + if (options.total) { + double sum = 0.0; + + for (i = 0; i < vstat.rcat; i++) { + sum += Values[i].d1; + } + fprintf(stdout, "total length%s%.15g\n", options.fs, sum); + } + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + json_object_set_number(record, "length", Values[i].d1); + json_array_append_value(records_array, record_value); + fsum += Values[i].d1; + } + json_object_dotset_number(root_object, "totals.length", fsum); + break; + } + break; + case O_SLOPE: + switch (format) { + case PLAIN: if (print_header) - fprintf(stdout, "cat%slength\n", options.fs); + fprintf(stdout, "cat%sslope\n", options.fs); for (i = 0; i < vstat.rcat; i++) fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, Values[i].d1); - } - if (options.total) { - double sum = 0.0; - + break; + case JSON: for (i = 0; i < vstat.rcat; i++) { - sum += Values[i].d1; + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + json_object_set_number(record, "slope", Values[i].d1); + json_array_append_value(records_array, record_value); } - fprintf(stdout, "total length%s%.15g\n", options.fs, sum); + break; } - break; - case O_SLOPE: - if (print_header) - fprintf(stdout, "cat%sslope\n", options.fs); - for (i = 0; i < vstat.rcat; i++) - fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, - Values[i].d1); break; case O_SINUOUS: - if (print_header) - fprintf(stdout, "cat%ssinuous\n", options.fs); - for (i = 0; i < vstat.rcat; i++) - fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, - Values[i].d1); - break; + switch (format) { + case PLAIN: + if (print_header) + fprintf(stdout, "cat%ssinuous\n", options.fs); + for (i = 0; i < vstat.rcat; i++) + fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, + Values[i].d1); + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + json_object_set_number(record, "sinuous", Values[i].d1); + json_array_append_value(records_array, record_value); + } + break; + } case O_COOR: case O_START: case O_END: - if (print_header) - fprintf(stdout, "cat%sx%sy%sz\n", options.fs, options.fs, - options.fs); - for (i = 0; i < vstat.rcat; i++) { - if (Values[i].count1 == 1) - fprintf(stdout, "%d%s%.15g%s%.15g%s%.15g\n", Values[i].cat, - options.fs, Values[i].d1, options.fs, Values[i].d2, - options.fs, Values[i].d3); + switch (format) { + case PLAIN: + if (print_header) + fprintf(stdout, "cat%sx%sy%sz\n", options.fs, options.fs, + options.fs); + for (i = 0; i < vstat.rcat; i++) { + if (Values[i].count1 == 1) + fprintf(stdout, "%d%s%.15g%s%.15g%s%.15g\n", Values[i].cat, + options.fs, Values[i].d1, options.fs, Values[i].d2, + options.fs, Values[i].d3); + } + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + if (Values[i].count1 == 1) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + json_object_set_number(record, "x", Values[i].d1); + json_object_set_number(record, "y", Values[i].d2); + json_object_set_number(record, "z", Values[i].d3); + json_array_append_value(records_array, record_value); + } + } + break; } break; case O_SIDES: - if (print_header) - fprintf(stdout, "cat%sleft%sright\n", options.fs, options.fs); - for (i = 0; i < vstat.rcat; i++) { - if (Values[i].count1 == 1) { - if (Values[i].i1 >= 0) - sprintf(left, "%d", Values[i].i1); - else - sprintf(left, "-1"); /* NULL, no area/cat */ - } - else if (Values[i].count1 > 1) { - sprintf(left, "-"); - } - else { /* Values[i].count1 == 0 */ - /* It can be OK if the category is assigned to an element - type which is not GV_BOUNDARY */ - /* -> TODO: print only if there is boundary with that cat */ - sprintf(left, "-"); - } + switch (format) { + case PLAIN: + if (print_header) + fprintf(stdout, "cat%sleft%sright\n", options.fs, options.fs); + for (i = 0; i < vstat.rcat; i++) { + if (Values[i].count1 == 1) { + if (Values[i].i1 >= 0) + sprintf(left, "%d", Values[i].i1); + else + sprintf(left, "-1"); /* NULL, no area/cat */ + } + else if (Values[i].count1 > 1) { + sprintf(left, "-"); + } + else { /* Values[i].count1 == 0 */ + /* It can be OK if the category is assigned to an element + type which is not GV_BOUNDARY */ + /* -> TODO: print only if there is boundary with that cat */ + sprintf(left, "-"); + } - if (Values[i].count2 == 1) { - if (Values[i].i2 >= 0) - sprintf(right, "%d", Values[i].i2); - else - sprintf(right, "-1"); /* NULL, no area/cat */ - } - else if (Values[i].count2 > 1) { - sprintf(right, "-"); - } - else { /* Values[i].count1 == 0 */ - sprintf(right, "-"); + if (Values[i].count2 == 1) { + if (Values[i].i2 >= 0) + sprintf(right, "%d", Values[i].i2); + else + sprintf(right, "-1"); /* NULL, no area/cat */ + } + else if (Values[i].count2 > 1) { + sprintf(right, "-"); + } + else { /* Values[i].count1 == 0 */ + sprintf(right, "-"); + } + + fprintf(stdout, "%d%s%s%s%s\n", Values[i].cat, options.fs, left, + options.fs, right); } + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + + if (Values[i].count1 == 1) { + if (Values[i].i1 >= 0) + json_object_set_number(record, "left", Values[i].i1); + else + json_object_set_number(record, "left", + -1); /* NULL, no area/cat */ + } + else if (Values[i].count1 > 1) { + json_object_set_null(record, "left"); + } + else { /* Values[i].count1 == 0 */ + /* It can be OK if the category is assigned to an element + type which is not GV_BOUNDARY */ + /* -> TODO: print only if there is boundary with that cat */ + json_object_set_null(record, "left"); + } - fprintf(stdout, "%d%s%s%s%s\n", Values[i].cat, options.fs, left, - options.fs, right); + if (Values[i].count2 == 1) { + if (Values[i].i2 >= 0) + json_object_set_number(record, "right", Values[i].i2); + else + json_object_set_number(record, "right", + -1); /* NULL, no area/cat */ + } + else if (Values[i].count2 > 1) { + json_object_set_null(record, "right"); + } + else { /* Values[i].count1 == 0 */ + json_object_set_null(record, "right"); + } + } + break; } break; case O_QUERY: - if (print_header) - fprintf(stdout, "cat%squery\n", options.fs); - for (i = 0; i < vstat.rcat; i++) { - if (Values[i].null) { - fprintf(stdout, "%d|-\n", Values[i].cat); + switch (format) { + case PLAIN: + if (print_header) + fprintf(stdout, "cat%squery\n", options.fs); + for (i = 0; i < vstat.rcat; i++) { + if (Values[i].null) { + fprintf(stdout, "%d|-\n", Values[i].cat); + } + else { + switch (vstat.qtype) { + case (DB_C_TYPE_INT): + fprintf(stdout, "%d%s%d\n", Values[i].cat, options.fs, + Values[i].i1); + break; + case (DB_C_TYPE_DOUBLE): + fprintf(stdout, "%d%s%15g\n", Values[i].cat, options.fs, + Values[i].d1); + break; + case (DB_C_TYPE_STRING): + fprintf(stdout, "%d%s%s\n", Values[i].cat, options.fs, + Values[i].str1); + break; + } + } } - else { - switch (vstat.qtype) { - case (DB_C_TYPE_INT): - fprintf(stdout, "%d%s%d\n", Values[i].cat, options.fs, - Values[i].i1); - break; - case (DB_C_TYPE_DOUBLE): - fprintf(stdout, "%d%s%15g\n", Values[i].cat, options.fs, - Values[i].d1); - break; - case (DB_C_TYPE_STRING): - fprintf(stdout, "%d%s%s\n", Values[i].cat, options.fs, - Values[i].str1); - break; + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + if (Values[i].null) { + json_object_set_null(record, "query"); + } + else { + switch (vstat.qtype) { + case (DB_C_TYPE_INT): + json_object_set_number(record, "query", Values[i].i1); + break; + case (DB_C_TYPE_DOUBLE): + json_object_set_number(record, "query", Values[i].d1); + break; + case (DB_C_TYPE_STRING): + json_object_set_string(record, "query", Values[i].str1); + break; + } } } } break; case O_AZIMUTH: - if (print_header) - fprintf(stdout, "cat%sazimuth\n", options.fs); - for (i = 0; i < vstat.rcat; i++) - fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, - Values[i].d1); + switch (format) { + case PLAIN: + if (print_header) + fprintf(stdout, "cat%sazimuth\n", options.fs); + for (i = 0; i < vstat.rcat; i++) + fprintf(stdout, "%d%s%.15g\n", Values[i].cat, options.fs, + Values[i].d1); + break; + case JSON: + for (i = 0; i < vstat.rcat; i++) { + record_value = json_value_init_object(); + record = json_object(record_value); + json_object_set_number(record, "category", Values[i].cat); + json_object_set_number(record, "azimuth", Values[i].d1); + json_array_append_value(records_array, record_value); + } + break; + } break; } + if (format == JSON) { + json_object_set_value(root_object, "records", records_value); + char *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); + } + return 0; }