Пример #1
0
def bankfull_nhd_area(bankfull_path, nhd_path, clip_path, espg, output_path,
                      out_name):

    clip_geom = collect_feature_class(clip_path)

    with TempGeopackage('sanitize_temp') as tempgpkg, \
            GeopackageLayer(output_path, out_name, write=True) as lyr_output:

        merged_path = os.path.join(tempgpkg.filepath,
                                   f"bankfull_nhd_merge_{str(uuid4())}")

        with GeopackageLayer(merged_path, write=True,
                             delete_dataset=True) as tmp_lyr:
            tmp_lyr.create_layer(ogr.wkbPolygon, espg)

        # Get merged and unioned Geom
        merge_feature_classes([nhd_path, bankfull_path], clip_geom,
                              merged_path)
        out_geom = get_geometry_unary_union(merged_path)

        # Write Output
        lyr_output.create_layer(ogr.wkbPolygon, espg)
        feat = ogr.Feature(lyr_output.ogr_layer_def)
        feat.SetGeometry(out_geom)
        lyr_output.create_feature(out_geom)
Пример #2
0
def vbet_network(flow_lines_path: str,
                 flow_areas_path: str,
                 out_path: str,
                 epsg: int = None,
                 fcodes: List[str] = None):

    log = Logger('VBET Network')
    log.info('Generating perennial network')
    fcodes = ["46006"] if fcodes is None else fcodes

    with get_shp_or_gpkg(out_path, write=True) as vbet_net, \
            get_shp_or_gpkg(flow_lines_path) as flow_lines_lyr:

        # Add input Layer Fields to the output Layer if it is the one we want
        vbet_net.create_layer_from_ref(flow_lines_lyr, epsg=epsg)

        # Perennial features
        log.info('Incorporating perennial features')
        fcode_filter = "FCode = " + " or FCode = ".join([
            f"'{fcode}'" for fcode in fcodes
        ]) if len(
            fcodes) > 0 else ""  # e.g. "FCode = '46006' or FCode = '55800'"
        fids = include_features(flow_lines_lyr, vbet_net, fcode_filter)

        # Flow area features
        polygon = get_geometry_unary_union(flow_areas_path, epsg=epsg)
        if polygon is not None:
            log.info('Incorporating flow areas.')
            include_features(flow_lines_lyr,
                             vbet_net,
                             "FCode <> '46006'",
                             polygon,
                             excluded_fids=fids)

        fcount = flow_lines_lyr.ogr_layer.GetFeatureCount()

        log.info('VBET network generated with {} features'.format(fcount))
Пример #3
0
def confinement(huc: int,
                flowlines_orig: Path,
                confining_polygon_orig: Path,
                output_folder: Path,
                buffer_field: str,
                confinement_type: str,
                reach_codes: List[str],
                min_buffer: float = 0.0,
                bankfull_expansion_factor: float = 1.0,
                debug: bool = False,
                meta=None):
    """Generate confinement attribute for a stream network

    Args:
        huc (integer): Huc identifier
        flowlines (path): input flowlines layer
        confining_polygon (path): valley bottom or other boundary defining confining margins
        output_folder (path): location to store confinement project and output geopackage
        buffer_field (string): name of float field with buffer values in meters (i.e. 'BFWidth')
        confinement_type (string): name of type of confinement generated
        reach_codes (List[int]): NHD reach codes for features to include in outputs
        min_buffer (float): minimum bankfull value to use in buffers e.g. raster cell resolution
        bankfull_expansion_factor (float): factor to expand bankfull on each side of bank
        debug (bool): run tool in debug mode (save intermediate outputs). Default = False
        meta (Dict[str,str]): dictionary of riverscapes metadata key: value pairs
    """

    log = Logger("Confinement")
    log.info(f'Confinement v.{cfg.version}')  # .format(cfg.version))

    try:
        int(huc)
    except ValueError:
        raise Exception(
            'Invalid HUC identifier "{}". Must be an integer'.format(huc))

    if not (len(huc) == 4 or len(huc) == 8):
        raise Exception('Invalid HUC identifier. Must be four digit integer')

    # Make the projectXML
    project, _realization, proj_nodes, report_path = create_project(
        huc, output_folder, {'ConfinementType': confinement_type})

    # Incorporate project metadata to the riverscapes project
    if meta is not None:
        project.add_metadata(meta)

    # Copy input shapes to a geopackage
    flowlines_path = os.path.join(
        output_folder, LayerTypes['INPUTS'].rel_path,
        LayerTypes['INPUTS'].sub_layers['FLOWLINES'].rel_path)
    confining_path = os.path.join(
        output_folder, LayerTypes['INPUTS'].rel_path,
        LayerTypes['INPUTS'].sub_layers['CONFINING_POLYGON'].rel_path)

    copy_feature_class(flowlines_orig, flowlines_path, epsg=cfg.OUTPUT_EPSG)
    copy_feature_class(confining_polygon_orig,
                       confining_path,
                       epsg=cfg.OUTPUT_EPSG)

    _nd, _inputs_gpkg_path, inputs_gpkg_lyrs = project.add_project_geopackage(
        proj_nodes['Inputs'], LayerTypes['INPUTS'])

    output_gpkg = os.path.join(output_folder,
                               LayerTypes['CONFINEMENT'].rel_path)
    intermediates_gpkg = os.path.join(output_folder,
                                      LayerTypes['INTERMEDIATES'].rel_path)

    # Creates an empty geopackage and replaces the old one
    GeopackageLayer(output_gpkg, delete_dataset=True)
    GeopackageLayer(intermediates_gpkg, delete_dataset=True)

    # Add the flowlines file with some metadata
    project.add_metadata({'BufferField': buffer_field},
                         inputs_gpkg_lyrs['FLOWLINES'][0])

    # Add the confinement polygon
    project.add_project_geopackage(proj_nodes['Intermediates'],
                                   LayerTypes['INTERMEDIATES'])
    _nd, _inputs_gpkg_path, out_gpkg_lyrs = project.add_project_geopackage(
        proj_nodes['Outputs'], LayerTypes['CONFINEMENT'])

    # Additional Metadata
    project.add_metadata(
        {
            'Min Buffer': str(min_buffer),
            "Expansion Factor": str(bankfull_expansion_factor)
        }, out_gpkg_lyrs['CONFINEMENT_BUFFERS'][0])

    # Generate confining margins
    log.info(f"Preparing output geopackage: {output_gpkg}")
    log.info(f"Generating Confinement from buffer field: {buffer_field}")

    # Load input datasets and set the global srs and a meter conversion factor
    with GeopackageLayer(flowlines_path) as flw_lyr:
        srs = flw_lyr.spatial_ref
        meter_conversion = flw_lyr.rough_convert_metres_to_vector_units(1)

    geom_confining_polygon = get_geometry_unary_union(confining_path,
                                                      cfg.OUTPUT_EPSG)

    # Calculate Spatial Constants
    # Get a very rough conversion factor for 1m to whatever units the shapefile uses
    offset = 0.1 * meter_conversion
    selection_buffer = 0.1 * meter_conversion

    # Standard Outputs
    field_lookup = {
        'side': ogr.FieldDefn("Side", ogr.OFTString),
        'flowlineID': ogr.FieldDefn(
            "NHDPlusID", ogr.OFTString
        ),  # ArcGIS cannot read Int64 and will show up as 0, however data is stored correctly in GPKG
        'confinement_type': ogr.FieldDefn("Confinement_Type", ogr.OFTString),
        'confinement_ratio': ogr.FieldDefn("Confinement_Ratio", ogr.OFTReal),
        'constriction_ratio': ogr.FieldDefn("Constriction_Ratio", ogr.OFTReal),
        'length': ogr.FieldDefn("ApproxLeng", ogr.OFTReal),
        'confined_length': ogr.FieldDefn("ConfinLeng", ogr.OFTReal),
        'constricted_length': ogr.FieldDefn("ConstrLeng", ogr.OFTReal),
        'bankfull_width': ogr.FieldDefn("Bankfull_Width", ogr.OFTReal),
        'buffer_width': ogr.FieldDefn("Buffer_Width", ogr.OFTReal),
        # Couple of Debug fields too
        'process': ogr.FieldDefn("ErrorProcess", ogr.OFTString),
        'message': ogr.FieldDefn("ErrorMessage", ogr.OFTString)
    }

    field_lookup['side'].SetWidth(5)
    field_lookup['confinement_type'].SetWidth(5)

    # Here we open all the necessary output layers and write the fields to them. There's no harm in quickly
    # Opening these layers to instantiate them

    # Standard Outputs
    with GeopackageLayer(output_gpkg,
                         layer_name=LayerTypes['CONFINEMENT'].
                         sub_layers["CONFINEMENT_MARGINS"].rel_path,
                         write=True) as margins_lyr:
        margins_lyr.create(ogr.wkbLineString, spatial_ref=srs)
        margins_lyr.ogr_layer.CreateField(field_lookup['side'])
        margins_lyr.ogr_layer.CreateField(field_lookup['flowlineID'])
        margins_lyr.ogr_layer.CreateField(field_lookup['length'])

    with GeopackageLayer(output_gpkg,
                         layer_name=LayerTypes['CONFINEMENT'].
                         sub_layers["CONFINEMENT_RAW"].rel_path,
                         write=True) as raw_lyr:
        raw_lyr.create(ogr.wkbLineString, spatial_ref=srs)
        raw_lyr.ogr_layer.CreateField(field_lookup['flowlineID'])
        raw_lyr.ogr_layer.CreateField(field_lookup['confinement_type'])
        raw_lyr.ogr_layer.CreateField(field_lookup['length'])

    with GeopackageLayer(output_gpkg,
                         layer_name=LayerTypes['CONFINEMENT'].
                         sub_layers["CONFINEMENT_RATIO"].rel_path,
                         write=True) as ratio_lyr:
        ratio_lyr.create(ogr.wkbLineString, spatial_ref=srs)
        ratio_lyr.ogr_layer.CreateField(field_lookup['flowlineID'])
        ratio_lyr.ogr_layer.CreateField(field_lookup['confinement_ratio'])
        ratio_lyr.ogr_layer.CreateField(field_lookup['constriction_ratio'])
        ratio_lyr.ogr_layer.CreateField(field_lookup['length'])
        ratio_lyr.ogr_layer.CreateField(field_lookup['confined_length'])
        ratio_lyr.ogr_layer.CreateField(field_lookup['constricted_length'])

    with GeopackageLayer(intermediates_gpkg,
                         layer_name=LayerTypes['INTERMEDIATES'].
                         sub_layers["CONFINEMENT_BUFFER_SPLIT"].rel_path,
                         write=True) as lyr:
        lyr.create(ogr.wkbPolygon, spatial_ref=srs)
        lyr.ogr_layer.CreateField(field_lookup['side'])
        lyr.ogr_layer.CreateField(field_lookup['flowlineID'])
        lyr.ogr_layer.CreateField(field_lookup['bankfull_width'])
        lyr.ogr_layer.CreateField(field_lookup['buffer_width'])

    with GeopackageLayer(output_gpkg,
                         layer_name=LayerTypes['CONFINEMENT'].
                         sub_layers["CONFINEMENT_BUFFERS"].rel_path,
                         write=True) as lyr:
        lyr.create(ogr.wkbPolygon, spatial_ref=srs)
        lyr.ogr_layer.CreateField(field_lookup['flowlineID'])
        lyr.ogr_layer.CreateField(field_lookup['bankfull_width'])
        lyr.ogr_layer.CreateField(field_lookup['buffer_width'])

    with GeopackageLayer(intermediates_gpkg,
                         layer_name=LayerTypes['INTERMEDIATES'].
                         sub_layers["SPLIT_POINTS"].rel_path,
                         write=True) as lyr:
        lyr.create(ogr.wkbPoint, spatial_ref=srs)
        lyr.ogr_layer.CreateField(field_lookup['side'])
        lyr.ogr_layer.CreateField(field_lookup['flowlineID'])

    with GeopackageLayer(intermediates_gpkg,
                         layer_name=LayerTypes['INTERMEDIATES'].
                         sub_layers["FLOWLINE_SEGMENTS"].rel_path,
                         write=True) as lyr:
        lyr.create(ogr.wkbLineString, spatial_ref=srs)
        lyr.ogr_layer.CreateField(field_lookup['side'])
        lyr.ogr_layer.CreateField(field_lookup['flowlineID'])

    with GeopackageLayer(intermediates_gpkg,
                         layer_name=LayerTypes['INTERMEDIATES'].
                         sub_layers["ERROR_POLYLINES"].rel_path,
                         write=True) as lyr:
        lyr.create(ogr.wkbLineString, spatial_ref=srs)
        lyr.ogr_layer.CreateField(field_lookup['process'])
        lyr.ogr_layer.CreateField(field_lookup['message'])

    with GeopackageLayer(intermediates_gpkg,
                         layer_name=LayerTypes['INTERMEDIATES'].
                         sub_layers["ERROR_POLYGONS"].rel_path,
                         write=True) as lyr:
        lyr.create(ogr.wkbPolygon, spatial_ref=srs)
        lyr.ogr_layer.CreateField(field_lookup['process'])
        lyr.ogr_layer.CreateField(field_lookup['message'])

    # Generate confinement per Flowline
    with GeopackageLayer(flowlines_path) as flw_lyr, \
            GeopackageLayer(output_gpkg, layer_name=LayerTypes['CONFINEMENT'].sub_layers["CONFINEMENT_MARGINS"].rel_path, write=True) as margins_lyr, \
            GeopackageLayer(output_gpkg, layer_name=LayerTypes['CONFINEMENT'].sub_layers["CONFINEMENT_RAW"].rel_path, write=True) as raw_lyr, \
            GeopackageLayer(output_gpkg, layer_name=LayerTypes['CONFINEMENT'].sub_layers["CONFINEMENT_RATIO"].rel_path, write=True) as ratio_lyr, \
            GeopackageLayer(intermediates_gpkg, layer_name=LayerTypes['INTERMEDIATES'].sub_layers["SPLIT_POINTS"].rel_path, write=True) as dbg_splitpts_lyr, \
            GeopackageLayer(intermediates_gpkg, layer_name=LayerTypes['INTERMEDIATES'].sub_layers["FLOWLINE_SEGMENTS"].rel_path, write=True) as dbg_flwseg_lyr, \
            GeopackageLayer(intermediates_gpkg, layer_name=LayerTypes['INTERMEDIATES'].sub_layers["CONFINEMENT_BUFFER_SPLIT"].rel_path, write=True) as conf_buff_split_lyr, \
            GeopackageLayer(output_gpkg, layer_name=LayerTypes['CONFINEMENT'].sub_layers["CONFINEMENT_BUFFERS"].rel_path, write=True) as buff_lyr, \
            GeopackageLayer(intermediates_gpkg, layer_name=LayerTypes['INTERMEDIATES'].sub_layers["ERROR_POLYLINES"].rel_path, write=True) as dbg_err_lines_lyr, \
            GeopackageLayer(intermediates_gpkg, layer_name=LayerTypes['INTERMEDIATES'].sub_layers["ERROR_POLYGONS"].rel_path, write=True) as dbg_err_polygons_lyr:

        err_count = 0

        for flowline, _counter, progbar in flw_lyr.iterate_features(
                "Generating confinement for flowlines",
                attribute_filter="FCode IN ({0})".format(','.join(
                    [key for key in reach_codes])),
                write_layers=[
                    margins_lyr, raw_lyr, ratio_lyr, dbg_splitpts_lyr,
                    dbg_flwseg_lyr, buff_lyr, conf_buff_split_lyr,
                    dbg_err_lines_lyr, dbg_err_polygons_lyr
                ]):
            # Load Flowline
            flowlineID = int(flowline.GetFieldAsInteger64("NHDPlusID"))

            bankfull_width = flowline.GetField(buffer_field)
            buffer_value = max(bankfull_width, min_buffer)

            geom_flowline = GeopackageLayer.ogr2shapely(flowline)
            if not geom_flowline.is_valid or geom_flowline.is_empty or geom_flowline.length == 0:
                progbar.erase()
                log.warning("Invalid flowline with id: {}".format(flowlineID))
                continue

            # Generate buffer on each side of the flowline
            geom_buffer = geom_flowline.buffer(
                ((buffer_value * meter_conversion) / 2) *
                bankfull_expansion_factor,
                cap_style=2)

            # inital cleanup if geom is multipolygon
            if geom_buffer.geom_type == "MultiPolygon":
                log.warning(f"Cleaning multipolygon for id{flowlineID}")
                polys = [g for g in geom_buffer if g.intersects(geom_flowline)]
                if len(polys) == 1:
                    geom_buffer = polys[0]

            if not geom_buffer.is_valid or geom_buffer.is_empty or geom_buffer.area == 0 or geom_buffer.geom_type not in [
                    "Polygon"
            ]:
                progbar.erase()
                log.warning("Invalid flowline (after buffering) id: {}".format(
                    flowlineID))
                dbg_err_lines_lyr.create_feature(
                    geom_flowline, {
                        "ErrorProcess": "Generate Buffer",
                        "ErrorMessage": "Invalid Buffer"
                    })
                err_count += 1
                continue

            buff_lyr.create_feature(
                geom_buffer, {
                    "NHDPlusID": flowlineID,
                    "Buffer_Width": buffer_value,
                    "Bankfull_Width": bankfull_width
                })

            # Split the Buffer by the flowline
            geom_buffer_splits = split(
                geom_buffer, geom_flowline
            )  # snap(geom, geom_buffer)) <--shapely does not snap vertex to edge. need to make new function for this to ensure more buffers have 2 split polygons
            # Process only if 2 buffers exist
            if len(geom_buffer_splits) != 2:

                # Lets try to split this again by slightly extending the line
                geom_newline = scale(geom_flowline, 1.1, 1.1, origin='center')
                geom_buffer_splits = split(geom_buffer, geom_newline)

                if len(geom_buffer_splits) != 2:
                    # triage the polygon if still cannot split it
                    error_message = f"WARNING: Flowline FID {flowline.GetFID()} | Incorrect number of split buffer polygons: {len(geom_buffer_splits)}"
                    progbar.erase()
                    log.warning(error_message)
                    dbg_err_lines_lyr.create_feature(
                        geom_flowline, {
                            "ErrorProcess": "Buffer Split",
                            "ErrorMessage": error_message
                        })
                    err_count += 1
                    if len(geom_buffer_splits) > 1:
                        for geom in geom_buffer_splits:
                            dbg_err_polygons_lyr.create_feature(
                                geom, {
                                    "ErrorProcess": "Buffer Split",
                                    "ErrorMessage": error_message
                                })
                    else:
                        dbg_err_polygons_lyr.create_feature(
                            geom_buffer_splits, {
                                "ErrorProcess": "Buffer Split",
                                "ErrorMessage": error_message
                            })
                    continue

            # Generate point to test side of flowline
            geom_offset = geom_flowline.parallel_offset(offset, "left")
            if not geom_offset.is_valid or geom_offset.is_empty or geom_offset.length == 0:
                progbar.erase()
                log.warning("Invalid flowline (after offset) id: {}".format(
                    flowlineID))
                err_count += 1
                dbg_err_lines_lyr.create_feature(
                    geom_flowline, {
                        "ErrorProcess":
                        "Offset Error",
                        "ErrorMessage":
                        "Invalid flowline (after offset) id: {}".format(
                            flowlineID)
                    })
                continue

            geom_side_point = geom_offset.interpolate(0.5, True)

            # Store output segements
            lgeoms_right_confined_flowline_segments = []
            lgeoms_left_confined_flowline_segments = []

            for geom_side in geom_buffer_splits:

                # Identify side of flowline
                side = "LEFT" if geom_side.contains(
                    geom_side_point) else "RIGHT"

                # Save the polygon
                conf_buff_split_lyr.create_feature(
                    geom_side, {
                        "Side": side,
                        "NHDPlusID": flowlineID,
                        "Buffer_Width": buffer_value,
                        "Bankfull_Width": bankfull_width
                    })

                # Generate Confining margins
                geom_confined_margins = geom_confining_polygon.boundary.intersection(
                    geom_side)  # make sure intersection splits lines
                if geom_confined_margins.is_empty:
                    continue

                # Multilinestring to individual linestrings
                lines = [
                    line for line in geom_confined_margins
                ] if geom_confined_margins.geom_type == 'MultiLineString' else [
                    geom_confined_margins
                ]
                for line in lines:
                    margins_lyr.create_feature(
                        line, {
                            "Side": side,
                            "NHDPlusID": flowlineID,
                            "ApproxLeng": line.length / meter_conversion
                        })

                    # Split flowline by Near Geometry
                    pt_start = nearest_points(Point(line.coords[0]),
                                              geom_flowline)[1]
                    pt_end = nearest_points(Point(line.coords[-1]),
                                            geom_flowline)[1]

                    for point in [pt_start, pt_end]:
                        dbg_splitpts_lyr.create_feature(
                            point, {
                                "Side": side,
                                "NHDPlusID": flowlineID
                            })

                    distance_sorted = sorted([
                        geom_flowline.project(pt_start),
                        geom_flowline.project(pt_end)
                    ])
                    segment = substring(geom_flowline, distance_sorted[0],
                                        distance_sorted[1])

                    # Store the segment by flowline side
                    if segment.is_valid and segment.geom_type in [
                            "LineString", "MultiLineString"
                    ]:
                        if side == "LEFT":
                            lgeoms_left_confined_flowline_segments.append(
                                segment)
                        else:
                            lgeoms_right_confined_flowline_segments.append(
                                segment)

                        dbg_flwseg_lyr.create_feature(segment, {
                            "Side": side,
                            "NHDPlusID": flowlineID
                        })

            # Raw Confinement Output
            # Prepare flowline splits
            splitpoints = [
                Point(x, y)
                for line in lgeoms_left_confined_flowline_segments +
                lgeoms_right_confined_flowline_segments for x, y in line.coords
            ]
            cut_distances = sorted(
                list(
                    set([
                        geom_flowline.project(point) for point in splitpoints
                    ])))
            lgeoms_flowlines_split = []
            current_line = geom_flowline
            cumulative_distance = 0.0
            while len(cut_distances) > 0:
                distance = cut_distances.pop(0) - cumulative_distance
                if not distance == 0.0:
                    outline = cut(current_line, distance)
                    if len(outline) == 1:
                        current_line = outline[0]
                    else:
                        current_line = outline[1]
                        lgeoms_flowlines_split.append(outline[0])
                    cumulative_distance = cumulative_distance + distance
            lgeoms_flowlines_split.append(current_line)

            # Confined Segments
            lgeoms_confined_left_split = select_geoms_by_intersection(
                lgeoms_flowlines_split,
                lgeoms_left_confined_flowline_segments,
                buffer=selection_buffer)
            lgeoms_confined_right_split = select_geoms_by_intersection(
                lgeoms_flowlines_split,
                lgeoms_right_confined_flowline_segments,
                buffer=selection_buffer)

            lgeoms_confined_left = select_geoms_by_intersection(
                lgeoms_confined_left_split,
                lgeoms_confined_right_split,
                buffer=selection_buffer,
                inverse=True)
            lgeoms_confined_right = select_geoms_by_intersection(
                lgeoms_confined_right_split,
                lgeoms_confined_left_split,
                buffer=selection_buffer,
                inverse=True)

            geom_confined = unary_union(lgeoms_confined_left_split +
                                        lgeoms_confined_right_split)

            # Constricted Segments
            lgeoms_constricted_l = select_geoms_by_intersection(
                lgeoms_confined_left_split,
                lgeoms_confined_right_split,
                buffer=selection_buffer)
            lgeoms_constrcited_r = select_geoms_by_intersection(
                lgeoms_confined_right_split,
                lgeoms_confined_left_split,
                buffer=selection_buffer)
            lgeoms_constricted = []
            for geom in lgeoms_constricted_l + lgeoms_constrcited_r:
                if not any(g.equals(geom) for g in lgeoms_constricted):
                    lgeoms_constricted.append(geom)
            geom_constricted = MultiLineString(lgeoms_constricted)

            # Unconfined Segments
            lgeoms_unconfined = select_geoms_by_intersection(
                lgeoms_flowlines_split,
                lgeoms_confined_left_split + lgeoms_confined_right_split,
                buffer=selection_buffer,
                inverse=True)

            # Save Raw Confinement
            for con_type, geoms in zip(["Left", "Right", "Both", "None"], [
                    lgeoms_confined_left, lgeoms_confined_right,
                    lgeoms_constricted, lgeoms_unconfined
            ]):
                for g in geoms:
                    if g.geom_type == "LineString":
                        raw_lyr.create_feature(
                            g, {
                                "NHDPlusID": flowlineID,
                                "Confinement_Type": con_type,
                                "ApproxLeng": g.length / meter_conversion
                            })
                    elif geoms.geom_type in ["Point", "MultiPoint"]:
                        progbar.erase()
                        log.warning(
                            f"Flowline FID: {flowline.GetFID()} | Point geometry identified generating outputs for Raw Confinement."
                        )
                    else:
                        progbar.erase()
                        log.warning(
                            f"Flowline FID: {flowline.GetFID()} | Unknown geometry identified generating outputs for Raw Confinement."
                        )

            # Calculated Confinement per Flowline
            confinement_ratio = geom_confined.length / geom_flowline.length if geom_confined else 0.0
            constricted_ratio = geom_constricted.length / geom_flowline.length if geom_constricted else 0.0

            # Save Confinement Ratio
            attributes = {
                "NHDPlusID":
                flowlineID,
                "Confinement_Ratio":
                confinement_ratio,
                "Constriction_Ratio":
                constricted_ratio,
                "ApproxLeng":
                geom_flowline.length / meter_conversion,
                "ConfinLeng":
                geom_confined.length /
                meter_conversion if geom_confined else 0.0,
                "ConstrLeng":
                geom_constricted.length /
                meter_conversion if geom_constricted else 0.0
            }

            ratio_lyr.create_feature(geom_flowline, attributes)

    # Write a report

    report = ConfinementReport(output_gpkg, report_path, project)
    report.write()

    progbar.finish()
    log.info(f"Count of Flowline segments with errors: {err_count}")
    log.info('Confinement Finished')
    return
    def test_get_geometry_unary_union(self):
        in_path = os.path.join(datadir, 'WBDHU12.shp')
        # Use this for the clip shape
        clip_path = os.path.join(datadir, 'WBDHU10.shp')

        # This is the whole file unioned
        result_all = vector_ops.get_geometry_unary_union(in_path, 4326)
        # This is one huc12
        result201 = vector_ops.get_geometry_unary_union(
            in_path, 4326, attribute_filter="HUC12 = '170603040201'")
        result202 = vector_ops.get_geometry_unary_union(
            in_path, 4326, attribute_filter="HUC12 = '170603040202'")
        result203 = vector_ops.get_geometry_unary_union(
            in_path, 4326, attribute_filter="HUC12 = '170603040203'")
        result101 = vector_ops.get_geometry_unary_union(
            in_path, 4326, attribute_filter="HUC12 = '170603040101'")
        result102 = vector_ops.get_geometry_unary_union(
            in_path, 4326, attribute_filter="HUC12 = '170603040102'")
        result103 = vector_ops.get_geometry_unary_union(
            in_path, 4326, attribute_filter="HUC12 = '170603040103'")
        # This is every huc12 with the pattern 1706030402%
        result20 = vector_ops.get_geometry_unary_union(
            in_path, 4326, attribute_filter="HUC12 LIKE '1706030402%'")
        result10 = vector_ops.get_geometry_unary_union(
            in_path, 4326, attribute_filter="HUC12 LIKE '1706030401%'")

        self.assertAlmostEqual(result_all.area, 0.06580, 4)
        self.assertAlmostEqual(
            result_all.area, result201.area + result202.area + result203.area +
            result101.area + result102.area + result103.area, 4)

        self.assertAlmostEqual(
            result10.area, result101.area + result102.area + result103.area, 4)
        self.assertAlmostEqual(
            result20.area, result201.area + result202.area + result203.area, 4)

        # Now test with clip_shape enabled
        with ShapefileLayer(clip_path) as clip_lyr:

            # Build a library of shapes to clip
            clip_shapes = {}
            for clip_feat, _counter, _progbar in clip_lyr.iterate_features(
                    "Gettingshapes"):
                huc10 = clip_feat.GetFieldAsString("HUC10")
                clip_shapes[huc10] = GeopackageLayer.ogr2shapely(clip_feat)

            for huc10, clip_shape in clip_shapes.items():
                debug_path = os.path.join(
                    datadir,
                    'test_get_geometry_unary_union_{}.gpkg'.format(huc10))
                buffered_clip_shape = clip_shape.buffer(-0.004)
                # Write the clipping shape
                with GeopackageLayer(debug_path,
                                     'CLIP_{}'.format(huc10),
                                     write=True) as deb_lyr:
                    deb_lyr.create_layer_from_ref(clip_lyr)
                    out_feature = ogr.Feature(deb_lyr.ogr_layer_def)
                    out_feature.SetGeometry(
                        GeopackageLayer.shapely2ogr(buffered_clip_shape))
                    deb_lyr.ogr_layer.CreateFeature(out_feature)

                # This is every huc12 within a single huc 10 unioned
                result_clipped = vector_ops.get_geometry_union(
                    in_path, clip_shape=buffered_clip_shape)

                with ShapefileLayer(in_path) as in_lyr, GeopackageLayer(
                        debug_path, 'result_{}'.format(huc10),
                        write=True) as deb_lyr:
                    deb_lyr.create(in_lyr.ogr_geom_type,
                                   spatial_ref=in_lyr.spatial_ref)
                    out_feature = ogr.Feature(deb_lyr.ogr_layer_def)
                    out_feature.SetGeometry(
                        GeopackageLayer.shapely2ogr(result_clipped))
                    deb_lyr.ogr_layer.CreateFeature(out_feature)

                self.assertAlmostEqual(clip_shape.area, result_clipped.area, 4)
def build_network(flowlines_path: str,
                  flowareas_path: str,
                  out_path: str,
                  epsg: int = None,
                  reach_codes: List[str] = None,
                  waterbodies_path: str = None,
                  waterbody_max_size=None,
                  create_layer: bool = True):
    """[summary]

    Args:
        flowlines_path (str): [description]
        flowareas_path (str): [description]
        out_path (str): [description]
        epsg (int, optional): [description]. Defaults to None.
        reach_codes (List[str], optional): [description]. Defaults to None.
        waterbodies_path (str, optional): [description]. Defaults to None.
        waterbody_max_size ([type], optional): [description]. Defaults to None.
        create_layer (bool, optional): [description]. Defaults to True.

    Returns:
        [type]: [description]
    """

    log = Logger('Build Network')

    log.info("Building network from flow lines {0}".format(flowlines_path))

    if reach_codes:
        for r in reach_codes:
            log.info('Retaining {} reaches with code {}'.format(
                FCodeValues[int(r)], r))
    else:
        log.info('Retaining all reaches. No reach filtering.')

    # Get the transformation required to convert to the target spatial reference
    if epsg is not None:
        with get_shp_or_gpkg(flowareas_path) as flowareas_lyr:
            out_spatial_ref, transform = VectorBase.get_transform_from_epsg(
                flowareas_lyr.spatial_ref, epsg)

    # Process all perennial/intermittment/ephemeral reaches first
    attribute_filter = None
    if reach_codes and len(reach_codes) > 0:
        _result = [
            log.info("{0} {1} network features (FCode {2})".format(
                'Retaining', FCodeValues[int(key)], key))
            for key in reach_codes
        ]
        attribute_filter = "FCode IN ({0})".format(','.join(
            [key for key in reach_codes]))

    if create_layer is True:
        with get_shp_or_gpkg(flowlines_path) as flowlines_lyr, get_shp_or_gpkg(
                out_path, write=True) as out_lyr:
            out_lyr.create_layer_from_ref(flowlines_lyr)

    log.info('Processing all reaches')
    process_reaches(flowlines_path,
                    out_path,
                    attribute_filter=attribute_filter)

    # Process artifical paths through small waterbodies
    if waterbodies_path is not None and waterbody_max_size is not None:
        small_waterbodies = get_geometry_unary_union(
            waterbodies_path, epsg,
            'AreaSqKm <= ({0})'.format(waterbody_max_size))
        log.info(
            'Retaining artificial features within waterbody features smaller than {0}km2'
            .format(waterbody_max_size))
        process_reaches(
            flowlines_path,
            out_path,
            transform=transform,
            attribute_filter='FCode = {0}'.format(ARTIFICIAL_REACHES),
            clip_shape=small_waterbodies)

    # Retain artifical paths through flow areas
    if flowareas_path:
        flow_polygons = get_geometry_unary_union(flowareas_path, epsg)
        if flow_polygons:
            log.info('Retaining artificial features within flow area features')
            process_reaches(
                flowlines_path,
                out_path,
                transform=transform,
                attribute_filter='FCode = {0}'.format(ARTIFICIAL_REACHES),
                clip_shape=flow_polygons)

        else:
            log.info('Zero artifical paths to be retained.')

    with get_shp_or_gpkg(out_path) as out_lyr:
        log.info(('{:,} features written to {:}'.format(
            out_lyr.ogr_layer.GetFeatureCount(), out_path)))

    log.info('Process completed successfully.')
    return out_spatial_ref
Пример #6
0
def rs_context(huc, existing_veg, historic_veg, ownership, fair_market,
               ecoregions, prism_folder, output_folder, download_folder,
               scratch_dir, parallel, force_download, meta: Dict[str, str]):
    """

    Download riverscapes context layers for the specified HUC and organize them as a Riverscapes project

    :param huc: Eight, 10 or 12 digit HUC identification number
    :param existing_veg: Path to the existing vegetation conditions raster
    :param historic_veg: Path to the historical vegetation conditions raster
    :param ownership: Path to the national land ownership Shapefile
    :param output_folder: Output location for the riverscapes context project
    :param download_folder: Temporary folder where downloads are cached. This can be shared between rs_context processes
    :param force_download: If false then downloads can be skipped if the files already exist
    :param prism_folder: folder containing PRISM rasters in *.bil format
    :param meta (Dict[str,str]): dictionary of riverscapes metadata key: value pairs
    :return:
    """
    log = Logger("RS Context")
    log.info('Starting RSContext v.{}'.format(cfg.version))

    try:
        int(huc)
    except ValueError:
        raise Exception(
            'Invalid HUC identifier "{}". Must be an integer'.format(huc))

    if not (len(huc) in [4, 8, 10, 12]):
        raise Exception(
            'Invalid HUC identifier. Must be 4, 8, 10 or 12 digit integer')

    safe_makedirs(output_folder)
    safe_makedirs(download_folder)

    # We need a temporary folder for slope rasters, Stitching inputs, intermeditary products, etc.
    scratch_dem_folder = os.path.join(scratch_dir, 'rs_context', huc)
    safe_makedirs(scratch_dem_folder)

    project, realization = create_project(huc, output_folder)
    hydrology_gpkg_path = os.path.join(output_folder,
                                       LayerTypes['HYDROLOGY'].rel_path)

    dem_node, dem_raster = project.add_project_raster(realization,
                                                      LayerTypes['DEM'])
    _node, hill_raster = project.add_project_raster(realization,
                                                    LayerTypes['HILLSHADE'])
    _node, flow_accum = project.add_project_raster(realization,
                                                   LayerTypes['FA'])
    _node, drain_area = project.add_project_raster(realization,
                                                   LayerTypes['DA'])
    hand_node, hand_raster = project.add_project_raster(
        realization, LayerTypes['HAND'])
    _node, slope_raster = project.add_project_raster(realization,
                                                     LayerTypes['SLOPE'])
    _node, existing_clip = project.add_project_raster(realization,
                                                      LayerTypes['EXVEG'])
    _node, historic_clip = project.add_project_raster(realization,
                                                      LayerTypes['HISTVEG'])
    _node, fair_market_clip = project.add_project_raster(
        realization, LayerTypes['FAIR_MARKET'])

    # Download the four digit NHD archive containing the flow lines and watershed boundaries
    log.info('Processing NHD')

    # Incorporate project metadata to the riverscapes project
    if meta is not None:
        project.add_metadata(meta)

    nhd_download_folder = os.path.join(download_folder, 'nhd', huc[:4])
    nhd_unzip_folder = os.path.join(scratch_dir, 'nhd', huc[:4])

    nhd, db_path, huc_name, nhd_url = clean_nhd_data(
        huc, nhd_download_folder, nhd_unzip_folder,
        os.path.join(output_folder, 'hydrology'), cfg.OUTPUT_EPSG, False)

    # Clean up the unzipped files. We won't need them again
    if parallel:
        safe_remove_dir(nhd_unzip_folder)
    project.add_metadata({'Watershed': huc_name})
    boundary = 'WBDHU{}'.format(len(huc))

    # For coarser rasters than the DEM we need to buffer our clip polygon to include enough pixels
    # This shouldn't be too much more data because these are usually integer rasters that are much lower res.
    buffered_clip_path100 = os.path.join(
        hydrology_gpkg_path,
        LayerTypes['HYDROLOGY'].sub_layers['BUFFEREDCLIP100'].rel_path)
    copy_feature_class(nhd[boundary],
                       buffered_clip_path100,
                       epsg=cfg.OUTPUT_EPSG,
                       buffer=100)

    buffered_clip_path500 = os.path.join(
        hydrology_gpkg_path,
        LayerTypes['HYDROLOGY'].sub_layers['BUFFEREDCLIP500'].rel_path)
    copy_feature_class(nhd[boundary],
                       buffered_clip_path500,
                       epsg=cfg.OUTPUT_EPSG,
                       buffer=500)

    # PRISM climate rasters
    mean_annual_precip = None
    bil_files = glob.glob(os.path.join(prism_folder, '**', '*.bil'))
    if (len(bil_files) == 0):
        raise Exception('Could not find any .bil files in the prism folder')
    for ptype in PrismTypes:
        try:
            # Next should always be guarded
            source_raster_path = next(
                x for x in bil_files
                if ptype.lower() in os.path.basename(x).lower())
        except StopIteration:
            raise Exception(
                'Could not find .bil file corresponding to "{}"'.format(ptype))
        _node, project_raster_path = project.add_project_raster(
            realization, LayerTypes[ptype])
        raster_warp(source_raster_path, project_raster_path, cfg.OUTPUT_EPSG,
                    buffered_clip_path500, {"cutlineBlend": 1})

        # Use the mean annual precipitation to calculate bankfull width
        if ptype.lower() == 'ppt':
            polygon = get_geometry_unary_union(nhd[boundary],
                                               epsg=cfg.OUTPUT_EPSG)
            mean_annual_precip = raster_buffer_stats2(
                {1: polygon}, project_raster_path)[1]['Mean']
            log.info('Mean annual precipitation for HUC {} is {} mm'.format(
                huc, mean_annual_precip))
            project.add_metadata(
                {'mean_annual_precipitation_mm': str(mean_annual_precip)})

            calculate_bankfull_width(nhd['NHDFlowline'], mean_annual_precip)

    # Add the DB record to the Project XML
    db_lyr = RSLayer('NHD Tables', 'NHDTABLES', 'SQLiteDB',
                     os.path.relpath(db_path, output_folder))
    sqlite_el = project.add_dataset(realization, db_path, db_lyr, 'SQLiteDB')
    project.add_metadata({'origin_url': nhd_url}, sqlite_el)

    # Add any results to project XML
    for name, file_path in nhd.items():
        lyr_obj = RSLayer(name, name, 'Vector',
                          os.path.relpath(file_path, output_folder))
        vector_nod, _fpath = project.add_project_vector(realization, lyr_obj)
        project.add_metadata({'origin_url': nhd_url}, vector_nod)

    states = get_nhd_states(nhd[boundary])

    # Download the NTD archive containing roads and rail
    log.info('Processing NTD')
    ntd_raw = {}
    ntd_unzip_folders = []
    ntd_urls = get_ntd_urls(states)
    for state, ntd_url in ntd_urls.items():
        ntd_download_folder = os.path.join(download_folder, 'ntd',
                                           state.lower())
        ntd_unzip_folder = os.path.join(
            scratch_dir, 'ntd', state.lower(), 'unzipped'
        )  # a little awkward but I need a folder for this and this was the best name I could find
        ntd_raw[state] = download_shapefile_collection(ntd_url,
                                                       ntd_download_folder,
                                                       ntd_unzip_folder,
                                                       force_download)
        ntd_unzip_folders.append(ntd_unzip_folder)

    ntd_clean = clean_ntd_data(ntd_raw, nhd['NHDFlowline'], nhd[boundary],
                               os.path.join(output_folder, 'transportation'),
                               cfg.OUTPUT_EPSG)

    # clean up the NTD Unzip folder. We won't need it again
    if parallel:
        for unzip_path in ntd_unzip_folders:
            safe_remove_dir(unzip_path)

    # Write transportation layers to project file
    log.info('Write transportation layers to project file')

    # Add any results to project XML
    for name, file_path in ntd_clean.items():
        lyr_obj = RSLayer(name, name, 'Vector',
                          os.path.relpath(file_path, output_folder))
        ntd_node, _fpath = project.add_project_vector(realization, lyr_obj)
        project.add_metadata({**ntd_urls}, ntd_node)

    # Download the HAND raster
    huc6 = huc[0:6]
    hand_download_folder = os.path.join(download_folder, 'hand')
    _hpath, hand_url = download_hand(huc6,
                                     cfg.OUTPUT_EPSG,
                                     hand_download_folder,
                                     nhd[boundary],
                                     hand_raster,
                                     warp_options={"cutlineBlend": 1})
    project.add_metadata({'origin_url': hand_url}, hand_node)

    # download contributing DEM rasters, mosaic and reproject into compressed GeoTIF
    ned_download_folder = os.path.join(download_folder, 'ned')
    ned_unzip_folder = os.path.join(scratch_dir, 'ned')
    dem_rasters, urls = download_dem(nhd[boundary], cfg.OUTPUT_EPSG, 0.01,
                                     ned_download_folder, ned_unzip_folder,
                                     force_download)

    need_dem_rebuild = force_download or not os.path.exists(dem_raster)
    if need_dem_rebuild:
        raster_vrt_stitch(dem_rasters,
                          dem_raster,
                          cfg.OUTPUT_EPSG,
                          clip=nhd[boundary],
                          warp_options={"cutlineBlend": 1})
        verify_areas(dem_raster, nhd[boundary])

    # Calculate slope rasters seperately and then stitch them
    slope_parts = []
    hillshade_parts = []

    need_slope_build = need_dem_rebuild or not os.path.isfile(slope_raster)
    need_hs_build = need_dem_rebuild or not os.path.isfile(hill_raster)

    project.add_metadata(
        {
            'num_rasters': str(len(urls)),
            'origin_urls': json.dumps(urls)
        }, dem_node)

    for dem_r in dem_rasters:
        slope_part_path = os.path.join(
            scratch_dem_folder,
            'SLOPE__' + os.path.basename(dem_r).split('.')[0] + '.tif')
        hs_part_path = os.path.join(
            scratch_dem_folder,
            'HS__' + os.path.basename(dem_r).split('.')[0] + '.tif')
        slope_parts.append(slope_part_path)
        hillshade_parts.append(hs_part_path)

        if force_download or need_dem_rebuild or not os.path.exists(
                slope_part_path):
            gdal_dem_geographic(dem_r, slope_part_path, 'slope')
            need_slope_build = True

        if force_download or need_dem_rebuild or not os.path.exists(
                hs_part_path):
            gdal_dem_geographic(dem_r, hs_part_path, 'hillshade')
            need_hs_build = True

    if need_slope_build:
        raster_vrt_stitch(slope_parts,
                          slope_raster,
                          cfg.OUTPUT_EPSG,
                          clip=nhd[boundary],
                          clean=parallel,
                          warp_options={"cutlineBlend": 1})
        verify_areas(slope_raster, nhd[boundary])
    else:
        log.info('Skipping slope build because nothing has changed.')

    if need_hs_build:
        raster_vrt_stitch(hillshade_parts,
                          hill_raster,
                          cfg.OUTPUT_EPSG,
                          clip=nhd[boundary],
                          clean=parallel,
                          warp_options={"cutlineBlend": 1})
        verify_areas(hill_raster, nhd[boundary])
    else:
        log.info('Skipping hillshade build because nothing has changed.')

    # Remove the unzipped rasters. We won't need them anymore
    if parallel:
        safe_remove_dir(ned_unzip_folder)

    # Calculate flow accumulation raster based on the DEM
    log.info('Running flow accumulation and converting to drainage area.')
    flow_accumulation(dem_raster, flow_accum, dinfinity=False, pitfill=True)
    flow_accum_to_drainage_area(flow_accum, drain_area)

    # Clip and re-project the existing and historic vegetation
    log.info('Processing existing and historic vegetation rasters.')
    clip_vegetation(buffered_clip_path100, existing_veg, existing_clip,
                    historic_veg, historic_clip, cfg.OUTPUT_EPSG)

    log.info('Process the Fair Market Value Raster.')
    raster_warp(fair_market,
                fair_market_clip,
                cfg.OUTPUT_EPSG,
                clip=buffered_clip_path500,
                warp_options={"cutlineBlend": 1})

    # Clip the landownership Shapefile to a 10km buffer around the watershed boundary
    own_path = os.path.join(output_folder, LayerTypes['OWNERSHIP'].rel_path)
    project.add_dataset(realization, own_path, LayerTypes['OWNERSHIP'],
                        'Vector')
    clip_ownership(nhd[boundary], ownership, own_path, cfg.OUTPUT_EPSG, 10000)

    #######################################################
    # Segmentation
    #######################################################

    # For now let's just make a copy of the NHD FLowlines
    tmr = Timer()
    rs_segmentation(nhd['NHDFlowline'], ntd_clean['Roads'], ntd_clean['Rail'],
                    own_path, hydrology_gpkg_path, SEGMENTATION['Max'],
                    SEGMENTATION['Min'], huc)
    log.debug('Segmentation done in {:.1f} seconds'.format(tmr.ellapsed()))
    project.add_project_geopackage(realization, LayerTypes['HYDROLOGY'])

    # Add Bankfull Buffer Polygons
    bankfull_path = os.path.join(
        hydrology_gpkg_path,
        LayerTypes['HYDROLOGY'].sub_layers['BANKFULL_CHANNEL'].rel_path)
    bankfull_buffer(
        os.path.join(hydrology_gpkg_path,
                     LayerTypes['HYDROLOGY'].sub_layers['NETWORK'].rel_path),
        cfg.OUTPUT_EPSG,
        bankfull_path,
    )

    # TODO Add nhd/bankfull union when merge feature classes in vector.ops works with Geopackage layers
    # bankfull_nhd_path = os.path.join(hydrology_gpkg_path, LayerTypes['HYDROLOGY'].sub_layers['COMPOSITE_CHANNEL_AREA'].rel_path)
    # clip_path = os.path.join(hydrology_gpkg_path, LayerTypes['HYDROLOGY'].sub_layers['BUFFEREDCLIP500'].rel_path)
    # bankfull_nhd_area(bankfull_path, nhd['NHDArea'], clip_path, cfg.OUTPUT_EPSG, hydrology_gpkg_path, LayerTypes['HYDROLOGY'].sub_layers['COMPOSITE_CHANNEL_AREA'].rel_path)

    # Filter the ecoregions Shapefile to only include attributes that intersect with our HUC
    eco_path = os.path.join(output_folder, 'ecoregions', 'ecoregions.shp')
    project.add_dataset(realization, eco_path, LayerTypes['ECOREGIONS'],
                        'Vector')
    filter_ecoregions(nhd[boundary], ecoregions, eco_path, cfg.OUTPUT_EPSG,
                      10000)

    report_path = os.path.join(project.project_dir,
                               LayerTypes['REPORT'].rel_path)
    project.add_report(realization, LayerTypes['REPORT'], replace=True)

    report = RSContextReport(report_path, project, output_folder)
    report.write()

    log.info('Process completed successfully.')
    return {
        'DEM': dem_raster,
        'Slope': slope_raster,
        'ExistingVeg': existing_veg,
        'HistoricVeg': historic_veg,
        'NHD': nhd
    }
Пример #7
0
def floodplain_connectivity(vbet_network: Path,
                            vbet_polygon: Path,
                            roads: Path,
                            railroads: Path,
                            output_dir: Path,
                            debug_gpkg: Path = None):
    """[summary]

    Args:
        vbet_network (Path): Filtered Flowline network used to generate VBET. Final selection is based on this intersection.
        vbet_polygon (Path): Vbet polygons with clipped NHD Catchments
        roads (Path): Road network
        railroads (Path): railroad network
        out_polygon (Path): Output path and layer name for floodplain polygons
        debug_gpkg (Path, optional): geopackage for saving debug layers (may substantially increase processing time). Defaults to None.
    """

    log = Logger('Floodplain Connectivity')
    log.info("Starting Floodplain Connectivity Script")

    out_polygon = os.path.join(output_dir, 'fconn.gpkg/outputs')

    # Prepare vbet and catchments
    geom_vbet = get_geometry_unary_union(vbet_polygon)
    geoms_raw_vbet = list(load_geometries(vbet_polygon, None).values())
    listgeoms = []
    for geom in geoms_raw_vbet:
        if geom.geom_type == "MultiPolygon":
            for g in geom:
                listgeoms.append(g)
        else:
            listgeoms.append(geom)
    geoms_vbet = MultiPolygon(listgeoms)

    # Clip Transportation Network by VBET
    log.info("Merging Transportation Networks")
    # merge_feature_classes([roads, railroads], geom_vbet, os.path.join(debug_gpkg, "Transportation")) TODO: error when calling this method
    geom_roads = get_geometry_unary_union(roads)
    geom_railroads = get_geometry_unary_union(railroads)
    geom_transportation = geom_roads.union(
        geom_railroads) if geom_railroads is not None else geom_roads
    log.info("Clipping Transportation Network by VBET")
    geom_transportation_clipped = geom_vbet.intersection(geom_transportation)
    if debug_gpkg:
        quicksave(debug_gpkg, "Clipped_Transportation",
                  geom_transportation_clipped, ogr.wkbLineString)

    # Split Valley Edges at transportation intersections
    log.info("Splitting Valley Edges at transportation network intersections")
    geom_vbet_edges = MultiLineString(
        [geom.exterior for geom in geoms_vbet] +
        [g for geom in geoms_vbet for g in geom.interiors])
    geom_vbet_interior_pts = MultiPoint([
        Polygon(g).representative_point() for geom in geom_vbet
        for g in geom.interiors
    ])

    if debug_gpkg:
        quicksave(debug_gpkg, "Valley_Edges_Raw", geom_vbet_edges,
                  ogr.wkbLineString)

    vbet_splitpoints = []
    vbet_splitlines = []
    counter = 0
    for geom_edge in geom_vbet_edges:
        counter += 1
        log.info('Splitting edge features {}/{}'.format(
            counter, len(geom_vbet_edges)))
        if geom_edge.is_valid:
            if not geom_edge.intersects(geom_transportation):
                vbet_splitlines = vbet_splitlines + [geom_edge]
                continue
            pts = geom_transportation.intersection(geom_edge)
            if pts.is_empty:
                vbet_splitlines = vbet_splitlines + [geom_edge]
                continue
            if isinstance(pts, Point):
                pts = [pts]
            geom_boundaries = [geom_edge]

            progbar = ProgressBar(len(geom_boundaries), 50, "Processing")
            counter = 0
            for pt in pts:
                # TODO: I tried to break this out but I'm not sure
                new_boundaries = []
                for line in geom_boundaries:
                    if line is not None:
                        split_line = line_splitter(line, pt)
                        progbar.total += len(split_line)
                        for new_line in split_line:
                            counter += 1
                            progbar.update(counter)
                            if new_line is not None:
                                new_boundaries.append(new_line)
                geom_boundaries = new_boundaries
                # TODO: Not sure this is having the intended effect
                # geom_boundaries = [new_line for line in geom_boundaries if line is not None for new_line in line_splitter(line, pt) if new_line is not None]
            progbar.finish()
            vbet_splitlines = vbet_splitlines + geom_boundaries
            vbet_splitpoints = vbet_splitpoints + [pt for pt in pts]

    if debug_gpkg:
        quicksave(debug_gpkg, "Split_Points", vbet_splitpoints, ogr.wkbPoint)
        quicksave(debug_gpkg, "Valley_Edges_Split", vbet_splitlines,
                  ogr.wkbLineString)

    # Generate Polygons from lines
    log.info("Generating Floodplain Polygons")
    geom_lines = unary_union(
        vbet_splitlines + [geom_tc for geom_tc in geom_transportation_clipped])
    geoms_areas = [
        geom for geom in polygonize(geom_lines)
        if not any(geom.contains(pt) for pt in geom_vbet_interior_pts)
    ]

    if debug_gpkg:
        quicksave(debug_gpkg, "Split_Polygons", geoms_areas, ogr.wkbPolygon)

    # Select Polygons by flowline intersection
    log.info("Selecting connected floodplains")
    geom_vbet_network = get_geometry_unary_union(vbet_network)
    geoms_connected = []
    geoms_disconnected = []
    progbar = ProgressBar(len(geoms_areas), 50, f"Running polygon selection")
    counter = 0
    for geom in geoms_areas:
        progbar.update(counter)
        counter += 1
        if geom_vbet_network.intersects(geom):
            geoms_connected.append(geom)
        else:
            geoms_disconnected.append(geom)

    log.info("Union connected floodplains")
    geoms_connected_output = [
        geom for geom in list(unary_union(geoms_connected))
    ]
    geoms_disconnected_output = [
        geom for geom in list(unary_union(geoms_disconnected))
    ]

    # Save Outputs
    log.info("Save Floodplain Output")
    with GeopackageLayer(out_polygon, write=True) as out_lyr:
        out_lyr.create_layer(ogr.wkbPolygon, epsg=4326)
        out_lyr.create_field("Connected", ogr.OFTInteger)
        progbar = ProgressBar(
            len(geoms_connected_output) + len(geoms_disconnected_output), 50,
            f"saving {out_lyr.ogr_layer_name} features")
        counter = 0
        for shape in geoms_connected_output:
            progbar.update(counter)
            counter += 1
            out_lyr.create_feature(shape, attributes={"Connected": 1})
        for shape in geoms_disconnected_output:
            progbar.update(counter)
            counter += 1
            out_lyr.create_feature(shape, attributes={"Connected": 0})
Пример #8
0
def rvd(huc: int, flowlines_orig: Path, existing_veg_orig: Path, historic_veg_orig: Path,
        valley_bottom_orig: Path, output_folder: Path, reach_codes: List[str], flow_areas_orig: Path, waterbodies_orig: Path, meta=None):
    """[Generate segmented reaches on flowline network and calculate RVD from historic and existing vegetation rasters

    Args:
        huc (integer): Watershed ID
        flowlines_orig (Path): Segmented flowlines feature layer
        existing_veg_orig (Path): LANDFIRE version 2.00 evt raster, with adjacent xml metadata file
        historic_veg_orig (Path): LANDFIRE version 2.00 bps raster, with adjacent xml metadata file
        valley_bottom_orig (Path): Vbet polygon feature layer
        output_folder (Path): destination folder for project output
        reach_codes (List[int]): NHD reach codes for features to include in outputs
        flow_areas_orig (Path): NHD flow area polygon feature layer
        waterbodies (Path): NHD waterbodies polygon feature layer
        meta (Dict[str,str]): dictionary of riverscapes metadata key: value pairs
    """

    log = Logger("RVD")
    log.info('RVD v.{}'.format(cfg.version))

    try:
        int(huc)
    except ValueError:
        raise Exception('Invalid HUC identifier "{}". Must be an integer'.format(huc))

    if not (len(huc) == 4 or len(huc) == 8):
        raise Exception('Invalid HUC identifier. Must be four digit integer')

    safe_makedirs(output_folder)

    project, _realization, proj_nodes = create_project(huc, output_folder)

    # Incorporate project metadata to the riverscapes project
    if meta is not None:
        project.add_metadata(meta)

    log.info('Adding inputs to project')
    _prj_existing_path_node, prj_existing_path = project.add_project_raster(proj_nodes['Inputs'], LayerTypes['EXVEG'], existing_veg_orig)
    _prj_historic_path_node, prj_historic_path = project.add_project_raster(proj_nodes['Inputs'], LayerTypes['HISTVEG'], historic_veg_orig)

    # TODO: Don't forget the att_filter
    # _prj_flowlines_node, prj_flowlines = project.add_project_geopackage(proj_nodes['Inputs'], LayerTypes['INPUTS'], flowlines, att_filter="\"ReachCode\" Like '{}%'".format(huc))
    # Copy in the vectors we need
    inputs_gpkg_path = os.path.join(output_folder, LayerTypes['INPUTS'].rel_path)
    intermediates_gpkg_path = os.path.join(output_folder, LayerTypes['INTERMEDIATES'].rel_path)
    outputs_gpkg_path = os.path.join(output_folder, LayerTypes['OUTPUTS'].rel_path)

    # Make sure we're starting with empty/fresh geopackages
    GeopackageLayer.delete(inputs_gpkg_path)
    GeopackageLayer.delete(intermediates_gpkg_path)
    GeopackageLayer.delete(outputs_gpkg_path)

    # Copy our input layers and also find the difference in the geometry for the valley bottom
    flowlines_path = os.path.join(inputs_gpkg_path, LayerTypes['INPUTS'].sub_layers['FLOWLINES'].rel_path)
    vbottom_path = os.path.join(inputs_gpkg_path, LayerTypes['INPUTS'].sub_layers['VALLEY_BOTTOM'].rel_path)

    copy_feature_class(flowlines_orig, flowlines_path, epsg=cfg.OUTPUT_EPSG)
    copy_feature_class(valley_bottom_orig, vbottom_path, epsg=cfg.OUTPUT_EPSG)

    with GeopackageLayer(flowlines_path) as flow_lyr:
        # Set the output spatial ref as this for the whole project
        out_srs = flow_lyr.spatial_ref
        meter_conversion = flow_lyr.rough_convert_metres_to_vector_units(1)
        distance_buffer = flow_lyr.rough_convert_metres_to_vector_units(1)

    # Transform issues reading 102003 as espg id. Using sr wkt seems to work, however arcgis has problems loading feature classes with this method...
    raster_srs = ogr.osr.SpatialReference()
    ds = gdal.Open(prj_existing_path, 0)
    raster_srs.ImportFromWkt(ds.GetProjectionRef())
    raster_srs.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER)
    transform_shp_to_raster = VectorBase.get_transform(out_srs, raster_srs)

    gt = ds.GetGeoTransform()
    cell_area = ((gt[1] / meter_conversion) * (-gt[5] / meter_conversion))

    # Create the output feature class fields
    with GeopackageLayer(outputs_gpkg_path, layer_name='ReachGeometry', delete_dataset=True) as out_lyr:
        out_lyr.create_layer(ogr.wkbMultiLineString, spatial_ref=out_srs, options=['FID=ReachID'], fields={
            'GNIS_NAME': ogr.OFTString,
            'ReachCode': ogr.OFTString,
            'TotDASqKm': ogr.OFTReal,
            'NHDPlusID': ogr.OFTReal,
            'WatershedID': ogr.OFTInteger
        })

    metadata = {
        'RVD_DateTime': datetime.datetime.now().isoformat(),
        'Reach_Codes': reach_codes
    }

    # Execute the SQL to create the lookup tables in the RVD geopackage SQLite database
    watershed_name = create_database(huc, outputs_gpkg_path, metadata, cfg.OUTPUT_EPSG, os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', 'database', 'rvd_schema.sql'))
    project.add_metadata({'Watershed': watershed_name})

    geom_vbottom = get_geometry_unary_union(vbottom_path, spatial_ref=raster_srs)

    flowareas_path = None
    if flow_areas_orig:
        flowareas_path = os.path.join(inputs_gpkg_path, LayerTypes['INPUTS'].sub_layers['FLOW_AREA'].rel_path)
        copy_feature_class(flow_areas_orig, flowareas_path, epsg=cfg.OUTPUT_EPSG)
        geom_flow_areas = get_geometry_unary_union(flowareas_path)
        # Difference with existing vbottom
        geom_vbottom = geom_vbottom.difference(geom_flow_areas)
    else:
        del LayerTypes['INPUTS'].sub_layers['FLOW_AREA']

    waterbodies_path = None
    if waterbodies_orig:
        waterbodies_path = os.path.join(inputs_gpkg_path, LayerTypes['INPUTS'].sub_layers['WATERBODIES'].rel_path)
        copy_feature_class(waterbodies_orig, waterbodies_path, epsg=cfg.OUTPUT_EPSG)
        geom_waterbodies = get_geometry_unary_union(waterbodies_path)
        # Difference with existing vbottom
        geom_vbottom = geom_vbottom.difference(geom_waterbodies)
    else:
        del LayerTypes['INPUTS'].sub_layers['WATERBODIES']

    # Add the inputs to the XML
    _nd, _in_gpkg_path, _sublayers = project.add_project_geopackage(proj_nodes['Inputs'], LayerTypes['INPUTS'])

    # Filter the flow lines to just the required features and then segment to desired length
    # TODO: These are brat methods that need to be refactored to use VectorBase layers
    cleaned_path = os.path.join(outputs_gpkg_path, 'ReachGeometry')
    build_network(flowlines_path, flowareas_path, cleaned_path, waterbodies_path=waterbodies_path, epsg=cfg.OUTPUT_EPSG, reach_codes=reach_codes, create_layer=False)

    # Generate Voroni polygons
    log.info("Calculating Voronoi Polygons...")

    # Add all the points (including islands) to the list
    flowline_thiessen_points_groups = centerline_points(cleaned_path, distance_buffer, transform_shp_to_raster)
    flowline_thiessen_points = [pt for group in flowline_thiessen_points_groups.values() for pt in group]
    simple_save([pt.point for pt in flowline_thiessen_points], ogr.wkbPoint, raster_srs, "Thiessen_Points", intermediates_gpkg_path)

    # Exterior is the shell and there is only ever 1
    myVorL = NARVoronoi(flowline_thiessen_points)

    # Generate Thiessen Polys
    myVorL.createshapes()

    # Dissolve by flowlines
    log.info("Dissolving Thiessen Polygons")
    dissolved_polys = myVorL.dissolve_by_property('fid')

    # Clip Thiessen Polys
    log.info("Clipping Thiessen Polygons to Valley Bottom")

    clipped_thiessen = clip_polygons(geom_vbottom, dissolved_polys)

    # Save Intermediates
    simple_save(clipped_thiessen.values(), ogr.wkbPolygon, raster_srs, "Thiessen", intermediates_gpkg_path)
    simple_save(dissolved_polys.values(), ogr.wkbPolygon, raster_srs, "ThiessenPolygonsDissolved", intermediates_gpkg_path)
    simple_save(myVorL.polys, ogr.wkbPolygon, raster_srs, "ThiessenPolygonsRaw", intermediates_gpkg_path)
    _nd, _inter_gpkg_path, _sublayers = project.add_project_geopackage(proj_nodes['Intermediates'], LayerTypes['INTERMEDIATES'])

    # OLD METHOD FOR AUDIT
    # dissolved_polys2 = dissolve_by_points(flowline_thiessen_points_groups, myVorL.polys)
    # simple_save(dissolved_polys2.values(), ogr.wkbPolygon, out_srs, "ThiessenPolygonsDissolved_OLD", intermediates_gpkg_path)

    # Load Vegetation Rasters
    log.info(f"Loading Existing and Historic Vegetation Rasters")
    vegetation = {}
    vegetation["EXISTING"] = load_vegetation_raster(prj_existing_path, outputs_gpkg_path, True, output_folder=os.path.join(output_folder, 'Intermediates'))
    vegetation["HISTORIC"] = load_vegetation_raster(prj_historic_path, outputs_gpkg_path, False, output_folder=os.path.join(output_folder, 'Intermediates'))

    for epoch in vegetation.keys():
        for name in vegetation[epoch].keys():
            if not f"{epoch}_{name}" == "HISTORIC_LUI":
                project.add_project_raster(proj_nodes['Intermediates'], LayerTypes[f"{epoch}_{name}"])

    if vegetation["EXISTING"]["RAW"].shape != vegetation["HISTORIC"]["RAW"].shape:
        raise Exception('Vegetation raster shapes are not equal Existing={} Historic={}. Cannot continue'.format(vegetation["EXISTING"]["RAW"].shape, vegetation["HISTORIC"]["RAW"].shape))

    # Vegetation zone calculations
    riparian_zone_arrays = {}
    riparian_zone_arrays["RIPARIAN_ZONES"] = ((vegetation["EXISTING"]["RIPARIAN"] + vegetation["HISTORIC"]["RIPARIAN"]) > 0) * 1
    riparian_zone_arrays["NATIVE_RIPARIAN_ZONES"] = ((vegetation["EXISTING"]["NATIVE_RIPARIAN"] + vegetation["HISTORIC"]["NATIVE_RIPARIAN"]) > 0) * 1
    riparian_zone_arrays["VEGETATION_ZONES"] = ((vegetation["EXISTING"]["VEGETATED"] + vegetation["HISTORIC"]["VEGETATED"]) > 0) * 1

    # Save Intermediate Rasters
    for name, raster in riparian_zone_arrays.items():
        save_intarr_to_geotiff(raster, os.path.join(output_folder, "Intermediates", f"{name}.tif"), prj_existing_path)
        project.add_project_raster(proj_nodes['Intermediates'], LayerTypes[name])

    # Calculate Riparian Departure per Reach
    riparian_arrays = {f"{epoch.capitalize()}{(name.capitalize()).replace('Native_riparian', 'NativeRiparian')}Mean": array for epoch, arrays in vegetation.items() for name, array in arrays.items() if name in ["RIPARIAN", "NATIVE_RIPARIAN"]}

    # Vegetation Cell Counts
    raw_arrays = {f"{epoch}": array for epoch, arrays in vegetation.items() for name, array in arrays.items() if name == "RAW"}

    # Generate Vegetation Conversions
    vegetation_change = (vegetation["HISTORIC"]["CONVERSION"] - vegetation["EXISTING"]["CONVERSION"])
    save_intarr_to_geotiff(vegetation_change, os.path.join(output_folder, "Intermediates", "Conversion_Raster.tif"), prj_existing_path)
    project.add_project_raster(proj_nodes['Intermediates'], LayerTypes['VEGETATION_CONVERSION'])

    # load conversion types dictionary from database
    conn = sqlite3.connect(outputs_gpkg_path)
    conn.row_factory = dict_factory
    curs = conn.cursor()
    curs.execute('SELECT * FROM ConversionTypes')
    conversion_classifications = curs.fetchall()
    curs.execute('SELECT * FROM vwConversions')
    conversion_ids = curs.fetchall()

    # Split vegetation change classes into binary arrays
    vegetation_change_arrays = {
        c['FieldName']: (vegetation_change == int(c["TypeValue"])) * 1 if int(c["TypeValue"]) in np.unique(vegetation_change) else None
        for c in conversion_classifications
    }

    # Calcuate average and unique cell counts  per reach
    progbar = ProgressBar(len(clipped_thiessen.keys()), 50, "Extracting array values by reach...")
    counter = 0
    discarded = 0
    with rasterio.open(prj_existing_path) as dataset:
        unique_vegetation_counts = {}
        reach_average_riparian = {}
        reach_average_change = {}
        for reachid, poly in clipped_thiessen.items():
            counter += 1
            progbar.update(counter)
            # we can discount a lot of shapes here.
            if not poly.is_valid or poly.is_empty or poly.area == 0 or poly.geom_type not in ["Polygon", "MultiPolygon"]:
                discarded += 1
                continue

            raw_values_unique = {}
            change_values_mean = {}
            riparian_values_mean = {}
            reach_raster = np.ma.masked_invalid(
                features.rasterize(
                    [poly],
                    out_shape=dataset.shape,
                    transform=dataset.transform,
                    all_touched=True,
                    fill=np.nan))
            for raster_name, raster in raw_arrays.items():
                if raster is not None:
                    current_raster = np.ma.masked_array(raster, mask=reach_raster.mask)
                    raw_values_unique[raster_name] = np.unique(np.ma.filled(current_raster, fill_value=0), return_counts=True)
                else:
                    raw_values_unique[raster_name] = []
            for raster_name, raster in riparian_arrays.items():
                if raster is not None:
                    current_raster = np.ma.masked_array(raster, mask=reach_raster.mask)
                    riparian_values_mean[raster_name] = np.ma.mean(current_raster)
                else:
                    riparian_values_mean[raster_name] = 0.0
            for raster_name, raster in vegetation_change_arrays.items():
                if raster is not None:
                    current_raster = np.ma.masked_array(raster, mask=reach_raster.mask)
                    change_values_mean[raster_name] = np.ma.mean(current_raster)
                else:
                    change_values_mean[raster_name] = 0.0
            unique_vegetation_counts[reachid] = raw_values_unique
            reach_average_riparian[reachid] = riparian_values_mean
            reach_average_change[reachid] = change_values_mean

    progbar.finish()

    with SQLiteCon(outputs_gpkg_path) as gpkg:
        # Ensure all reaches are present in the ReachAttributes table before storing RVD output values
        gpkg.curs.execute('INSERT INTO ReachAttributes (ReachID) SELECT ReachID FROM ReachGeometry;')

        errs = 0
        for reachid, epochs in unique_vegetation_counts.items():
            for epoch in epochs.values():
                insert_values = [[reachid, int(vegetationid), float(count * cell_area), int(count)] for vegetationid, count in zip(epoch[0], epoch[1]) if vegetationid != 0]
                try:
                    gpkg.curs.executemany('''INSERT INTO ReachVegetation (
                        ReachID,
                        VegetationID,
                        Area,
                        CellCount)
                        VALUES (?,?,?,?)''', insert_values)
                # Sqlite can't report on SQL errors so we have to print good log messages to help intuit what the problem is
                except sqlite3.IntegrityError as err:
                    # THis is likely a constraint error.
                    errstr = "Integrity Error when inserting records: ReachID: {} VegetationIDs: {}".format(reachid, str(list(epoch[0])))
                    log.error(errstr)
                    errs += 1
                except sqlite3.Error as err:
                    # This is any other kind of error
                    errstr = "SQL Error when inserting records: ReachID: {} VegetationIDs: {} ERROR: {}".format(reachid, str(list(epoch[0])), str(err))
                    log.error(errstr)
                    errs += 1
        if errs > 0:
            raise Exception('Errors were found inserting records into the database. Cannot continue.')
        gpkg.conn.commit()

    # load RVD departure levels from DepartureLevels database table
    with SQLiteCon(outputs_gpkg_path) as gpkg:
        gpkg.curs.execute('SELECT LevelID, MaxRVD FROM DepartureLevels ORDER BY MaxRVD ASC')
        departure_levels = gpkg.curs.fetchall()

    # Calcuate Average Departure for Riparian and Native Riparian
    riparian_departure_values = riparian_departure(reach_average_riparian, departure_levels)
    write_db_attributes(outputs_gpkg_path, riparian_departure_values, departure_type_columns)

    # Add Conversion Code, Type to Vegetation Conversion
    with SQLiteCon(outputs_gpkg_path) as gpkg:
        gpkg.curs.execute('SELECT LevelID, MaxValue, NAME FROM ConversionLevels ORDER BY MaxValue ASC')
        conversion_levels = gpkg.curs.fetchall()
    reach_values_with_conversion_codes = classify_conversions(reach_average_change, conversion_ids, conversion_levels)
    write_db_attributes(outputs_gpkg_path, reach_values_with_conversion_codes, rvd_columns)

    # # Write Output to GPKG table
    # log.info('Insert values to GPKG tables')

    # # TODO move this to write_attirubtes method
    # with get_shp_or_gpkg(outputs_gpkg_path, layer_name='ReachAttributes', write=True, ) as in_layer:
    #     # Create each field and store the name and index in a list of tuples
    #     field_indices = [(field, in_layer.create_field(field, field_type)) for field, field_type in {
    #         "FromConifer": ogr.OFTReal,
    #         "FromDevegetated": ogr.OFTReal,
    #         "FromGrassShrubland": ogr.OFTReal,
    #         "FromDeciduous": ogr.OFTReal,
    #         "NoChange": ogr.OFTReal,
    #         "Deciduous": ogr.OFTReal,
    #         "GrassShrubland": ogr.OFTReal,
    #         "Devegetation": ogr.OFTReal,
    #         "Conifer": ogr.OFTReal,
    #         "Invasive": ogr.OFTReal,
    #         "Development": ogr.OFTReal,
    #         "Agriculture": ogr.OFTReal,
    #         "ConversionCode": ogr.OFTInteger,
    #         "ConversionType": ogr.OFTString}.items()]

    #     for feature, _counter, _progbar in in_layer.iterate_features("Writing Attributes", write_layers=[in_layer]):
    #         reach = feature.GetFID()
    #         if reach not in reach_values_with_conversion_codes:
    #             continue

    #         # Set all the field values and then store the feature
    #         for field, _idx in field_indices:
    #             if field in reach_values_with_conversion_codes[reach]:
    #                 if not reach_values_with_conversion_codes[reach][field]:
    #                     feature.SetField(field, None)
    #                 else:
    #                     feature.SetField(field, reach_values_with_conversion_codes[reach][field])
    #         in_layer.ogr_layer.SetFeature(feature)

    #     # Create each field and store the name and index in a list of tuples
    #     field_indices = [(field, in_layer.create_field(field, field_type)) for field, field_type in {
    #         "EXISTING_RIPARIAN_MEAN": ogr.OFTReal,
    #         "HISTORIC_RIPARIAN_MEAN": ogr.OFTReal,
    #         "RIPARIAN_DEPARTURE": ogr.OFTReal,
    #         "EXISTING_NATIVE_RIPARIAN_MEAN": ogr.OFTReal,
    #         "HISTORIC_NATIVE_RIPARIAN_MEAN": ogr.OFTReal,
    #         "NATIVE_RIPARIAN_DEPARTURE": ogr.OFTReal, }.items()]

    #     for feature, _counter, _progbar in in_layer.iterate_features("Writing Attributes", write_layers=[in_layer]):
    #         reach = feature.GetFID()
    #         if reach not in riparian_departure_values:
    #             continue

    #         # Set all the field values and then store the feature
    #         for field, _idx in field_indices:
    #             if field in riparian_departure_values[reach]:
    #                 if not riparian_departure_values[reach][field]:
    #                     feature.SetField(field, None)
    #                 else:
    #                     feature.SetField(field, riparian_departure_values[reach][field])
    #         in_layer.ogr_layer.SetFeature(feature)

    # with sqlite3.connect(outputs_gpkg_path) as conn:
    #     cursor = conn.cursor()
    #     errs = 0
    #     for reachid, epochs in unique_vegetation_counts.items():
    #         for epoch in epochs.values():
    #             insert_values = [[reachid, int(vegetationid), float(count * cell_area), int(count)] for vegetationid, count in zip(epoch[0], epoch[1]) if vegetationid != 0]
    #             try:
    #                 cursor.executemany('''INSERT INTO ReachVegetation (
    #                     ReachID,
    #                     VegetationID,
    #                     Area,
    #                     CellCount)
    #                     VALUES (?,?,?,?)''', insert_values)
    #             # Sqlite can't report on SQL errors so we have to print good log messages to help intuit what the problem is
    #             except sqlite3.IntegrityError as err:
    #                 # THis is likely a constraint error.
    #                 errstr = "Integrity Error when inserting records: ReachID: {} VegetationIDs: {}".format(reachid, str(list(epoch[0])))
    #                 log.error(errstr)
    #                 errs += 1
    #             except sqlite3.Error as err:
    #                 # This is any other kind of error
    #                 errstr = "SQL Error when inserting records: ReachID: {} VegetationIDs: {} ERROR: {}".format(reachid, str(list(epoch[0])), str(err))
    #                 log.error(errstr)
    #                 errs += 1
    #     if errs > 0:
    #         raise Exception('Errors were found inserting records into the database. Cannot continue.')
    #     conn.commit()

    # Add intermediates and the report to the XML
    # project.add_project_geopackage(proj_nodes['Intermediates'], LayerTypes['INTERMEDIATES']) already
    # added above
    project.add_project_geopackage(proj_nodes['Outputs'], LayerTypes['OUTPUTS'])

    # Add the report to the XML
    report_path = os.path.join(project.project_dir, LayerTypes['REPORT'].rel_path)
    project.add_report(proj_nodes['Outputs'], LayerTypes['REPORT'], replace=True)

    report = RVDReport(report_path, project)
    report.write()

    log.info('RVD complete')
Пример #9
0
def calc_conflict_attributes(flowlines_path, valley_bottom, roads, rail,
                             canals, ownership, buffer_distance_metres,
                             cell_size_meters, epsg, canal_codes,
                             intermediates_gpkg_path):

    log = Logger('Conflict')
    log.info('Calculating conflict attributes')

    # Create union of all reaches and another of the reaches without any canals
    reach_union = get_geometry_unary_union(flowlines_path)
    if canal_codes is None:
        reach_union_no_canals = reach_union
    else:
        reach_union_no_canals = get_geometry_unary_union(
            flowlines_path,
            attribute_filter='FCode NOT IN ({})'.format(','.join(canal_codes)))

    crossin = intersect_geometry_to_layer(intermediates_gpkg_path,
                                          'road_crossings', ogr.wkbMultiPoint,
                                          reach_union, roads, epsg)
    diverts = intersect_geometry_to_layer(intermediates_gpkg_path,
                                          'diversions', ogr.wkbMultiPoint,
                                          reach_union_no_canals, canals, epsg)

    road_vb = intersect_to_layer(intermediates_gpkg_path, valley_bottom, roads,
                                 'road_valleybottom', ogr.wkbMultiLineString,
                                 epsg)
    rail_vb = intersect_to_layer(intermediates_gpkg_path, valley_bottom, rail,
                                 'rail_valleybottom', ogr.wkbMultiLineString,
                                 epsg)

    private = os.path.join(intermediates_gpkg_path, 'private_land')
    copy_feature_class(ownership, private, epsg,
                       "ADMIN_AGEN = 'PVT' OR ADMIN_AGEN = 'UND'")

    # Buffer all reaches (being careful to use the units of the Shapefile)
    reaches = load_geometries(flowlines_path, epsg=epsg)
    with get_shp_or_gpkg(flowlines_path) as lyr:
        buffer_distance = lyr.rough_convert_metres_to_vector_units(
            buffer_distance_metres)
        cell_size = lyr.rough_convert_metres_to_vector_units(cell_size_meters)
        geopackage_path = lyr.filepath

    polygons = {
        reach_id: polyline.buffer(buffer_distance)
        for reach_id, polyline in reaches.items()
    }

    results = {}
    tmp_folder = os.path.join(os.path.dirname(intermediates_gpkg_path),
                              'tmp_conflict')
    distance_from_features(polygons, tmp_folder, reach_union.bounds,
                           cell_size_meters, cell_size, results, road_vb,
                           'Mean', 'iPC_RoadVB')
    distance_from_features(polygons, tmp_folder, reach_union.bounds,
                           cell_size_meters, cell_size, results, crossin,
                           'Mean', 'iPC_RoadX')
    distance_from_features(polygons, tmp_folder, reach_union.bounds,
                           cell_size_meters, cell_size, results, diverts,
                           'Mean', 'iPC_DivPts')
    distance_from_features(polygons, tmp_folder, reach_union.bounds,
                           cell_size_meters, cell_size, results, private,
                           'Mean', 'iPC_Privat')
    distance_from_features(polygons, tmp_folder, reach_union.bounds,
                           cell_size_meters, cell_size, results, rail_vb,
                           'Mean', 'iPC_RailVB')
    distance_from_features(polygons, tmp_folder, reach_union.bounds,
                           cell_size_meters, cell_size, results, canals,
                           'Mean', 'iPC_Canal')
    distance_from_features(polygons, tmp_folder, reach_union.bounds,
                           cell_size_meters, cell_size, results, roads, 'Mean',
                           'iPC_Road')
    distance_from_features(polygons, tmp_folder, reach_union.bounds,
                           cell_size_meters, cell_size, results, rail, 'Mean',
                           'iPC_Rail')

    # Calculate minimum distance to conflict
    min_keys = [
        'iPC_Road', 'iPC_RoadX', 'iPC_RoadVB', 'iPC_Rail', 'iPC_RailVB'
    ]
    for values in results.values():
        values['oPC_Dist'] = min([values[x] for x in min_keys if x in values])

    # Retrieve the agency responsible for administering the land at the midpoint of each reach
    admin_agency(geopackage_path, reaches, ownership, results)

    log.info('Conflict attribute calculation complete')

    # Cleanup temporary feature classes
    safe_remove_dir(tmp_folder)

    return results