diff --git a/src/vector/v.stream.network/v.stream.network.html b/src/vector/v.stream.network/v.stream.network.html index b76b71da2f..86d4018063 100644 --- a/src/vector/v.stream.network/v.stream.network.html +++ b/src/vector/v.stream.network/v.stream.network.html @@ -7,6 +7,10 @@

DESCRIPTION

  • tostream: category number of the next stream; is equal to 0 if the stream flows off of the map
  • +Any stream-like network will work, but the lines need to be connected. +In terms of graph theory, a tree and forest are supported. Behavior for +a cyclic graph is undefined. +

    NOTES

    streams is a set of vector lines that is generated by r.stream.extract. It is recommended to be built as follows (Python code): @@ -33,7 +37,17 @@

    NOTES

    REFERENCES

    -None (yet) +

    SEE ALSO

    diff --git a/src/vector/v.stream.network/v.stream.network.py b/src/vector/v.stream.network/v.stream.network.py index 4d4c93924d..0a11299ba0 100644 --- a/src/vector/v.stream.network/v.stream.network.py +++ b/src/vector/v.stream.network/v.stream.network.py @@ -4,12 +4,13 @@ # MODULE: v.stream.network # # AUTHOR(S): Andrew Wickert +# Vaclav Petras (v8 fixes and interface improvements) # # PURPOSE: Attach IDs of upstream and downstream nodes as well as the # category value of the next downstream stream segment # (0 if the stream exits the map) # -# COPYRIGHT: (c) 2016-2017 Andrew Wickert +# COPYRIGHT: (c) 2016-2023 Andrew Wickert and the GRASS Development Team # # This program is free software under the GNU General Public # License (>=v2). Read the file COPYING that comes with GRASS @@ -20,9 +21,6 @@ # REQUIREMENTS: # - uses inputs from r.stream.extract -# More information -# Started 14 October 2016 - # %module # % description: Build a linked stream network: each link knows its downstream link # % keyword: vector @@ -73,150 +71,82 @@ # %option # % key: tostream_cat_column # % type: string -# % description: Adjacent downstream segment cat (0 = offmap flow) +# % label: Adjacent downstream segment category +# % description: Zero (0) indicates off-map flow # % answer: tostream # % required : no # %end -################## -# IMPORT MODULES # -################## -# PYTHON +"""Add stream network links to attribute table""" + import numpy as np -# GRASS -from grass.pygrass.modules.shortcuts import general as g -from grass.pygrass.modules.shortcuts import raster as r +from grass import script as gs + from grass.pygrass.modules.shortcuts import vector as v -from grass.pygrass.gis import region -from grass.pygrass import vector # Change to "v"? +from grass.pygrass.vector import VectorTopo from grass.script import vector_db_select -from grass.pygrass.vector import Vector, VectorTopo -from grass.pygrass.raster import RasterRow -from grass.pygrass import utils -from grass import script as gscript - -############### -# MAIN MODULE # -############### def main(): - """ + """Links each segment to its downstream segment + Links each river segment to the next downstream segment in a tributary network by referencing its category (cat) number in a new column. "0" means that the river exits the map. """ - - options, flags = gscript.parser() + options, unused_flags = gs.parser() streams = options["map"] x1 = options["upstream_easting_column"] y1 = options["upstream_northing_column"] x2 = options["downstream_easting_column"] y2 = options["downstream_northing_column"] + to_stream = options["tostream_cat_column"] - streamsTopo = VectorTopo(streams) - # streamsTopo.build() - - # 1. Get vectorTopo - streamsTopo.open(mode="rw") - """ - points_in_streams = [] - cat_of_line_segment = [] - - # 2. Get coordinates - for row in streamsTopo: - cat_of_line_segment.append(row.cat) - if type(row) == vector.geometry.Line: - points_in_streams.append(row) - """ - - # 3. Coordinates of points: 1 = start, 2 = end - try: - streamsTopo.table.columns.add(x1, "double precision") - except: - pass - try: - streamsTopo.table.columns.add(y1, "double precision") - except: - pass - try: - streamsTopo.table.columns.add(x2, "double precision") - except: - pass - try: - streamsTopo.table.columns.add(y2, "double precision") - except: - pass - try: - streamsTopo.table.columns.add("tostream", "int") - except: - pass - streamsTopo.table.conn.commit() - - # Is this faster than v.to.db? - """ - cur = streamsTopo.table.conn.cursor() - for i in range(len(points_in_streams)): - cur.execute("update streams set x1="+str(points_in_streams[i][0].x)+" where cat="+str(cat_of_line_segment[i])) - cur.execute("update streams set y1="+str(points_in_streams[i][0].y)+" where cat="+str(cat_of_line_segment[i])) - cur.execute("update streams set x2="+str(points_in_streams[i][-1].x)+" where cat="+str(cat_of_line_segment[i])) - cur.execute("update streams set y2="+str(points_in_streams[i][-1].y)+" where cat="+str(cat_of_line_segment[i])) - streamsTopo.table.conn.commit() - streamsTopo.build() - """ - # v.to.db Works more consistently, at least - streamsTopo.close() + # Get coordinates of points: 1 = start, 2 = end v.to_db(map=streams, option="start", columns=x1 + "," + y1) v.to_db(map=streams, option="end", columns=x2 + "," + y2) - # 4. Read in and save the start and end coordinate points - colNames = np.array(vector_db_select(streams)["columns"]) - colValues = np.array(vector_db_select(streams)["values"].values()) - cats = colValues[:, colNames == "cat"].astype(int).squeeze() # river number - xy1 = colValues[:, (colNames == "x1") + (colNames == "y1")].astype( - float - ) # upstream - xy2 = colValues[:, (colNames == "x2") + (colNames == "y2")].astype( - float - ) # downstream - - # 5. Build river network - tocat = [] + # Read in and save the start and end coordinate points + col_names = np.array(vector_db_select(streams)["columns"]) + col_values = np.array(list(vector_db_select(streams)["values"].values())) + # river number + cats = col_values[:, col_names == "cat"].astype(int).squeeze() + # upstream + xy1 = col_values[:, (col_names == "x1") + (col_names == "y1")].astype(float) + # downstream + xy2 = col_values[:, (col_names == "x2") + (col_names == "y2")].astype(float) + + # Build river network + to_cats = [] for i in range(len(cats)): - tosegment_mask = np.prod(xy1 == xy2[i], axis=1) - if np.sum(tosegment_mask) == 0: - tocat.append(0) + to_segment_mask = np.prod(xy1 == xy2[i], axis=1) + if np.sum(to_segment_mask) == 0: + to_cats.append(0) else: - tocat.append(cats[tosegment_mask.nonzero()[0][0]]) - tocat = np.asarray(tocat).astype(int) + to_cats.append(cats[to_segment_mask.nonzero()[0][0]]) + to_cats = np.asarray(to_cats).astype(int) + streams_vector = VectorTopo(streams) + streams_vector.open("rw") + streams_vector.table.columns.add(f"{to_stream}", "int") + streams_vector.table.conn.commit() # This gives us a set of downstream-facing adjacencies. # We will update the database with it. - streamsTopo.build() - streamsTopo.open("rw") - cur = streamsTopo.table.conn.cursor() + cur = streams_vector.table.conn.cursor() # Default to 0 if no stream flows to it - cur.execute("update " + streams + " set tostream=0") - for i in range(len(tocat)): - cur.execute( - "update " - + streams - + " set tostream=" - + str(tocat[i]) - + " where cat=" - + str(cats[i]) - ) - streamsTopo.table.conn.commit() - # streamsTopo.build() - streamsTopo.close() - - gscript.message("") - gscript.message( - 'Drainage topology built. Check "tostream" column for the downstream cat.' + cur.execute(f"update {streams} set {to_stream}=0") + for to_cat, where_cat in zip(to_cats, cats): + cur.execute(f"update {streams} set {to_stream}={to_cat} where cat={where_cat}") + streams_vector.table.conn.commit() + streams_vector.close() + + gs.message( + _( + 'Drainage topology built. See "{to_stream}" column for the downstream cat.' + ).format(to_stream=to_stream) ) - gscript.message("A cat value of 0 indicates the downstream-most segment.") - gscript.message("") + gs.message(_("A cat value of 0 indicates the downstream-most segment.")) if __name__ == "__main__":