diff --git a/temporal/t.vect.db.select/t.vect.db.select.md b/temporal/t.vect.db.select/t.vect.db.select.md index 27779833ff8..07142afc83a 100644 --- a/temporal/t.vect.db.select/t.vect.db.select.md +++ b/temporal/t.vect.db.select/t.vect.db.select.md @@ -40,6 +40,36 @@ start_time|end_time|DATE|SOURCE 1849|1873|01/01/1873|Coastal Carolina University ``` +You can output the data in three formats: plain (the default), CSV, and JSON. + +```sh +t.vect.db.select input=schools_stds columns="cat,CORECAPACI" format=json +``` + +```json +[ + { + "start_time": "2024-01-01 00:00:00", + "end_time": "2025-01-01 00:00:00", + "cat": 1, + "CORECAPACI": 448 + }, + { + "start_time": "2024-01-01 00:00:00", + "end_time": "2025-01-01 00:00:00", + "cat": 2, + "CORECAPACI": 540 + }, + { + "start_time": "2024-01-01 00:00:00", + "end_time": "2025-01-01 00:00:00", + "cat": 3, + "CORECAPACI": 523 + }, + ... +] +``` + ## SEE ALSO *[t.create](t.create.md), [t.info](t.info.md)* diff --git a/temporal/t.vect.db.select/t.vect.db.select.py b/temporal/t.vect.db.select/t.vect.db.select.py index a8f2490749e..be4e8986f3c 100755 --- a/temporal/t.vect.db.select/t.vect.db.select.py +++ b/temporal/t.vect.db.select/t.vect.db.select.py @@ -36,8 +36,16 @@ # %option G_OPT_DB_COLUMNS # %end +# %option G_OPT_F_FORMAT +# % options: plain,csv,json +# % descriptions: plain;Plain text output;csv;CSV (Comma Separated Values);json;JSON (JavaScript Object Notation) +# % answer: plain +# % guisection: Formatting +# %end + # %option G_OPT_F_SEP # % label: Field separator character between the output columns +# % answer: # %end # %option G_OPT_V_FIELD @@ -50,14 +58,15 @@ # % key: t_where # %end +import json + import grass.script as gs +from grass.tools import Tools ############################################################################ def main(): - # lazy imports - import grass.temporal as tgis # Get the options input = options["input"] @@ -66,6 +75,7 @@ def main(): tempwhere = options["t_where"] layer = options["layer"] separator = gs.separator(options["separator"]) + output_format = options.get("format", "plain") if where in {"", " ", "\n"}: where = None @@ -73,6 +83,24 @@ def main(): if columns in {"", " ", "\n"}: columns = None + if output_format == "csv": + if not separator: + separator = "," + elif len(separator) > 1: + gs.fatal( + _("A standard CSV separator (delimiter) is only one character long") + ) + + elif output_format == "json": + if separator: + gs.fatal(_("Separator option is not allowed with JSON format")) + + elif not separator: # output_format == "plain" + separator = "|" + + # lazy imports + import grass.temporal as tgis + # Make sure the temporal database exists tgis.init() @@ -82,7 +110,10 @@ def main(): "name,layer,mapset,start_time,end_time", tempwhere, "start_time", None ) + tools = Tools() + json_output = [] col_names = "" + if rows: for row in rows: vector_name = "%s@%s" % (row["name"], row["mapset"]) @@ -91,52 +122,62 @@ def main(): if row["layer"]: layer = row["layer"] - select = gs.read_command( - "v.db.select", + res = tools.v_db_select( map=vector_name, layer=layer, columns=columns, - separator="%s" % (separator), + separator=separator, where=where, + format=output_format, ) - if not select: - gs.fatal( - _("Unable to run v.db.select for vector map <%s> with layer %s") - % (vector_name, layer) - ) - # The first line are the column names - list = select.split("\n") - count = 0 - for entry in list: - if entry.strip() != "": - # print the column names in case they change - if count == 0: - col_names_new = "start_time%send_time%s%s" % ( - separator, - separator, - entry, - ) - if col_names != col_names_new: - col_names = col_names_new - print(col_names) - elif row["end_time"]: - print( - "%s%s%s%s%s" - % ( - row["start_time"], + if output_format == "json": + records = res["records"] + for record in records: + new_record = { + "start_time": row["start_time"], + "end_time": row["end_time"], + } + new_record.update(record) + json_output.append(new_record) + + else: + select = res.text + # The first line are the column names + list = select.split("\n") + count = 0 + for entry in list: + if entry.strip() != "": + # print the column names in case they change + if count == 0: + col_names_new = "start_time%send_time%s%s" % ( separator, - row["end_time"], separator, entry, ) - ) - else: - print( - "%s%s%s%s" - % (row["start_time"], separator, separator, entry) - ) - count += 1 + if col_names != col_names_new: + col_names = col_names_new + print(col_names) + elif row["end_time"]: + print( + "%s%s%s%s%s" + % ( + row["start_time"], + separator, + row["end_time"], + separator, + entry, + ) + ) + else: + print( + "%s%s%s%s" + % (row["start_time"], separator, separator, entry) + ) + count += 1 + + if output_format == "json": + print(json.dumps(json_output, indent=4, default=str)) if __name__ == "__main__": diff --git a/temporal/t.vect.db.select/tests/t_vect_db_select_test.py b/temporal/t.vect.db.select/tests/t_vect_db_select_test.py index f0e09c0dd47..d401814855b 100644 --- a/temporal/t.vect.db.select/tests/t_vect_db_select_test.py +++ b/temporal/t.vect.db.select/tests/t_vect_db_select_test.py @@ -121,3 +121,67 @@ def test_t_vect_db_select_instance_where(space_time_vector_dataset): for actual, expected in zip(actual_lines, expected_lines, strict=True): assert actual == expected + + +def test_t_vect_db_select_format_csv(space_time_vector_dataset): + """Verify CSV output format.""" + tools = Tools(session=space_time_vector_dataset.session) + + result = tools.t_vect_db_select( + input=space_time_vector_dataset.interval_name, + format="csv", + where="cat = 1", + ) + assert result.returncode == 0 + + expected_output = """start_time,end_time,cat,observation +2001-03-01 00:00:00,2001-04-01 00:00:00,1,100 +2001-04-01 00:00:00,2001-05-01 00:00:00,1,200 +2001-05-01 00:00:00,2001-06-01 00:00:00,1,300""" + + actual_lines = result.text_split("\n") + expected_lines = expected_output.strip().splitlines() + + for actual, expected in zip(actual_lines, expected_lines, strict=True): + assert actual == expected + + +def test_t_vect_db_select_format_json(space_time_vector_dataset): + """Verify standard JSON output format.""" + tools = Tools(session=space_time_vector_dataset.session) + + result = tools.t_vect_db_select( + input=space_time_vector_dataset.interval_name, + format="json", + where="cat = 1", + ) + assert result.returncode == 0 + + expected_records = [ + { + "start_time": "2001-03-01 00:00:00", + "end_time": "2001-04-01 00:00:00", + "cat": 1, + "observation": 100, + }, + { + "start_time": "2001-04-01 00:00:00", + "end_time": "2001-05-01 00:00:00", + "cat": 1, + "observation": 200, + }, + { + "start_time": "2001-05-01 00:00:00", + "end_time": "2001-06-01 00:00:00", + "cat": 1, + "observation": 300, + }, + ] + + assert len(result) == len(expected_records) + + for actual, expected in zip(result, expected_records, strict=True): + assert actual["start_time"] == expected["start_time"] + assert actual["end_time"] == expected["end_time"] + assert actual["cat"] == expected["cat"] + assert actual["observation"] == expected["observation"]