예제 #1
0
def set_initial_directionality(links, nodes, imshape):
    """
    Make initial attempt to set flow directions.

    Makes an initial attempt to set all flow directions within the network.
    This represents the core of the "delta recipe" described in the following
    open access paper:
    https://esurf.copernicus.org/articles/8/87/2020/esurf-8-87-2020.pdf
    However, note that as RivGraph develops, this recipe may no longer match
    the one presented in that paper. The recipe chains together a number of
    exploitative algorithms to iteratively set flow directions for the most
    certain links first.


    Parameters
    ----------
    links : dict
        Network links and associated properties.
    nodes : dict
        Network nodes and associated properties.
    imshape : tuple
        Shape of binary mask as (nrows, ncols).

    Returns
    -------
    links : dict
        Network links and associated properties with initial directions set.
    nodes : dict
        Network nodes and associated properties with initial directions set.

    """
    # Compute all the "guesses"
    links, nodes = dy.dir_main_channel(links, nodes)
    links, nodes = dir_synthetic_DEM(links, nodes, imshape)
    links, nodes = dy.dir_shortest_paths_nodes(links, nodes)
    links, nodes = dy.dir_shortest_paths_links(links, nodes)
    links, nodes = dy.dir_bridges(links, nodes)

    # Set link directions
    # First, set inlet/outlet directions as they are always 100% accurate
    links, nodes = dy.set_inletoutlet(links, nodes)

    # Use bridges to set links as they are always 100% accurate
    # alg = 5
    alg = dy.algmap('bridges')
    for lid, idcs, lg, lga, cert in zip(links['id'], links['idx'],
                                        links['guess'], links['guess_alg'],
                                        links['certain']):
        # Only need to set links that haven't been set
        if cert == 1:
            continue
        linkidx = links['id'].index(lid)
        # Set all the links that are known from bridge links
        if alg in lga:
            links, nodes = dy.set_link(links, nodes, linkidx,
                                       lg[lga.index(alg)], alg)

    # Use main channels (4) to set links
    # alg = 4
    alg = dy.algmap('main_chans')
    for lid, idcs, lg, lga, cert in zip(links['id'], links['idx'],
                                        links['guess'], links['guess_alg'],
                                        links['certain']):
        # Only need to set links that haven't been set
        if cert == 1:
            continue
        linkidx = links['id'].index(lid)
        # Set all the links that are known from main_channel
        if alg in lga:
            links, nodes = dy.set_link(links, nodes, linkidx,
                                       lg[lga.index(alg)], alg)

    # Set the longest, steepest links according to io_surface
    # (these are those we are most certain of)
    # alg = 13
    alg = dy.algmap('longest_steepest')
    len75 = np.percentile(links['len_adj'], 75)
    slope50 = np.percentile(np.abs(links['slope']), 50)
    for lid, llen, lg, lga, cert, lslope in zip(links['id'], links['len_adj'],
                                                links['guess'],
                                                links['guess_alg'],
                                                links['certain'],
                                                links['slope']):
        if cert == 1:
            continue
        if llen > len75 and abs(lslope) > slope50:
            linkidx = links['id'].index(lid)
            if dy.algmap('syn_dem') in lga:
                usnode = lg[lga.index(dy.algmap('syn_dem'))]
                links, nodes = dy.set_link(links, nodes, linkidx, usnode, alg)

    # Set the most certain angles
    angthreshs = np.linspace(0, 0.5, 10)
    for a in angthreshs:
        links, nodes = dy.set_by_known_flow_directions(links,
                                                       nodes,
                                                       imshape,
                                                       angthresh=a)

    # Set using direction of nearest main channel
    links, nodes = dy.set_by_nearest_main_channel(links,
                                                  nodes,
                                                  imshape,
                                                  nodethresh=2)

    # Set the most certain angles
    angthreshs = np.linspace(0, 0.7, 10)
    for a in angthreshs:
        links, nodes = dy.set_by_known_flow_directions(links,
                                                       nodes,
                                                       imshape,
                                                       angthresh=a)

    # Set using direction of nearest main channel
    links, nodes = dy.set_by_nearest_main_channel(links,
                                                  nodes,
                                                  imshape,
                                                  nodethresh=1)

    angthreshs = np.linspace(0, 0.8, 10)
    for a in angthreshs:
        links, nodes = dy.set_by_known_flow_directions(links,
                                                       nodes,
                                                       imshape,
                                                       angthresh=a,
                                                       lenthresh=3)

    # Use io_surface (3) to set links that are longer
    # than the median link length
    alg = dy.algmap('syn_dem')
    medlinklen = np.median(links['len'])
    for lid, llen, lg, lga, cert in zip(links['id'], links['len'],
                                        links['guess'], links['guess_alg'],
                                        links['certain']):
        if cert == 1:
            continue
        if llen > medlinklen and dy.algmap('syn_dem') in lga:
            linkidx = links['id'].index(lid)
            usnode = lg[lga.index(dy.algmap('syn_dem'))]
            links, nodes = dy.set_link(links, nodes, linkidx, usnode, alg)

    # Set again by angles, but reduce the lenthresh
    # (shorter links will be set that were not previously)
    angthreshs = np.linspace(0, 0.6, 10)
    for a in angthreshs:
        links, nodes = dy.set_by_known_flow_directions(links,
                                                       nodes,
                                                       imshape,
                                                       angthresh=a,
                                                       lenthresh=0)

    # If any three methods agree, set that link to whatever they agree on
    # alg = 15
    alg = dy.algmap('three_agree')
    for lid, idcs, lg, lga, cert in zip(links['id'], links['idx'],
                                        links['guess'], links['guess_alg'],
                                        links['certain']):
        # Only need to set links that haven't been set
        if cert == 1:
            continue
        linkidx = links['id'].index(lid)
        # Set all the links with 3 or more guesses that agree
        m = mode(lg)
        if m.count[0] > 2:
            links, nodes = dy.set_link(links, nodes, linkidx, m.mode[0], alg)

    # Set again by angles, but reduce the lenthresh
    # (shorter links will be set that were not previously)
    angthreshs = np.linspace(0, 0.8, 10)
    for a in angthreshs:
        links, nodes = dy.set_by_known_flow_directions(links,
                                                       nodes,
                                                       imshape,
                                                       angthresh=a,
                                                       lenthresh=0)

    # If artificial DEM and at least one shortest path method agree,
    # set link to be their agreement
    # alg = 16
    alg = dy.algmap('syn_dem_and_sp')
    for lid, idcs, lg, lga, cert in zip(links['id'], links['idx'],
                                        links['guess'], links['guess_alg'],
                                        links['certain']):
        # Only need to set links that haven't been set
        if cert == 1:
            continue
        linkidx = links['id'].index(lid)
        # Set all the links with 2 or more same guesses that are not
        # shortest path (one may be shortest path)
        if dy.algmap('syn_dem') in lga and dy.algmap('sp_links') in lga:
            if lg[lga.index(dy.algmap('syn_dem'))] == lg[lga.index(
                    dy.algmap('sp_links'))]:
                links, nodes = dy.set_link(links, nodes, linkidx,
                                           lg[lga.index(dy.algmap('syn_dem'))],
                                           alg)
        elif dy.algmap('syn_dem') in lga and dy.algmap('sp_nodes') in lga:
            if lg[lga.index(dy.algmap('syn_dem'))] == lg[lga.index(
                    dy.algmap('sp_nodes'))]:
                links, nodes = dy.set_link(links, nodes, linkidx,
                                           lg[lga.index(dy.algmap('syn_dem'))],
                                           alg)

    # Find remaining uncertain links
    uncertain = [l for l, lc in zip(links['id'], links['certain']) if lc != 1]

    # Set remaining uncertains according to io_surface (3)
    # alg = 10 # change this one!
    alg = dy.algmap('syn_dem')
    for lid in uncertain:
        linkidx = links['id'].index(lid)
        if alg in links['guess_alg'][linkidx]:
            usnode = links['guess'][linkidx][links['guess_alg'][linkidx].index(
                alg)]
            links, nodes = dy.set_link(links, nodes, linkidx, usnode, alg)

    return links, nodes
예제 #2
0
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