Example #1
0
def links_to_geofile(links, dims, gt, crs, path_export):
    """
    Saves the links of the network to a georeferencedshapefile or geojson.
    Computed link properties are saved as attributes when available. Note that
    the 'wid_pix' property, which stores the width at each pixel along the
    link, may be truncated depending on its length and the filetype.
    The filetype is specified by the export path.

    Parameters
    ----------
    links : dict
        Network links and associated properties.
    dims : tuple
        (nrows, ncols) of the original mask from which links were derived.
    gt : tuple
        GDAL geotransform of the original mask from which links were derived.
    crs : pyrpoj.CRS
        CRS object specifying the coordinate reference system of the original
        mask from which links were derived.
    path_export : str
        Path, including extension, specifying where to save the links export.

    Returns
    -------
    None.

    """
    # Create line objects to write to shapefile
    all_links = []
    for link in links['idx']:
        xy = np.unravel_index(link, dims)
        x, y = gu.xy_to_coords(xy[1], xy[0], gt)
        all_links.append(LineString(zip(x, y)))

    # Create GeoDataFrame for storing geometries and attributes
    gdf = gpd.GeoDataFrame(geometry=all_links)
    gdf.crs = crs

    # Store attributes as strings (numpy types give fiona trouble)
    dontstore = ['idx', 'n_networks']
    storekeys = [k for k in links.keys() if k not in dontstore]
    storekeys = [k for k in storekeys if len(links[k]) == len(links['id'])]
    store_as_num = ['id', 'flux', 'logflux']
    for k in storekeys:
        if k in store_as_num:
            gdf[k] = [c for c in links[k]]
        elif k == 'wid_pix':
            gdf[k] = [
                str(c.tolist()).replace('[', '').replace(']', '')
                for c in links[k]
            ]
        else:
            gdf[k] = [
                str(c).replace('[', '').replace(']', '') for c in links[k]
            ]

    # Write geodataframe to file
    gdf.to_file(path_export, driver=get_driver(path_export))
Example #2
0
def nodes_to_shapefile(nodes, dims, gt, epsg, outpath):

    # Create point objects to write to shapefile
    if 'id' not in nodes.keys():
        ids = list(range(0, len(nodes['idx'])))
    else:
        ids = nodes['id']

    all_nodes = []
    for node in nodes['idx']:
        pt = ogr.Geometry(type=ogr.wkbPoint)
        xy = np.unravel_index(node, dims)
        ll = gu.xy_to_coords(xy[1] + .5,
                             xy[0] + .5,
                             gt,
                             inputEPSG=epsg,
                             outputEPSG=epsg)[0]
        pt.AddPoint_2D(ll[1], ll[0])
        all_nodes.append(pt)

    # Write the shapefile
    driver = ogr.GetDriverByName('ESRI Shapefile')
    datasource = driver.CreateDataSource(outpath)

    srs = osr.SpatialReference()
    srs.ImportFromEPSG(epsg)

    layer = datasource.CreateLayer("Nodes", srs, ogr.wkbPoint)
    defn = layer.GetLayerDefn()

    idField = ogr.FieldDefn('id', ogr.OFTInteger)
    connField = ogr.FieldDefn('conn', ogr.OFTString)

    layer.CreateField(idField)
    layer.CreateField(connField)

    for p, i in zip(all_nodes, ids):

        # Create a new feature (attribute and geometry)
        feat = ogr.Feature(defn)
        feat.SetField('id', int(i))

        fieldstr = str(nodes['conn'][nodes['id'].index(i)])
        fieldstr = fieldstr[1:-1]
        feat.SetField('conn', fieldstr)

        # Make a geometry
        geom = ogr.CreateGeometryFromWkb(p.ExportToWkb())
        feat.SetGeometry(geom)

        layer.CreateFeature(feat)
        feat = geom = None  # destroy these

    # Save and close everything
    datasource = layer = feat = geom = None
Example #3
0
    def compute_centerline(self):
        """
        Computes the centerline of the holes-filled river binary image.

        """
        logger.info('Computing centerline...')

        centerline_pix, valley_centerline_widths = ru.mask_to_centerline(self.Imask, self.exit_sides)
        self.max_valley_width_pixels = np.max(valley_centerline_widths)
        self.centerline = gu.xy_to_coords(centerline_pix[:,0], centerline_pix[:,1], self.gt)

        logger.info('centerline computation is done.')
Example #4
0
def nodes_to_geofile(nodes, dims, gt, crs, path_export):
    """
    Saves the nodes of the network to a georeferencedshapefile or geojson.
    Computed node properties are appended as attributes when available.
    The filetype is specified by the export path.

    Parameters
    ----------
    nodes : dict
        Network nodes and associated properties.
    dims : tuple
        (nrows, ncols) of the original mask from which nodes were derived.
    gt : tuple
        GDAL geotransform of the original mask from which nodes were derived.
    crs : pyrpoj.CRS
        CRS object specifying the coordinate reference system of the original
        mask from which nodes were derived.
    path_export : str
        Path, including extension, where to save the nodes export.

    Returns
    -------
    None.

    """
    nodexy = np.unravel_index(nodes['idx'], dims)
    x, y = gu.xy_to_coords(nodexy[1], nodexy[0], gt)
    all_nodes = [Point(x, y) for x, y in zip(x, y)]

    # Create GeoDataFrame for storing geometries and attributes
    gdf = gpd.GeoDataFrame(geometry=all_nodes)
    gdf.crs = crs

    # Store attributes as strings (numpy types give fiona trouble)
    dontstore = ['idx']
    storekeys = [
        k for k in nodes.keys()
        if len(nodes[k]) == len(nodes['id']) and k not in dontstore
    ]
    store_as_num = ['id', 'idx', 'logflux', 'flux', 'outletflux']
    for k in storekeys:
        if k in store_as_num:
            gdf[k] = [c for c in nodes[k]]
        else:
            gdf[k] = [
                str(c).replace('[', '').replace(']', '') for c in nodes[k]
            ]

    # Write geodataframe to file
    gdf.to_file(path_export, driver=get_driver(path_export))
Example #5
0
    def compute_centerline(self):
        """
        Computes the centerline of the holes-filled river binary image.

        """
        if self.verbose is True:
            print('Computing centerline...', end='')

        centerline_pix, valley_centerline_widths = ru.mask_to_centerline(
            self.Imask, self.exit_sides)
        self.max_valley_width_pixels = np.max(valley_centerline_widths)
        self.centerline = gu.xy_to_coords(centerline_pix[:, 0],
                                          centerline_pix[:, 1], self.gt)

        if self.verbose is True:
            print('done.')
Example #6
0
def links_to_shapefile(links, dims, gt, EPSG, outpath):

    # Create line objects to write to shapefile
    all_links = []
    for link in links['idx']:
        line = ogr.Geometry(type=ogr.wkbLineString)
        for pix in link:
            xy = np.unravel_index(pix, dims)
            ll = gu.xy_to_coords(xy[1] + .5,
                                 xy[0] + .5,
                                 gt,
                                 inputEPSG=4326,
                                 outputEPSG=4326)[0]
            line.AddPoint_2D(ll[1], ll[0])

        all_links.append(line)

    # Write the shapefile
    driver = ogr.GetDriverByName('ESRI Shapefile')
    datasource = driver.CreateDataSource(outpath)

    srs = osr.SpatialReference()
    srs.ImportFromEPSG(EPSG)

    layer = datasource.CreateLayer("Links", srs, ogr.wkbLineString)
    defn = layer.GetLayerDefn()

    idField = ogr.FieldDefn('id', ogr.OFTInteger)
    layer.CreateField(idField)
    usField = ogr.FieldDefn('us node', ogr.OFTInteger)
    dsField = ogr.FieldDefn('ds node', ogr.OFTInteger)
    layer.CreateField(usField)
    layer.CreateField(dsField)

    # Include other attributes if available
    if 'len' in links.keys():
        lenField = ogr.FieldDefn('length', ogr.OFTReal)
        layer.CreateField(lenField)
    if 'wid' in links.keys():
        lenField = ogr.FieldDefn('width', ogr.OFTReal)
        layer.CreateField(lenField)
    if 'len_adj' in links.keys():
        widField = ogr.FieldDefn('len_adj', ogr.OFTReal)
        layer.CreateField(widField)
    if 'wid_adj' in links.keys():
        widField = ogr.FieldDefn('width_adj', ogr.OFTReal)
        layer.CreateField(widField)

    usnodes = [c[0] for c in links['conn']]
    dsnodes = [c[1] for c in links['conn']]
    for i, p in enumerate(all_links):

        # Create a new feature (attribute and geometry)
        feat = ogr.Feature(defn)
        feat.SetField('id', int(links['id'][i]))

        # Set upstream and downstream node attributes
        fieldstr = str(usnodes[i])
        feat.SetField('us node', fieldstr)
        fieldstr = str(dsnodes[i])
        feat.SetField('ds node', fieldstr)

        # Set other attributes if available
        if 'len' in links.keys():
            fieldstr = str(links['len'][i])
            feat.SetField('length', fieldstr)

        if 'len_adj' in links.keys():
            fieldstr = str(links['len_adj'][i])
            feat.SetField('len_adj', fieldstr)

        if 'wid' in links.keys():
            fieldstr = str(links['wid'][i])
            feat.SetField('width', fieldstr)

        if 'wid_adj' in links.keys():
            fieldstr = str(links['wid_adj'][i])
            feat.SetField('wid_adj', fieldstr)

        # Make a geometry
        geom = ogr.CreateGeometryFromWkb(p.ExportToWkb())
        feat.SetGeometry(geom)

        layer.CreateFeature(feat)

        feat = geom = None  # destroy these

    # Save and close everything
    datasource = layer = feat = geom = None
Example #7
0
def get_island_properties(Imask,
                          pixlen,
                          pixarea,
                          crs,
                          gt,
                          props,
                          connectivity=2):
    """Get island properties."""

    # maxwidth is an additional property
    if 'maxwidth' in props:
        props.remove('maxwidth')
        do_maxwidth = True
    else:
        do_maxwidth = False

    # Need perimeter to make island polygons
    if 'perimeter' not in props:
        props.append('perimeter')

    # Pad by one pixel to help identify and remove the outer portion of
    # the channel netowrk
    Imaskpad = np.array(np.pad(Imask, 1, mode='constant'), dtype=bool)
    Imp_invert = np.invert(Imaskpad)

    rp_islands, Ilabeled = iu.regionprops(Imp_invert,
                                          props=props,
                                          connectivity=connectivity)

    # Make polygons of the island perimeters
    # Also get ids to match the labeled image
    pgons = []
    ids = []
    for ip, p in enumerate(rp_islands['perimeter']):
        ids.append(Ilabeled[p[0][0], p[0][1]])  # Store the index

        p = np.vstack((p, p[0]))  # Close the polygon
        # Adjust for the single-pixel padding we added to the image
        cr = gu.xy_to_coords(p[:, 1] - 1, p[:, 0] - 1, gt)

        # Special cases: where the island is two pixels or less, we use the
        # corner coordinates rather than the center coordinates to define
        # the polygon.
        if len(cr[0]) <= 2:
            pixgon = [pixagon(cc, rc, pixlen) for cc, rc, in zip(cr[0], cr[1])]
            if len(pixgon) > 1:
                pixgon = cascaded_union(pixgon)
            pgons.append(pixgon)
        else:
            pgons.append(Polygon(zip(cr[0], cr[1])))

    # Do maximum width if requested
    if do_maxwidth:
        Idist = distance_transform_edt(Imp_invert)
        maxwids = []
        for i in ids:
            maxwids.append(np.max(Idist[Ilabeled == i]) * 2 * pixlen)

    # Convert requested properties to proper units
    if 'area' in props:
        rp_islands['area'] = rp_islands['area'] * pixarea
    if 'major_axis_length' in props:
        rp_islands[
            'major_axis_length'] = rp_islands['major_axis_length'] * pixlen
    if 'minor_axis_length' in props:
        rp_islands[
            'minor_axis_length'] = rp_islands['minor_axis_length'] * pixlen
    if 'perim_len' in props:
        rp_islands['perim_len'] = rp_islands['perim_len'] * pixlen
    if 'convex_area' in props:
        rp_islands['convex_area'] = rp_islands['convex_area'] * pixarea

    # Need to change 'area' key as it's a function in geopandas
    if 'area' in rp_islands:
        rp_islands['Area'] = rp_islands.pop('area')

    # Create islands geodataframe
    gdf_dict = {
        k: rp_islands[k]
        for k in rp_islands if k not in ['coords', 'perimeter', 'centroid']
    }
    gdf_dict['geometry'] = pgons
    gdf_dict['id'] = ids
    if do_maxwidth:
        gdf_dict['maxwid'] = maxwids
    gdf = gpd.GeoDataFrame(gdf_dict)
    gdf.crs = crs

    # Identify and remove the border blob
    border_id = Ilabeled[0][0]
    Ilabeled[Ilabeled == border_id] = 0
    gdf = gdf[gdf.id.values != border_id]

    # Put 'id' column in front
    colnames = [k for k in gdf.keys()]
    colnames.remove('id')
    colnames.insert(0, 'id')
    gdf = gdf[colnames]

    return gdf, Ilabeled[1:-1, 1:-1]
Example #8
0
def dir_centerline(links, nodes, meshpolys, meshlines, Imask, gt, pixlen):
    """
    Guess flow directions of links in a braided river channel.

    Guesses the flow direction of links in a braided river channel network by
    exploiting a "valleyline" centerline. Two metrics are computed to help
    guess the correct direction. The first is the number of centerline
    transects (meshlines) that the link crosses. The second is the local angle
    of the centerline compared to the link's angle. These metrics are appended
    to the links dictionary as links['cldist'] and links['clangs'].

    Parameters
    ----------
    links : dict
        Network links and associated properties.
    nodes : dict
        Network nodes and associated properties.
    meshpolys : list
        List of shapely.geometry.Polygons that define the valleyline mesh.
    meshlines : list
        List of shapely.geometry.LineStrings that define the valleyline mesh.
    Imask : np.array
        Binary mask of the network.
    gt : tuple
        gdal-type GeoTransform of the original binary mask.
    pixlen : float
        Length resolution of each pixel.

    Returns
    -------
    links : dict
        Network links and associated properties with 'cldists' and 'clangs'
        attributes appended.

    """
    # alg = 20
    alg = dy.algmap('cl_dist_guess')

    # Create geodataframes for intersecting meshpolys with nodes
    mp_gdf = gpd.GeoDataFrame(geometry=[Polygon(mp) for mp in meshpolys])
    rc = np.unravel_index(nodes['idx'], Imask.shape)
    nodecoords = gu.xy_to_coords(rc[1], rc[0], gt)
    node_gdf = gpd.GeoDataFrame(
        geometry=[Point(x, y) for x, y in zip(nodecoords[0], nodecoords[1])],
        index=nodes['id'])

    # Determine which meshpoly each node lies within
    intersect = gpd.sjoin(node_gdf, mp_gdf, op='intersects', rsuffix='right')

    # Compute guess and certainty, where certainty is how many transects apart
    # the link endpoints are (longer=more certain)
    cldists = np.zeros((len(links['id']), 1))
    for i, lconn in enumerate(links['conn']):
        try:
            first = intersect.loc[lconn[0]].index_right
            second = intersect.loc[lconn[1]].index_right
            cldists[i] = second - first
        except KeyError:
            pass

    for i, c in enumerate(cldists):
        if c != 0:
            if c > 0:
                links['guess'][i].append(links['conn'][i][0])
                links['guess_alg'][i].append(alg)
            elif c < 0:
                links['guess'][i].append(links['conn'][i][-1])
                links['guess_alg'][i].append(alg)

    # Save the distances for certainty
    links['cldists'] = np.abs(cldists)

    # Compute guesses based on how the link aligns with the local centerline
    # direction
    # alg = 21
    alg = dy.algmap('cl_ang_guess')
    clangs = np.ones((len(links['id']), 1)) * np.nan
    for i, (lconn, lidx) in enumerate(zip(links['conn'], links['idx'])):
        # Get coordinates of link endpoints
        rc = np.unravel_index([lidx[0], lidx[-1]], Imask.shape)

        try:  # Try is because some points may not lie within the mesh polygons
            # Get coordinates of centerline midpoints
            first = intersect.loc[lconn[0]].index_right
            second = intersect.loc[lconn[1]].index_right
            if first > second:
                first, second = second, first
            first_mp = np.mean(np.array(meshlines[first]), axis=0)  # midpoint
            second_mp = np.mean(np.array(meshlines[second + 1]),
                                axis=0)  # midpoint
        except KeyError:
            continue

        # Centerline vector
        cl_vec = second_mp - first_mp
        cl_vec = cl_vec / np.sqrt(np.sum(cl_vec**2))

        # Link vectors - as-is and flipped (reversed)
        link_vec = dy.get_link_vector(links,
                                      nodes,
                                      links['id'][i],
                                      Imask.shape,
                                      pixlen=pixlen)
        link_vec_rev = -link_vec

        # Compute interior radians between centerline vector and link vector
        # (then again with link vector flipped)
        lva = np.math.atan2(np.linalg.det([cl_vec, link_vec]),
                            np.dot(cl_vec, link_vec))
        lvar = np.math.atan2(np.linalg.det([cl_vec, link_vec_rev]),
                             np.dot(cl_vec, link_vec_rev))

        # Save the maximum angle
        clangs[i] = np.min(np.abs([lva, lvar]))

        # Make a guess; smaller interior angle (i.e. link direction that aligns
        # best with local centerline direction) guesses the link orientation
        if np.abs(lvar) < np.abs(lva):
            links['guess'][i].append(links['conn'][i][1])
            links['guess_alg'][i].append(alg)
        else:
            links['guess'][i].append(links['conn'][i][0])
            links['guess_alg'][i].append(alg)
    links['clangs'] = clangs

    return links