From f3204e7783dcaf42fb42c756026a22f49bf233e1 Mon Sep 17 00:00:00 2001 From: Kriti Birda <164247895+kritibirda26@users.noreply.github.com> Date: Wed, 19 Jun 2024 05:20:18 +0530 Subject: [PATCH] v.info: add JSON support (#3755) Use parson to have JSON as an output format support. The module has various flags to control the fields being output in case of plain shell format controlled by flags. All (-get) of these are enabled when using format=shell. format=plain now allows use of flags. This may need special handling in the future to resolve the edge cases in a better way. --- lib/gis/testsuite/test_parser_json.py | 4 +- vector/v.info/Makefile | 2 +- vector/v.info/local_proto.h | 13 +- vector/v.info/main.c | 36 ++- vector/v.info/parse.c | 34 ++- vector/v.info/print.c | 382 ++++++++++++++++++++------ vector/v.info/testsuite/test_vinfo.py | 63 +++++ vector/v.info/v.info.html | 58 ++++ 8 files changed, 491 insertions(+), 101 deletions(-) diff --git a/lib/gis/testsuite/test_parser_json.py b/lib/gis/testsuite/test_parser_json.py index b325de2314c..918fb453d04 100644 --- a/lib/gis/testsuite/test_parser_json.py +++ b/lib/gis/testsuite/test_parser_json.py @@ -97,13 +97,15 @@ def test_v_info(self): inputs = [ {"param": "map", "value": "hospitals@PERMANENT"}, {"param": "layer", "value": "1"}, + {"param": "format", "value": "plain"}, ] stdout, stderr = subprocess.Popen(args, stdout=subprocess.PIPE).communicate() print(stdout) json_code = json.loads(decode(stdout)) + print(json_code) self.assertEqual(json_code["module"], "v.info") - self.assertEqual(len(json_code["inputs"]), 2) + self.assertEqual(len(json_code["inputs"]), 3) self.assertEqual(json_code["inputs"], inputs) diff --git a/vector/v.info/Makefile b/vector/v.info/Makefile index e7bb7e6eff6..6791e2513c7 100644 --- a/vector/v.info/Makefile +++ b/vector/v.info/Makefile @@ -3,7 +3,7 @@ MODULE_TOPDIR = ../.. PGM = v.info -LIBES = $(VECTORLIB) $(DIG2LIB) $(DBMILIB) $(GISLIB) +LIBES = $(VECTORLIB) $(DIG2LIB) $(DBMILIB) $(GISLIB) $(PARSONLIB) DEPENDENCIES = $(VECTORDEP) $(DIG2DEP) $(DBMIDEP) $(GISDEP) EXTRA_INC = $(VECT_INC) EXTRA_CFLAGS = $(VECT_CFLAGS) diff --git a/vector/v.info/local_proto.h b/vector/v.info/local_proto.h index 8d16b41bbe1..1ed32468562 100644 --- a/vector/v.info/local_proto.h +++ b/vector/v.info/local_proto.h @@ -1,20 +1,25 @@ #include +#include #define SHELL_NO 0x00 #define SHELL_BASIC 0x02 #define SHELL_REGION 0x04 #define SHELL_TOPO 0x08 +enum OutputFormat { PLAIN, SHELL, JSON }; + /* level1.c */ int level_one_info(struct Map_info *); /* parse.c */ -void parse_args(int, char **, char **, char **, int *, int *, int *); +void parse_args(int, char **, char **, char **, int *, int *, int *, + enum OutputFormat *); /* print.c */ void format_double(double, char *); -void print_region(struct Map_info *); -void print_topo(struct Map_info *); +void print_region(struct Map_info *, enum OutputFormat, JSON_Object *); +void print_topo(struct Map_info *, enum OutputFormat, JSON_Object *); void print_columns(struct Map_info *, const char *, const char *); void print_info(struct Map_info *); -void print_shell(struct Map_info *, const char *); +void print_shell(struct Map_info *, const char *, enum OutputFormat, + JSON_Object *); diff --git a/vector/v.info/main.c b/vector/v.info/main.c index 4506b2b7bf6..6f4aa9a8fcf 100644 --- a/vector/v.info/main.c +++ b/vector/v.info/main.c @@ -29,6 +29,11 @@ int main(int argc, char *argv[]) char *input_opt, *field_opt; int hist_flag, col_flag, shell_flag; + enum OutputFormat format; + + JSON_Value *root_value; + JSON_Object *root_object; + struct Map_info Map; G_gisinit(argv[0]); @@ -47,7 +52,12 @@ int main(int argc, char *argv[]) G_debug(1, "LFS is %s", sizeof(off_t) == 8 ? "available" : "not available"); parse_args(argc, argv, &input_opt, &field_opt, &hist_flag, &col_flag, - &shell_flag); + &shell_flag, &format); + + if (format == JSON) { + root_value = json_value_init_object(); + root_object = json_value_get_object(root_value); + } /* try to open head-only on level 2 */ if (Vect_open_old_head2(&Map, input_opt, "", field_opt) < 2) { @@ -82,19 +92,29 @@ int main(int argc, char *argv[]) return (EXIT_SUCCESS); } - if (shell_flag & SHELL_BASIC) { - print_shell(&Map, field_opt); + if ((shell_flag & SHELL_BASIC) || format == JSON) { + print_shell(&Map, field_opt, format, root_object); } - if (shell_flag & SHELL_REGION) { - print_region(&Map); + if ((shell_flag & SHELL_REGION) || format == JSON) { + print_region(&Map, format, root_object); } - if (shell_flag & SHELL_TOPO) { - print_topo(&Map); + if ((shell_flag & SHELL_TOPO) || format == JSON) { + print_topo(&Map, format, root_object); } - if (shell_flag == 0) { + if (shell_flag == 0 && format == PLAIN) { print_info(&Map); } + if (format == JSON) { + 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); + } + Vect_close(&Map); return (EXIT_SUCCESS); diff --git a/vector/v.info/parse.c b/vector/v.info/parse.c index 8d439535443..b63b7a64055 100644 --- a/vector/v.info/parse.c +++ b/vector/v.info/parse.c @@ -6,9 +6,9 @@ #include "local_proto.h" void parse_args(int argc, char **argv, char **input, char **field, int *history, - int *columns, int *shell) + int *columns, int *shell, enum OutputFormat *format_ptr) { - struct Option *input_opt, *field_opt; + struct Option *input_opt, *field_opt, *format_opt; struct Flag *hist_flag, *col_flag, *shell_flag, *region_flag, *topo_flag; input_opt = G_define_standard_option(G_OPT_V_MAP); @@ -42,6 +42,13 @@ void parse_args(int argc, char **argv, char **input, char **field, int *history, topo_flag->description = _("Print topology info in shell script style"); topo_flag->guisection = _("Print"); + format_opt = G_define_standard_option(G_OPT_F_FORMAT); + format_opt->options = "plain,shell,json"; + format_opt->descriptions = _("plain;Human readable text output;" + "shell;shell script style text output;" + "json;JSON (JavaScript Object Notation);"); + format_opt->guisection = _("Print"); + if (G_parser(argc, argv)) exit(EXIT_FAILURE); @@ -56,4 +63,27 @@ void parse_args(int argc, char **argv, char **input, char **field, int *history, *shell |= SHELL_REGION; if (topo_flag->answer) *shell |= SHELL_TOPO; + + if (strcmp(format_opt->answer, "plain") == 0) { + // if shell flags are specified and format=PLAIN (default), + // print in shell script format + if (*shell != 0) { + *format_ptr = SHELL; + } + else { + *format_ptr = PLAIN; + } + } + else if (strcmp(format_opt->answer, "json") == 0) + *format_ptr = JSON; + else { + *format_ptr = SHELL; + // if shell flags are specified with format=shell, obey them + // if only format=shell is specified, print all info + if (*shell == 0) { + *shell |= SHELL_BASIC; + *shell |= SHELL_REGION; + *shell |= SHELL_TOPO; + } + } } diff --git a/vector/v.info/print.c b/vector/v.info/print.c index 0830006a777..6c36d6391f6 100644 --- a/vector/v.info/print.c +++ b/vector/v.info/print.c @@ -5,6 +5,8 @@ #include #include +#include + #include "local_proto.h" #define printline(x) G_faprintf(stdout, " | %-74.74s |\n", x) @@ -38,7 +40,8 @@ static char *format_zone(int zone_num) return zone_str; } -void print_region(struct Map_info *Map) +void print_region(struct Map_info *Map, enum OutputFormat format, + JSON_Object *root_object) { char tmp1[1024], tmp2[1024]; @@ -46,20 +49,36 @@ void print_region(struct Map_info *Map) /* print the spatial extent as double values */ Vect_get_map_box(Map, &box); - G_format_northing(box.N, tmp1, -1); - G_format_northing(box.S, tmp2, -1); - fprintf(stdout, "north=%s\n", tmp1); - fprintf(stdout, "south=%s\n", tmp2); - - G_format_easting(box.E, tmp1, -1); - G_format_easting(box.W, tmp2, -1); - fprintf(stdout, "east=%s\n", tmp1); - fprintf(stdout, "west=%s\n", tmp2); - fprintf(stdout, "top=%f\n", box.T); - fprintf(stdout, "bottom=%f\n", box.B); + + switch (format) { + case PLAIN: + break; + case SHELL: + G_format_northing(box.N, tmp1, -1); + G_format_northing(box.S, tmp2, -1); + fprintf(stdout, "north=%s\n", tmp1); + fprintf(stdout, "south=%s\n", tmp2); + + G_format_easting(box.E, tmp1, -1); + G_format_easting(box.W, tmp2, -1); + fprintf(stdout, "east=%s\n", tmp1); + fprintf(stdout, "west=%s\n", tmp2); + fprintf(stdout, "top=%f\n", box.T); + fprintf(stdout, "bottom=%f\n", box.B); + break; + case JSON: + json_object_set_number(root_object, "north", box.N); + json_object_set_number(root_object, "south", box.S); + json_object_set_number(root_object, "east", box.E); + json_object_set_number(root_object, "west", box.W); + json_object_set_number(root_object, "top", box.T); + json_object_set_number(root_object, "bottom", box.B); + break; + } } -void print_topo(struct Map_info *Map) +void print_topo(struct Map_info *Map, enum OutputFormat format, + JSON_Object *root_object) { int with_z; long nprimitives; @@ -77,50 +96,83 @@ void print_topo(struct Map_info *Map) nprimitives += Vect_get_num_primitives(Map, GV_KERNEL); } - fprintf(stdout, "nodes=%d\n", Vect_get_num_nodes(Map)); - fflush(stdout); - - fprintf(stdout, "points=%d\n", Vect_get_num_primitives(Map, GV_POINT)); - fflush(stdout); - - fprintf(stdout, "lines=%d\n", Vect_get_num_primitives(Map, GV_LINE)); - fflush(stdout); - - fprintf(stdout, "boundaries=%d\n", - Vect_get_num_primitives(Map, GV_BOUNDARY)); - fflush(stdout); + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "nodes=%d\n", Vect_get_num_nodes(Map)); + fflush(stdout); - fprintf(stdout, "centroids=%d\n", - Vect_get_num_primitives(Map, GV_CENTROID)); - fflush(stdout); + fprintf(stdout, "points=%d\n", Vect_get_num_primitives(Map, GV_POINT)); + fflush(stdout); - fprintf(stdout, "areas=%d\n", Vect_get_num_areas(Map)); - fflush(stdout); + fprintf(stdout, "lines=%d\n", Vect_get_num_primitives(Map, GV_LINE)); + fflush(stdout); - fprintf(stdout, "islands=%d\n", Vect_get_num_islands(Map)); - fflush(stdout); + fprintf(stdout, "boundaries=%d\n", + Vect_get_num_primitives(Map, GV_BOUNDARY)); + fflush(stdout); - if (with_z) { - fprintf(stdout, "faces=%d\n", Vect_get_num_primitives(Map, GV_FACE)); + fprintf(stdout, "centroids=%d\n", + Vect_get_num_primitives(Map, GV_CENTROID)); fflush(stdout); - fprintf(stdout, "kernels=%d\n", - Vect_get_num_primitives(Map, GV_KERNEL)); + fprintf(stdout, "areas=%d\n", Vect_get_num_areas(Map)); fflush(stdout); - fprintf(stdout, "volumes=%d\n", - Vect_get_num_primitives(Map, GV_VOLUME)); + fprintf(stdout, "islands=%d\n", Vect_get_num_islands(Map)); fflush(stdout); - fprintf(stdout, "holes=%d\n", Vect_get_num_holes(Map)); + if (with_z) { + fprintf(stdout, "faces=%d\n", + Vect_get_num_primitives(Map, GV_FACE)); + fflush(stdout); + + fprintf(stdout, "kernels=%d\n", + Vect_get_num_primitives(Map, GV_KERNEL)); + fflush(stdout); + + fprintf(stdout, "volumes=%d\n", + Vect_get_num_primitives(Map, GV_VOLUME)); + fflush(stdout); + + fprintf(stdout, "holes=%d\n", Vect_get_num_holes(Map)); + fflush(stdout); + } + + fprintf(stdout, "primitives=%ld\n", nprimitives); fflush(stdout); - } - fprintf(stdout, "primitives=%ld\n", nprimitives); - fflush(stdout); + fprintf(stdout, "map3d=%d\n", Vect_is_3d(Map) ? 1 : 0); + fflush(stdout); - fprintf(stdout, "map3d=%d\n", Vect_is_3d(Map) ? 1 : 0); - fflush(stdout); + break; + case JSON: + json_object_set_number(root_object, "nodes", Vect_get_num_nodes(Map)); + json_object_set_number(root_object, "points", + Vect_get_num_primitives(Map, GV_POINT)); + json_object_set_number(root_object, "lines", + Vect_get_num_primitives(Map, GV_LINE)); + json_object_set_number(root_object, "boundaries", + Vect_get_num_primitives(Map, GV_BOUNDARY)); + json_object_set_number(root_object, "centroids", + Vect_get_num_primitives(Map, GV_CENTROID)); + json_object_set_number(root_object, "areas", Vect_get_num_areas(Map)); + json_object_set_number(root_object, "islands", + Vect_get_num_islands(Map)); + if (with_z) { + json_object_set_number(root_object, "faces", + Vect_get_num_primitives(Map, GV_FACE)); + json_object_set_number(root_object, "kernels", + Vect_get_num_primitives(Map, GV_KERNEL)); + json_object_set_number(root_object, "volumes", + Vect_get_num_primitives(Map, GV_VOLUME)); + json_object_set_number(root_object, "holes", + Vect_get_num_holes(Map)); + } + json_object_set_number(root_object, "primitives", nprimitives); + json_object_set_boolean(root_object, "map3d", Vect_is_3d(Map)); + } } void print_columns(struct Map_info *Map, const char *input_opt, @@ -174,7 +226,8 @@ void print_columns(struct Map_info *Map, const char *input_opt, db_shutdown_driver(driver); } -void print_shell(struct Map_info *Map, const char *field_opt) +void print_shell(struct Map_info *Map, const char *field_opt, + enum OutputFormat format, JSON_Object *root_object) { int map_type; int time_ok, first_time_ok, second_time_ok; @@ -197,31 +250,91 @@ void print_shell(struct Map_info *Map, const char *field_opt) map_type = Vect_maptype(Map); - fprintf(stdout, "name=%s\n", Vect_get_name(Map)); - fprintf(stdout, "mapset=%s\n", Vect_get_mapset(Map)); - fprintf(stdout, "location=%s\n", G_location()); - fprintf(stdout, "project=%s\n", G_location()); - fprintf(stdout, "database=%s\n", G_gisdbase()); - fprintf(stdout, "title=%s\n", Vect_get_map_name(Map)); - fprintf(stdout, "scale=1:%d\n", Vect_get_scale(Map)); - fprintf(stdout, "creator=%s\n", Vect_get_person(Map)); - fprintf(stdout, "organization=%s\n", Vect_get_organization(Map)); - fprintf(stdout, "source_date=%s\n", Vect_get_map_date(Map)); + char scale_tmp[18]; + snprintf(scale_tmp, 18, "1:%d", Vect_get_scale(Map)); + + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "name=%s\n", Vect_get_name(Map)); + fprintf(stdout, "mapset=%s\n", Vect_get_mapset(Map)); + fprintf(stdout, "location=%s\n", G_location()); + fprintf(stdout, "project=%s\n", G_location()); + fprintf(stdout, "database=%s\n", G_gisdbase()); + fprintf(stdout, "title=%s\n", Vect_get_map_name(Map)); + fprintf(stdout, "scale=%s\n", scale_tmp); + fprintf(stdout, "creator=%s\n", Vect_get_person(Map)); + fprintf(stdout, "organization=%s\n", Vect_get_organization(Map)); + fprintf(stdout, "source_date=%s\n", Vect_get_map_date(Map)); + break; + case JSON: + json_object_set_string(root_object, "name", Vect_get_name(Map)); + json_object_set_string(root_object, "mapset", Vect_get_mapset(Map)); + json_object_set_string(root_object, "project", G_location()); + json_object_set_string(root_object, "database", G_gisdbase()); + json_object_set_string(root_object, "title", Vect_get_map_name(Map)); + json_object_set_string(root_object, "scale", scale_tmp); + json_object_set_string(root_object, "creator", Vect_get_person(Map)); + json_object_set_string(root_object, "organization", + Vect_get_organization(Map)); + json_object_set_string(root_object, "source_date", + Vect_get_map_date(Map)); + break; + } + /* This shows the TimeStamp (if present) */ if (time_ok == TRUE && (first_time_ok || second_time_ok)) { G_format_timestamp(&ts, timebuff); - fprintf(stdout, "timestamp=%s\n", timebuff); + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "timestamp=%s\n", timebuff); + break; + case JSON: + json_object_set_string(root_object, "timestamp", timebuff); + break; + } } else { - fprintf(stdout, "timestamp=none\n"); + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "timestamp=none\n"); + break; + case JSON: + json_object_set_null(root_object, "timestamp"); + break; + } } if (map_type == GV_FORMAT_OGR || map_type == GV_FORMAT_OGR_DIRECT) { - fprintf(stdout, "format=%s,%s\n", Vect_maptype_info(Map), - Vect_get_finfo_format_info(Map)); - fprintf(stdout, "ogr_layer=%s\n", Vect_get_finfo_layer_name(Map)); - fprintf(stdout, "ogr_dsn=%s\n", Vect_get_finfo_dsn_name(Map)); - fprintf(stdout, "feature_type=%s\n", Vect_get_finfo_geometry_type(Map)); + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "format=%s,%s\n", Vect_maptype_info(Map), + Vect_get_finfo_format_info(Map)); + fprintf(stdout, "ogr_layer=%s\n", Vect_get_finfo_layer_name(Map)); + fprintf(stdout, "ogr_dsn=%s\n", Vect_get_finfo_dsn_name(Map)); + fprintf(stdout, "feature_type=%s\n", + Vect_get_finfo_geometry_type(Map)); + break; + case JSON: + json_object_set_string(root_object, "format", + Vect_maptype_info(Map)); + json_object_set_string(root_object, "format-detail", + Vect_get_finfo_format_info(Map)); + json_object_set_string(root_object, "ogr_layer", + Vect_get_finfo_layer_name(Map)); + json_object_set_string(root_object, "ogr_dsn", + Vect_get_finfo_dsn_name(Map)); + json_object_set_string(root_object, "feature_type", + Vect_get_finfo_geometry_type(Map)); + break; + } } else if (map_type == GV_FORMAT_POSTGIS) { int topo_format; @@ -230,47 +343,146 @@ void print_shell(struct Map_info *Map, const char *field_opt) finfo = Vect_get_finfo(Map); - fprintf(stdout, "format=%s,%s\n", Vect_maptype_info(Map), - Vect_get_finfo_format_info(Map)); - fprintf(stdout, "pg_table=%s\n", Vect_get_finfo_layer_name(Map)); - fprintf(stdout, "pg_dbname=%s\n", Vect_get_finfo_dsn_name(Map)); - fprintf(stdout, "geometry_column=%s\n", finfo->pg.geom_column); - fprintf(stdout, "feature_type=%s\n", Vect_get_finfo_geometry_type(Map)); + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "format=%s,%s\n", Vect_maptype_info(Map), + Vect_get_finfo_format_info(Map)); + fprintf(stdout, "pg_table=%s\n", Vect_get_finfo_layer_name(Map)); + fprintf(stdout, "pg_dbname=%s\n", Vect_get_finfo_dsn_name(Map)); + fprintf(stdout, "geometry_column=%s\n", finfo->pg.geom_column); + fprintf(stdout, "feature_type=%s\n", + Vect_get_finfo_geometry_type(Map)); + break; + case JSON: + json_object_set_string(root_object, "format", + Vect_maptype_info(Map)); + json_object_set_string(root_object, "format-detail", + Vect_get_finfo_format_info(Map)); + json_object_set_string(root_object, "pg_table", + Vect_get_finfo_layer_name(Map)); + json_object_set_string(root_object, "pg_dbname", + Vect_get_finfo_dsn_name(Map)); + json_object_set_string(root_object, "geometry_column", + finfo->pg.geom_column); + json_object_set_string(root_object, "feature_type", + Vect_get_finfo_geometry_type(Map)); + break; + } + topo_format = Vect_get_finfo_topology_info(Map, &toposchema_name, &topogeom_column, NULL); if (topo_format == GV_TOPO_POSTGIS) { - fprintf(stdout, "pg_topo_schema=%s\n", toposchema_name); - fprintf(stdout, "pg_topo_column=%s\n", topogeom_column); + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "pg_topo_schema=%s\n", toposchema_name); + fprintf(stdout, "pg_topo_column=%s\n", topogeom_column); + break; + case JSON: + json_object_set_string(root_object, "pg_topo_schema", + toposchema_name); + json_object_set_string(root_object, "pg_topo_column", + topogeom_column); + break; + } } } else { - fprintf(stdout, "format=%s\n", Vect_maptype_info(Map)); + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "format=%s\n", Vect_maptype_info(Map)); + break; + case JSON: + json_object_set_string(root_object, "format", + Vect_maptype_info(Map)); + break; + } } - fprintf(stdout, "level=%d\n", Vect_level(Map)); - + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "level=%d\n", Vect_level(Map)); + break; + case JSON: + json_object_set_number(root_object, "level", Vect_level(Map)); + break; + } if (Vect_level(Map) > 0) { - fprintf(stdout, "num_dblinks=%d\n", Vect_get_num_dblinks(Map)); + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "num_dblinks=%d\n", Vect_get_num_dblinks(Map)); + break; + case JSON: + json_object_set_number(root_object, "num_dblinks", + Vect_get_num_dblinks(Map)); + break; + } if (Vect_get_num_dblinks(Map) > 0) { fi = Vect_get_field2(Map, field_opt); if (fi != NULL) { - fprintf(stdout, "attribute_layer_number=%i\n", fi->number); - fprintf(stdout, "attribute_layer_name=%s\n", fi->name); - fprintf(stdout, "attribute_database=%s\n", fi->database); - fprintf(stdout, "attribute_database_driver=%s\n", fi->driver); - fprintf(stdout, "attribute_table=%s\n", fi->table); - fprintf(stdout, "attribute_primary_key=%s\n", fi->key); + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "attribute_layer_number=%i\n", fi->number); + fprintf(stdout, "attribute_layer_name=%s\n", fi->name); + fprintf(stdout, "attribute_database=%s\n", fi->database); + fprintf(stdout, "attribute_database_driver=%s\n", + fi->driver); + fprintf(stdout, "attribute_table=%s\n", fi->table); + fprintf(stdout, "attribute_primary_key=%s\n", fi->key); + break; + case JSON: + json_object_set_number( + root_object, "attribute_layer_number", fi->number); + json_object_set_string(root_object, "attribute_layer_name", + fi->name); + json_object_set_string(root_object, "attribute_database", + fi->database); + json_object_set_string( + root_object, "attribute_database_driver", fi->driver); + json_object_set_string(root_object, "attribute_table", + fi->table); + json_object_set_string(root_object, "attribute_primary_key", + fi->key); + break; + } } } } - fprintf(stdout, "projection=%s\n", Vect_get_proj_name(Map)); - if (G_projection() == PROJECTION_UTM) { - fprintf(stdout, "zone=%d\n", Vect_get_zone(Map)); + switch (format) { + case PLAIN: + break; + case SHELL: + fprintf(stdout, "projection=%s\n", Vect_get_proj_name(Map)); + if (G_projection() == PROJECTION_UTM) { + fprintf(stdout, "zone=%d\n", Vect_get_zone(Map)); + } + fprintf(stdout, "digitization_threshold=%f\n", Vect_get_thresh(Map)); + fprintf(stdout, "comment=%s\n", Vect_get_comment(Map)); + break; + case JSON: + json_object_set_string(root_object, "projection", + Vect_get_proj_name(Map)); + if (G_projection() == PROJECTION_UTM) { + json_object_set_number(root_object, "zone", Vect_get_zone(Map)); + } + json_object_set_number(root_object, "digitization_threshold", + Vect_get_thresh(Map)); + json_object_set_string(root_object, "comment", Vect_get_comment(Map)); + break; } - fprintf(stdout, "digitization_threshold=%f\n", Vect_get_thresh(Map)); - fprintf(stdout, "comment=%s\n", Vect_get_comment(Map)); } void print_info(struct Map_info *Map) diff --git a/vector/v.info/testsuite/test_vinfo.py b/vector/v.info/testsuite/test_vinfo.py index b163df02327..b5b3aa17b82 100644 --- a/vector/v.info/testsuite/test_vinfo.py +++ b/vector/v.info/testsuite/test_vinfo.py @@ -1,6 +1,10 @@ +import json + from grass.gunittest.case import TestCase from grass.gunittest.main import test +from grass.gunittest.gmodules import SimpleModule + class TestVInfo(TestCase): """Test the shell output of v.info that is not location/mapset or user dependent""" @@ -183,6 +187,65 @@ def test_info_with_db_3d(self): ), ) + def test_json(self): + module = SimpleModule("v.info", map=self.test_vinfo_with_db_3d, format="json") + self.runModule(module) + + expected = { + "name": "test_vinfo_with_db_3d", + "title": "", + "scale": "1:1", + "organization": "", + "timestamp": "15 Jan 1994", + "format": "native", + "level": 2, + "num_dblinks": 1, + "attribute_layer_number": 1, + "attribute_layer_name": "test_vinfo_with_db_3d", + "attribute_database_driver": "sqlite", + "attribute_table": "test_vinfo_with_db_3d", + "attribute_primary_key": "cat", + "projection": "Lambert Conformal Conic", + "digitization_threshold": 0, + "comment": "", + "nodes": 0, + "points": 5, + "lines": 0, + "boundaries": 0, + "centroids": 0, + "areas": 0, + "islands": 0, + "faces": 0, + "kernels": 0, + "volumes": 0, + "holes": 0, + "primitives": 5, + "map3d": 1, + } + result = json.loads(module.outputs.stdout) + + # the following fields vary with the Grass sample data's path + # therefore only check for their presence in the JSON output + # and not exact values + remove_fields = [ + "project", + "database", + "source_date", + "attribute_database", + "top", + "bottom", + "east", + "west", + "north", + "south", + "creator", + "mapset", + ] + for field in remove_fields: + self.assertIn(field, result) + result.pop(field) + self.assertDictEqual(expected, result) + def test_database_table(self): """Test the database table column and type of the two vector maps with attribute data""" self.assertModuleKeyValue( diff --git a/vector/v.info/v.info.html b/vector/v.info/v.info.html index 1facf172dc9..05e7903ac2d 100644 --- a/vector/v.info/v.info.html +++ b/vector/v.info/v.info.html @@ -136,6 +136,49 @@

Basic metadata information in shell script style

bottom=0.000000 +

Output in JSON format

+
+{
+    "name": "geology",
+    "mapset": "PERMANENT",
+    "project": "nc_spm_08_grass7",
+    "database": "\/grassdata",
+    "title": "North Carolina geology map (polygon map)",
+    "scale": 1,
+    "creator": "helena",
+    "organization": "NC OneMap",
+    "source_date": "Mon Nov  6 15:48:53 2006",
+    "timestamp": null,
+    "format": "native",
+    "level": 2,
+    "num_dblinks": 1,
+    "attribute_layer_number": 1,
+    "attribute_layer_name": "geology",
+    "attribute_database": "\/grassdata\/nc_spm_08_grass7\/PERMANENT\/sqlite\/sqlite.db",
+    "attribute_database_driver": "sqlite",
+    "attribute_table": "geology",
+    "attribute_primary_key": "cat",
+    "projection": "Lambert Conformal Conic",
+    "digitization_threshold": 0,
+    "comment": "",
+    "north": 318117.43741634465,
+    "south": 10875.827232091688,
+    "east": 930172.31282271142,
+    "west": 123971.19498978264,
+    "top": 0,
+    "bottom": 0,
+    "nodes": 2724,
+    "points": 0,
+    "lines": 0,
+    "boundaries": 3649,
+    "centroids": 1832,
+    "areas": 1832,
+    "islands": 907,
+    "primitives": 5481,
+    "map3d": false
+}
+
+

PYTHON

See Python @@ -151,6 +194,21 @@

PYTHON

gcore.vector_info_topo('geology') # for `v.info shell=topo` +Here is an example of how the JSON output format can be used to integrate Grass with other python libraries easily. +
+import grass.script as gs
+import pandas as pd
+
+# Run v.info command
+busstops = gs.run_command("v.info", map="busstopsall", format="json")
+
+# Load data into dataframe
+df = pd.DataFrame([busstops])
+
+# Display the DataFrame
+print(df)
+
+

SEE ALSO