def test_directionlity_trackers(): """Test of directionality tracker function.""" links = {'id': [0, 1]} nodes = {'id': [0, 1]} ntype = 'delta' di.add_directionality_trackers(links, nodes, ntype) # assertions assert 'certain' in links.keys() assert 'certain_order' in links.keys() assert 'certain_alg' in links.keys() assert 'guess' in links.keys() assert 'guess_alg' in links.keys() assert 'maxang' not in links.keys() # pretend its a river di.add_directionality_trackers(links, nodes, 'river') assert 'maxang' in links.keys()
def set_link_directions(links, nodes, imshape, manual_set_csv=None): """ Set each link direction in a network. This function sets the direction of each link within the network. It calls a number of helping functions and uses a somewhat-complicated logic to achieve this. The algorithms and logic is described in this open-access paper: https://esurf.copernicus.org/articles/8/87/2020/esurf-8-87-2020.pdf Every time this is run, all directionality information is reset and recomputed. This includes checking for manually set links via the provided csv. Parameters ---------- links : dict Network links and associated properties. nodes : dict Network nodes and associated properties. imshape : tuple Shape of binary mask as (nrows, ncols). manual_set_csv : str, optional Path to a user-provided csv file of known link directions. The default is None. Returns ------- links : dict Network links and associated properties with all directions set. nodes : dict Network nodes and associated properties with all directions set. """ # Add fields to links dict for tracking and setting directionality links, nodes = dy.add_directionality_trackers(links, nodes, 'delta') # If a manual fix csv has been provided, set those links first links, nodes = dy.dir_set_manually(links, nodes, manual_set_csv) # Initial attempt to set directions links, nodes = set_initial_directionality(links, nodes, imshape) # At this point, all links have been set. Check for nodes that violate # continuity cont_violators = dy.check_continuity(links, nodes) # Attempt to fix any sources or sinks within the network if len(cont_violators) > 0: links, nodes = dy.fix_sources_and_sinks(links, nodes) # Check that continuity problems are resolved cont_violators = dy.check_continuity(links, nodes) if len(cont_violators) > 0: print( 'Nodes {} violate continuity. Check connected links and fix manually.' .format(cont_violators)) # Attempt to fix any cycles in the network (reports unfixable within function) links, nodes, allcyclesfixed = fix_delta_cycles(links, nodes, imshape) # The following is done automatically now, regardless of if cycles or sinks exist # # Create a csv to store manual edits to directionality if does not exist # if os.path.isfile(manual_set_csv) is False: # if len(cont_violators) > 0 or allcyclesfixed == 0: # io.create_manual_dir_csv(manual_set_csv) # print('A .csv file for manual fixes to link directions at {}.'.format(manual_set_csv)) if allcyclesfixed == 2: print('No cycles were found in network.') return links, nodes
def set_directionality(links, nodes, Imask, exit_sides, gt, meshlines, meshpolys, Idt, pixlen, manual_set_csv): """ Set direction of each link. This function sets the direction of each link within the network. It calls a number of helping functions and uses a somewhat-complicated logic to achieve this. The algorithms and logic is described in this open-access paper: https://esurf.copernicus.org/articles/8/87/2020/esurf-8-87-2020.pdf Every time this is run, all directionality information is reset and recomputed. This includes checking for manually set links via the provided csv. Parameters ---------- links : dict Network links and associated properties. nodes : dict Network nodes and associated properties. Imask : np.array Binary mask of the network. exit_sides : str Two-character string of cardinal directions denoting the upstream and downsteram sides of the image that the network intersects (e.g. 'SW'). gt : tuple gdal-type GeoTransform of the original binary mask. meshlines : list List of shapely.geometry.LineStrings that define the valleyline mesh. meshpolys : list List of shapely.geometry.Polygons that define the valleyline mesh. Idt : np.array() Distance transform of Imask. pixlen : float Length resolution of each pixel. manual_set_csv : str, optional Path to a user-provided csv file of known link directions. The default is None. Returns ------- links : dict Network links and associated properties with all directions set. nodes : dict Network nodes and associated properties with all directions set. """ imshape = Imask.shape # Add fields to links dict for tracking and setting directionality links, nodes = dy.add_directionality_trackers(links, nodes, 'river') # If a manual fix csv has been provided, set those links first links, nodes = dy.dir_set_manually(links, nodes, manual_set_csv) # Append morphological information used to set directionality to links dict links, nodes = directional_info(links, nodes, Imask, pixlen, exit_sides, gt, meshlines, meshpolys, Idt) # Begin setting link directionality # First, set inlet/outlet directions as they are always 100% accurate links, nodes = dy.set_inletoutlet(links, nodes) # Set the directions of the links that are more certain via centerline # distance method # alg = 22 alg = dy.algmap('cl_dist_set') cl_distthresh = np.percentile(links['cldists'], 85) for lid, cld, lg, lga, cert in zip(links['id'], links['cldists'], links['guess'], links['guess_alg'], links['certain']): if cert == 1: continue if cld >= cl_distthresh: linkidx = links['id'].index(lid) if dy.algmap('cl_dist_guess') in lga: usnode = lg[lga.index(dy.algmap('cl_dist_guess'))] links, nodes = dy.set_link(links, nodes, linkidx, usnode, alg) # Set the directions of the links that are more certain via centerline # angle method # alg = 23 alg = dy.algmap('cl_ang_set') cl_angthresh = np.percentile( links['clangs'][np.isnan(links['clangs']) == 0], 25) for lid, cla, lg, lga, cert in zip(links['id'], links['clangs'], links['guess'], links['guess_alg'], links['certain']): if cert == 1: continue if np.isnan(cla) == True: continue if cla <= cl_angthresh: linkidx = links['id'].index(lid) if dy.algmap('cl_ang_guess') in lga: usnode = lg[lga.index(dy.algmap('cl_ang_guess'))] links, nodes = dy.set_link(links, nodes, linkidx, usnode, alg) # Set the directions of the links that are more certain via centerline # distance AND centerline angle methods # alg = 24 alg = dy.algmap('cl_dist_and_ang') cl_distthresh = np.percentile(links['cldists'], 70) ang_thresh = np.percentile(links['clangs'][np.isnan(links['clangs']) == 0], 35) for lid, cld, cla, lg, lga, cert in zip(links['id'], links['cldists'], links['clangs'], links['guess'], links['guess_alg'], links['certain']): if cert == 1: continue if cld >= cl_distthresh and cla < ang_thresh: linkidx = links['id'].index(lid) if dy.algmap('cl_dist_guess') in lga and dy.algmap( 'cl_ang_guess') in lga: if lg[lga.index(dy.algmap('cl_dist_guess'))] == lg[lga.index( dy.algmap('cl_ang_guess'))]: usnode = lg[lga.index(dy.algmap('cl_dist_guess'))] links, nodes = dy.set_link(links, nodes, linkidx, usnode, alg) # Set directions by most-certain angles angthreshs = np.linspace(0, 0.4, 10) for a in angthreshs: links, nodes = dy.set_by_known_flow_directions(links, nodes, imshape, angthresh=a, lenthresh=3) # Set using direction of nearest main channel links, nodes = dy.set_by_nearest_main_channel(links, nodes, imshape, nodethresh=1) angthreshs = np.linspace(0, 1.5, 20) for a in angthreshs: links, nodes = dy.set_by_known_flow_directions(links, nodes, imshape, angthresh=a) # At this point, if any links remain unset, they are just set randomly if np.sum(links['certain']) != len(links['id']): print('{} links were randomly set.'.format( len(links['id']) - np.sum(links['certain']))) links['certain'] = np.ones((len(links['id']), 1)) # Check for and try to fix cycles in the graph links, nodes, cantfix_cyclelinks, cantfix_cyclenodes = fix_river_cycles( links, nodes, imshape) # Check for sources or sinks within the graph cont_violators = dy.check_continuity(links, nodes) # Summary of problems: manual_fix = 0 if len(cantfix_cyclelinks) > 0: print('Could not fix cycle links: {}.'.format(cantfix_cyclelinks)) manual_fix = 1 else: print('All cycles were resolved.') if len(cont_violators) > 0: print('Continuity violated at nodes {}.'.format(cont_violators)) manual_fix = 1 # Create a csv to store manual edits to directionality if does not exist if manual_fix == 1: if os.path.isfile(manual_set_csv) is False: io.create_manual_dir_csv(manual_set_csv) print('A .csv file for manual fixes to link directions at {}.'. format(manual_set_csv)) else: print('Use the csv file at {} to manually fix link directions.'. format(manual_set_csv)) return links, nodes