Пример #1
0
def load_geometries(in_layer_path: str, id_field: str = None, epsg: int = None, spatial_ref: osr.SpatialReference = None) -> dict:
    """[summary]

    Args:
        in_layer_path (str): [description]
        id_field (str, optional): [description]. Defaults to None.
        epsg (int, optional): [description]. Defaults to None.
        spatial_ref (osr.SpatialReference, optional): [description]. Defaults to None.

    Raises:
        VectorBaseException: [description]

    Returns:
        dict: [description]
    """
    log = Logger('load_geometries')

    if epsg is not None and spatial_ref is not None:
        raise VectorBaseException('Specify either an EPSG or a spatial_ref. Not both')

    with get_shp_or_gpkg(in_layer_path) as in_layer:
        # Determine the transformation if user provides an EPSG
        transform = None
        if epsg is not None:
            _outref, transform = VectorBase.get_transform_from_epsg(in_layer.spatial_ref, epsg)
        elif spatial_ref is not None:
            transform = in_layer.get_transform(in_layer.spatial_ref, spatial_ref)

        features = {}

        for feature, _counter, progbar in in_layer.iterate_features("Loading features"):

            if id_field is None:
                reach = feature.GetFID()
            else:
                reach = feature.GetField(id_field)

            geom = feature.GetGeometryRef()
            geo_type = geom.GetGeometryType()

            new_geom = VectorBase.ogr2shapely(geom, transform=transform)

            if new_geom.is_empty:
                progbar.erase()  # get around the progressbar
                log.warning('Empty feature with FID={} cannot be unioned and will be ignored'.format(feature.GetFID()))
            elif not new_geom.is_valid:
                progbar.erase()  # get around the progressbar
                log.warning('Invalid feature with FID={} cannot be unioned and will be ignored'.format(feature.GetFID()))
            # Filter out zero-length lines
            elif geo_type in VectorBase.LINE_TYPES and new_geom.length == 0:
                progbar.erase()  # get around the progressbar
                log.warning('Zero Length for feature with FID={}'.format(feature.GetFID()))
            # Filter out zero-area polys
            elif geo_type in VectorBase.POLY_TYPES and new_geom.area == 0:
                progbar.erase()  # get around the progressbar
                log.warning('Zero Area for feature with FID={}'.format(feature.GetFID()))
            else:
                features[reach] = new_geom

    return features
Пример #2
0
def include_features(source_layer: VectorBase,
                     out_layer: VectorBase,
                     attribute_filter: str = None,
                     clip_shape: BaseGeometry = None,
                     excluded_fids: list = None):

    included_fids = []
    excluded_fids = [] if excluded_fids is None else excluded_fids
    for feature, _counter, _progbar in source_layer.iterate_features(
            'Including Features',
            write_layers=[out_layer],
            attribute_filter=attribute_filter,
            clip_shape=clip_shape):
        out_feature = ogr.Feature(out_layer.ogr_layer_def)

        if feature.GetFID() not in excluded_fids:

            included_fids.append(feature.GetFID())

            # Add field values from input Layer
            for i in range(0, out_layer.ogr_layer_def.GetFieldCount()):
                out_feature.SetField(
                    out_layer.ogr_layer_def.GetFieldDefn(i).GetNameRef(),
                    feature.GetField(i))

            geom = feature.GetGeometryRef()
            out_feature.SetGeometry(geom.Clone())
            out_layer.ogr_layer.CreateFeature(out_feature)

    return included_fids
Пример #3
0
def load_geoms(in_lines):
    out = []
    with get_shp_or_gpkg(in_lines) as in_lyr:
        for feat, _counter, _progbar in in_lyr.iterate_features(
                "Loading geometry"):
            shapely_geom = VectorBase.ogr2shapely(feat)
            out.append(shapely_geom)

    return out
Пример #4
0
def network_statistics(label: str, vector_layer_path: str):

    log = Logger('network_statistics')
    log.info('Network ShapeFile Summary: {}'.format(vector_layer_path))

    results = {}
    total_length = 0.0
    min_length = None
    max_length = None
    invalid_features = 0
    no_geometry = 0

    with get_shp_or_gpkg(vector_layer_path) as vector_layer:

        # Delete output column from network ShapeFile if it exists and then recreate it
        for fieldidx in range(0, vector_layer.ogr_layer_def.GetFieldCount()):
            results[vector_layer.ogr_layer_def.GetFieldDefn(fieldidx).GetName()] = 0

        for feature, _counter, _progbar in vector_layer.iterate_features("Calculating Stats"):
            geom = feature.GetGeometryRef()

            if geom is None:
                no_geometry += 1
                return

            shapely_obj = VectorBase.ogr2shapely(geom)
            length = shapely_obj.length

            if shapely_obj.is_empty or shapely_obj.is_valid is False:
                invalid_features += 1

            total_length += length
            min_length = length if not min_length or min_length > length else min_length
            max_length = length if not max_length or max_length < length else max_length

            for fieldidx in range(0, vector_layer.ogr_layer_def.GetFieldCount()):
                field = vector_layer.ogr_layer_def.GetFieldDefn(fieldidx).GetName()
                if field not in results:
                    results[field] = 0

                results[field] += 0 if feature.GetField(field) else 1

        features = vector_layer.ogr_layer.GetFeatureCount()
        results['Feature Count'] = features
        results['Invalid Features'] = invalid_features
        results['Features without geometry'] = no_geometry
        results['Min Length'] = min_length
        results['Max Length'] = max_length
        results['Avg Length'] = (total_length / features) if features > 0 and total_length != 0 else 0.0
        results['Total Length'] = total_length

        for key, value in results.items():
            if value > 0:
                log.info('{}, {} with {:,} NULL values'.format(label, key, value))

    return results
Пример #5
0
def get_geometry_union(in_layer_path: str, epsg: int = None,
                       attribute_filter: str = None,
                       clip_shape: BaseGeometry = None,
                       clip_rect: List[float] = None
                       ) -> BaseGeometry:
    """[summary]

    Args:
        in_layer_path (str): [description]
        epsg (int, optional): [description]. Defaults to None.
        attribute_filter (str, optional): [description]. Defaults to None.
        clip_shape (BaseGeometry, optional): [description]. Defaults to None.
        clip_rect (List[double minx, double miny, double maxx, double maxy)]): Iterate over a subset by clipping to a Shapely-ish geometry. Defaults to None.

    Returns:
        BaseGeometry: [description]
    """

    log = Logger('get_geometry_union')

    with get_shp_or_gpkg(in_layer_path) as in_layer:

        transform = None
        if epsg:
            _outref, transform = VectorBase.get_transform_from_epsg(in_layer.spatial_ref, epsg)

        geom = None

        for feature, _counter, progbar in in_layer.iterate_features("Getting geometry union", attribute_filter=attribute_filter, clip_shape=clip_shape, clip_rect=clip_rect):
            if feature.GetGeometryRef() is None:
                progbar.erase()  # get around the progressbar
                log.warning('Feature with FID={} has no geometry. Skipping'.format(feature.GetFID()))
                continue

            new_shape = VectorBase.ogr2shapely(feature, transform=transform)
            try:
                geom = geom.union(new_shape) if geom is not None else new_shape
            except Exception:
                progbar.erase()  # get around the progressbar
                log.warning('Union failed for shape with FID={} and will be ignored'.format(feature.GetFID()))

    return geom
Пример #6
0
def midpoints(in_lines):

    out_points = []
    with get_shp_or_gpkg(in_lines) as in_lyr:
        for feat in in_lyr.iterate_features('Getting Midpoints'):
            geom = feat.GetGeometryRef()
            line = VectorBase.ogr2shapely(geom)
            out_points.append(RiverPoint(line.interpolate(0.5, True)))
            feat = None

    return out_points
Пример #7
0
def centerline_points(
        in_lines: Path,
        distance: float = 0.0,
        transform: Transform = None) -> Dict[int, List[RiverPoint]]:
    """Generates points along each line feature at specified distances from the end as well as quarter and halfway

    Args:
        in_lines (Path): path of shapefile with features
        distance (float, optional): distance from ends to generate points. Defaults to 0.0.
        transform (Transform, optional): coordinate transformation. Defaults to None.

    Returns:
        [type]: [description]
    """
    log = Logger('centerline_points')
    with get_shp_or_gpkg(in_lines) as in_lyr:
        out_group = {}
        ogr_extent = in_lyr.ogr_layer.GetExtent()
        extent = Polygon.from_bounds(ogr_extent[0], ogr_extent[2],
                                     ogr_extent[1], ogr_extent[3])

        for feat, _counter, progbar in in_lyr.iterate_features(
                "Centerline points"):

            line = VectorBase.ogr2shapely(feat, transform)

            fid = feat.GetFID()
            out_points = []
            # Attach the FID in case we need it later
            props = {'fid': fid}

            pts = [
                line.interpolate(distance),
                line.interpolate(0.5, True),
                line.interpolate(-distance)
            ]

            if line.project(line.interpolate(0.25, True)) > distance:
                pts.append(line.interpolate(0.25, True))
                pts.append(line.interpolate(-0.25, True))

            for pt in pts:
                # Recall that interpolation can have multiple solutions due to pythagorean theorem
                # Throw away anything that's not inside our bounds
                if not extent.contains(pt):
                    progbar.erase()
                    log.warning('Point {} is outside of extent: {}'.format(
                        pt.coords[0], ogr_extent))
                out_points.append(RiverPoint(pt, properties=props))

            out_group[int(fid)] = out_points
            feat = None
        return out_group
Пример #8
0
def get_riverpoints(inpath, epsg, attribute_filter=None):
    """[summary]

    Args:
        inpath ([type]): Path to a ShapeFile
        epsg ([type]):  Desired output spatial reference
        attribute_filter ([type], optional): [description]. Defaults to None.

    Returns:
        [type]: List of RiverPoint objects
    """

    log = Logger('get_riverpoints')
    points = []

    with get_shp_or_gpkg(inpath) as in_lyr:

        _out_spatial_ref, transform = get_transform_from_epsg(
            in_lyr.spatial_ref, epsg)

        for feat, _counter, progbar in in_lyr.iterate_features(
                'Getting points for use in Thiessen',
                attribute_filter=attribute_filter):

            new_geom = feat.GetGeometryRef()

            if new_geom is None:
                progbar.erase()  # get around the progressbar
                log.warning(
                    'Feature with FID={} has no geometry. Skipping'.format(
                        feat.GetFID()))
                continue

            new_geom.Transform(transform)
            new_shape = VectorBase.ogr2shapely(new_geom)

            if new_shape.type == 'Polygon':
                new_shape = MultiPolygon([new_shape])

            for poly in new_shape:
                # Exterior is the shell and there is only ever 1
                for pt in list(poly.exterior.coords):
                    points.append(RiverPoint(pt, interior=False))

                # Now we consider interiors. NB: Interiors are only qualifying islands in this case
                for idx, island in enumerate(poly.interiors):
                    for pt in list(island.coords):
                        points.append(RiverPoint(pt, interior=True,
                                                 island=idx))

    return points
Пример #9
0
def merge_feature_classes(feature_class_paths: List[str], boundary: BaseGeometry, out_layer_path: str):
    """[summary]

    Args:
        feature_class_paths (List[str]): [description]
        boundary (BaseGeometry): [description]
        out_layer_path (str): [description]
    """
    log = Logger('merge_feature_classes')
    log.info('Merging {} feature classes.'.format(len(feature_class_paths)))

    with get_shp_or_gpkg(out_layer_path, write=True) as out_layer:
        fccount = 0
        for in_layer_path in feature_class_paths:
            fccount += 1
            log.info("Merging feature class {}/{}".format(fccount, len(feature_class_paths)))

            with get_shp_or_gpkg(in_layer_path) as in_layer:
                in_layer.SetSpatialFilter(VectorBase.shapely2ogr(boundary))

                # First input spatial ref sets the SRS for the output file
                transform = in_layer.get_transform(out_layer)

                for i in range(in_layer.ogr_layer_def.GetFieldCount()):
                    in_field_def = in_layer.ogr_layer_def.GetFieldDefn(i)
                    # Only create fields if we really don't have them
                    # NOTE: THIS ASSUMES ALL FIELDS OF THE SAME NAME HAVE THE SAME TYPE
                    if out_layer.ogr_layer_def.GetFieldIndex(in_field_def.GetName()) == -1:
                        out_layer.ogr_layer.CreateField(in_field_def)

                log.info('Processing feature: {}/{}'.format(fccount, len(feature_class_paths)))

                for feature, _counter, progbar in in_layer.iterate_features('Processing feature'):
                    geom = feature.GetGeometryRef()

                    if geom is None:
                        progbar.erase()  # get around the progressbar
                        log.warning('Feature with FID={} has no geometry. Skipping'.format(feature.GetFID()))
                        continue

                    geom.Transform(transform)
                    out_feature = ogr.Feature(out_layer.ogr_layer_def)

                    for i in range(in_layer.ogr_layer_def.GetFieldCount()):
                        out_feature.SetField(out_layer.ogr_layer_def.GetFieldDefn(i).GetNameRef(), feature.GetField(i))

                    out_feature.SetGeometry(geom)
                    out_layer.ogr_layer.CreateFeature(out_feature)

    log.info('Merge complete.')
    return fccount
Пример #10
0
def buffer_by_field(in_layer_path: str, out_layer_path, field: str, epsg: int = None, min_buffer=None, centered=False) -> None:
    """generate buffered polygons by value in field

    Args:
        flowlines (str): feature class of line features to buffer
        field (str): field with buffer value
        epsg (int): output srs
        min_buffer: use this buffer value for field values that are less than this

    Returns:
        geometry: unioned polygon geometry of buffered lines
    """
    log = Logger('buffer_by_field')

    with get_shp_or_gpkg(out_layer_path, write=True) as out_layer, get_shp_or_gpkg(in_layer_path) as in_layer:
        conversion = in_layer.rough_convert_metres_to_vector_units(1)

        # Add input Layer Fields to the output Layer if it is the one we want
        out_layer.create_layer(ogr.wkbPolygon, epsg=epsg, fields=in_layer.get_fields())

        transform = VectorBase.get_transform(in_layer.spatial_ref, out_layer.spatial_ref)

        factor = 0.5 if centered else 1.0

        for feature, _counter, progbar in in_layer.iterate_features('Buffering features', write_layers=[out_layer]):
            geom = feature.GetGeometryRef()

            if geom is None:
                progbar.erase()  # get around the progressbar
                log.warning('Feature with FID={} has no geometry. Skipping'.format(feature.GetFID()))
                continue

            buffer_dist = feature.GetField(field) * conversion * factor
            geom.Transform(transform)
            geom_buffer = geom.Buffer(buffer_dist if buffer_dist > min_buffer else min_buffer)

            # Create output Feature
            out_feature = ogr.Feature(out_layer.ogr_layer_def)
            out_feature.SetGeometry(geom_buffer)

            # Add field values from input Layer
            for i in range(0, out_layer.ogr_layer_def.GetFieldCount()):
                out_feature.SetField(out_layer.ogr_layer_def.GetFieldDefn(i).GetNameRef(), feature.GetField(i))

            out_layer.ogr_layer.CreateFeature(out_feature)
            out_feature = None
Пример #11
0
def rasterize(in_lyr_path, out_raster_path, template_path):
    """Rasterizing an input 

    Args:
        in_lyr_path ([type]): [description]
        out_raster_ ([type]): [description]
        template_path ([type]): [description]
    """
    log = Logger('VBETRasterize')
    ds_path, lyr_path = VectorBase.path_sorter(in_lyr_path)

    progbar = ProgressBar(100, 50, "Rasterizing ")

    with rasterio.open(template_path) as raster:
        t = raster.transform
        raster_bounds = raster.bounds

    def poly_progress(progress, _msg, _data):
        progbar.update(int(progress * 100))

    # Rasterize the features (roads, rail etc) and calculate a raster of Euclidean distance from these features
    progbar.update(0)

    # Rasterize the polygon to a temporary file
    with TempRaster('vbet_rasterize') as tempfile:
        log.debug('Temporary file: {}'.format(tempfile.filepath))
        gdal.Rasterize(
            tempfile.filepath,
            ds_path,
            layers=[lyr_path],
            xRes=t[0],
            yRes=t[4],
            burnValues=1,
            outputType=gdal.GDT_Int16,
            creationOptions=['COMPRESS=LZW'],
            # outputBounds --- assigned output bounds: [minx, miny, maxx, maxy]
            outputBounds=[
                raster_bounds.left, raster_bounds.bottom, raster_bounds.right,
                raster_bounds.top
            ],
            callback=poly_progress)
        progbar.finish()

        # Now mask the output correctly
        mask_rasters_nodata(tempfile.filepath, template_path, out_raster_path)
Пример #12
0
def load_geometries(database, target_srs=None, where_clause=None):

    transform = None
    if target_srs:
        db_srs = get_db_srs(database)
        # https://github.com/OSGeo/gdal/issues/1546
        target_srs.SetAxisMappingStrategy(db_srs.GetAxisMappingStrategy())
        transform = osr.CoordinateTransformation(db_srs, target_srs)

    conn = sqlite3.connect(database)
    curs = conn.execute('SELECT ReachID, Geometry FROM Reaches {}'.format('WHERE {}'.format(where_clause) if where_clause else ''))
    reaches = {}

    for row in curs.fetchall():
        geom = ogr.CreateGeometryFromJson(row[1])
        if transform:
            geom.Transform(transform)
        reaches[row[0]] = VectorBase.ogr2shapely(geom)
    return reaches
def calc_max_drainage(huc_search, precip_raster, wbd, bankfull):
    """ Temporary script to calculate the BRAT maximum drainage area threshold
        Takes HUC8 watershed boundary polygons and finds the mean annual
        precipitation then uses the inverted Beechi and Imaki formula
        to derive drainage area at the specified constant bankfull width.

    Args:
        huc_search (str): feature layer attribute filter string (e.g. '17%' for hydro region 17)
        precip_raster (str): path to the national PRISM annual precipitation raster
        wbd (str): file path to the national watershed boundary dataset (WBD) file geodatabase
        bankfull (float): bankfull width at which drainage area threshold is calculated
    """

    # open watershed boundary file geodatabase
    driver = ogr.GetDriverByName('OpenFileGDB')
    data_source = driver.Open(wbd, 0)
    wbd_layer = data_source.GetLayer('WBDHU8')
    wbd_layer.SetAttributeFilter('HUC8 LIKE \'{}\''.format(huc_search))

    # Need to convert watersheds to the PESG:4269 used by the PRISM raster
    _srs, transform = get_transform_from_epsg(wbd_layer.GetSpatialRef(), 4269)

    watersheds = {}
    for feature in wbd_layer:
        huc = feature.GetField('HUC8')
        states = feature.GetField('states')
        if 'cn' not in states.lower():
            watersheds[huc] = VectorBase.ogr2shapely(feature, transform)

    stats = raster_buffer_stats2(watersheds, precip_raster)

    for huc, stat in stats.items():

        # PRISM precipitation is in mm but Beechie and Imaki require it in cm
        mean_precip_cm = stat['Mean'] / 10.0

        max_drain = pow(bankfull / (0.177) / (pow(mean_precip_cm, 0.453)),
                        1 / 0.397)
        print(
            "UPDATE watersheds SET max_drainage = {} WHERE watershed_id = '{}' AND ((max_drainage IS NULL) OR (max_drainage = 0)); -- {}"
            .format(int(max_drain), huc, mean_precip_cm))
Пример #14
0
def centerline_vertex_between_distance(in_lines, distance=0.0):

    out_group = []
    with get_shp_or_gpkg(in_lines) as in_lyr:
        for feat, _counter, _progbar in in_lyr.iterate_features(
                "Centerline points between distance"):
            line = VectorBase.ogr2shapely(feat)

            out_points = []
            out_points.append(RiverPoint(line.interpolate(distance)))
            out_points.append(RiverPoint(line.interpolate(-distance)))

            max_distance = line.length - distance

            for vertex in list(line.coords):
                test_dist = line.project(Point(vertex))
                if test_dist > distance and test_dist < max_distance:
                    out_points.append(RiverPoint(Point(vertex)))
            out_group.append(out_points)
            feat = None
    return out_group
Пример #15
0
def hand_rasterize(in_lyr_path: str, template_dem_path: str,
                   out_raster_path: str):
    log = Logger('hand_rasterize')

    ds_path, lyr_path = VectorBase.path_sorter(in_lyr_path)

    g = gdal.Open(template_dem_path)
    geo_t = g.GetGeoTransform()
    width, height = g.RasterXSize, g.RasterYSize
    xmin = min(geo_t[0], geo_t[0] + width * geo_t[1])
    xmax = max(geo_t[0], geo_t[0] + width * geo_t[1])
    ymin = min(geo_t[3], geo_t[3] + geo_t[-1] * height)
    ymax = max(geo_t[3], geo_t[3] + geo_t[-1] * height)
    # Close our dataset
    g = None

    progbar = ProgressBar(100, 50, "Rasterizing for HAND")

    def poly_progress(progress, _msg, _data):
        progbar.update(int(progress * 100))

    # https://gdal.org/programs/gdal_rasterize.html
    # https://gdal.org/python/osgeo.gdal-module.html#RasterizeOptions
    gdal.Rasterize(
        out_raster_path,
        ds_path,
        layers=[lyr_path],
        height=height,
        width=width,
        burnValues=1,
        outputType=gdal.GDT_CFloat32,
        creationOptions=['COMPRESS=LZW'],
        # outputBounds --- assigned output bounds: [minx, miny, maxx, maxy]
        outputBounds=[xmin, ymin, xmax, ymax],
        callback=poly_progress)
    progbar.finish()

    # Rasterize the features (roads, rail etc) and calculate a raster of Euclidean distance from these features
    progbar.update(0)
Пример #16
0
def simple_save(list_geoms, ogr_type, srs, layer_name, gpkg_path):
    with GeopackageLayer(gpkg_path, layer_name, write=True) as lyr:
        lyr.create_layer(ogr_type, spatial_ref=srs)

        progbar = ProgressBar(len(list_geoms), 50, f"Saving {gpkg_path}/{layer_name}")
        counter = 0
        progbar.update(counter)
        lyr.ogr_layer.StartTransaction()
        for geom in list_geoms:
            counter += 1
            progbar.update(counter)

            feature = ogr.Feature(lyr.ogr_layer_def)
            geom_ogr = VectorBase.shapely2ogr(geom)
            feature.SetGeometry(geom_ogr)
            # if attributes:
            #     for field, value in attributes.items():
            #         feature.SetField(field, value)
            lyr.ogr_layer.CreateFeature(feature)
            feature = None

        progbar.finish()
        lyr.ogr_layer.CommitTransaction()
Пример #17
0
def get_geometry_unary_union(in_layer_path: str, epsg: int = None, spatial_ref: osr.SpatialReference = None,
                             attribute_filter: str = None,
                             clip_shape: BaseGeometry = None,
                             clip_rect: List[float] = None
                             ) -> BaseGeometry:
    """Load all features from a ShapeFile and union them together into a single geometry

    Args:
        in_layer_path (str): path to layer
        epsg (int, optional): EPSG to project to. Defaults to None.
        spatial_ref (osr.SpatialReference, optional): Spatial Ref to project to. Defaults to None.
        attribute_filter (str, optional): Filter to a set of attributes. Defaults to None.
        clip_shape (BaseGeometry, optional): Clip to a specified shape. Defaults to None.
        clip_rect (List[double minx, double miny, double maxx, double maxy)]): Iterate over a subset by clipping to a Shapely-ish geometry. Defaults to None.

    Raises:
        VectorBaseException: [description]

    Returns:
        BaseGeometry: [description]
    """
    log = Logger('get_geometry_unary_union')

    if epsg is not None and spatial_ref is not None:
        raise VectorBaseException('Specify either an EPSG or a spatial_ref. Not both')

    with get_shp_or_gpkg(in_layer_path) as in_layer:
        transform = None
        if epsg is not None:
            _outref, transform = VectorBase.get_transform_from_epsg(in_layer.spatial_ref, epsg)
        elif spatial_ref is not None:
            transform = in_layer.get_transform(in_layer.spatial_ref, spatial_ref)

        geom_list = []

        for feature, _counter, progbar in in_layer.iterate_features("Unary Unioning features", attribute_filter=attribute_filter, clip_shape=clip_shape, clip_rect=clip_rect):
            new_geom = feature.GetGeometryRef()
            geo_type = new_geom.GetGeometryType()

            # We can't union non-valid shapes but sometimes a buffer by 0 can help
            if not new_geom.IsValid():
                progbar.erase()  # get around the progressbar
                log.warning('Invalid shape with FID={} trying the Buffer0 technique...'.format(feature.GetFID()))
                try:
                    new_geom = new_geom.Buffer(0)
                    if not new_geom.IsValid():
                        log.warning('   Still invalid. Skipping this geometry')
                        continue
                except Exception:
                    log.warning('Exception raised during buffer 0 technique. skipping this file')
                    continue

            if new_geom is None:
                progbar.erase()  # get around the progressbar
                log.warning('Feature with FID={} has no geoemtry. Skipping'.format(feature.GetFID()))
            # Filter out zero-length lines
            elif geo_type in VectorBase.LINE_TYPES and new_geom.Length() == 0:
                progbar.erase()  # get around the progressbar
                log.warning('Zero Length for shape with FID={}'.format(feature.GetFID()))
            # Filter out zero-area polys
            elif geo_type in VectorBase.POLY_TYPES and new_geom.Area() == 0:
                progbar.erase()  # get around the progressbar
                log.warning('Zero Area for shape with FID={}'.format(feature.GetFID()))
            else:
                geom_list.append(VectorBase.ogr2shapely(new_geom, transform))

                # IF we get past a certain size then run the union
                if len(geom_list) >= 500:
                    geom_list = [unary_union(geom_list)]
            new_geom = None

    log.debug('finished iterating with list of size: {}'.format(len(geom_list)))

    if len(geom_list) > 1:
        log.debug('Starting final union of geom_list of size: {}'.format(len(geom_list)))
        # Do a final union to clean up anything that might still be in the list
        geom_union = unary_union(geom_list)
    elif len(geom_list) == 0:
        log.warning('No geometry found to union')
        return None
    else:
        log.debug('FINAL Unioning geom_list of size {}'.format(len(geom_list)))
        geom_union = geom_list[0]
        log.debug('   done')

    print_geom_size(log, geom_union)
    log.debug('Complete')
    # Return a shapely object
    return geom_union
Пример #18
0
def segment_network(inpath: str,
                    outpath: str,
                    interval: float,
                    minimum: float,
                    watershed_id: str,
                    create_layer=False):
    """
    Chop the lines in a polyline feature class at the specified interval unless
    this would create a line less than the minimum in which case the line is not segmented.
    :param inpath: Original network feature class
    :param outpath: Output segmented network feature class
    :param interval: Distance at which to segment each line feature (map units)
    :param minimum: Minimum length below which lines are not segmented (map units)
    :param watershed_id: Give this watershed an id (str)
    :param create_layer: This layer may be created earlier. We can choose to create it. Defaults to false (bool)
    :return: None
    """

    log = Logger('Segment Network')

    if interval <= 0:
        log.info('Skipping segmentation.')
    else:
        log.info(
            'Segmenting network to {}m, with minimum feature length of {}m'.
            format(interval, minimum))
        log.info('Segmenting network from {0}'.format(inpath))

    # NOTE: Remember to always open the 'write' layer first in case it's the same geopackage
    with get_shp_or_gpkg(
            outpath, write=True) as out_lyr, get_shp_or_gpkg(inpath) as in_lyr:
        # Get the input NHD flow lines layer
        srs = in_lyr.spatial_ref
        feature_count = in_lyr.ogr_layer.GetFeatureCount()
        log.info('Input feature count {:,}'.format(feature_count))

        # Get the closest EPSG possible to calculate length
        extent_poly = ogr.Geometry(ogr.wkbPolygon)
        extent_centroid = extent_poly.Centroid()
        utm_epsg = get_utm_zone_epsg(extent_centroid.GetX())
        transform_ref, transform = VectorBase.get_transform_from_epsg(
            in_lyr.spatial_ref, utm_epsg)

        # IN order to get accurate lengths we are going to need to project into some coordinate system
        transform_back = osr.CoordinateTransformation(transform_ref, srs)

        # Create the output shapefile
        if create_layer is True:
            out_lyr.create_layer_from_ref(in_lyr)
            # We add two features to this
            out_lyr.create_fields({
                'ReachID': ogr.OFTInteger,
                'WatershedID': ogr.OFTString
            })

        # Retrieve all input features keeping track of which ones have GNIS names or not
        named_features = {}
        all_features = []
        junctions = []

        # Omit pipelines with FCode 428**
        attribute_filter = 'FCode < 42800 OR FCode > 42899'
        log.info('Filtering out pipelines ({})'.format(attribute_filter))

        for in_feature, _counter, _progbar in in_lyr.iterate_features(
                "Loading Network", attribute_filter=attribute_filter):
            # Store relevant items as a tuple:
            # (name, FID, StartPt, EndPt, Length, FCode)
            s_feat = SegmentFeature(in_feature, transform)

            # Add the end points of all lines to a single list
            junctions.extend([s_feat.start, s_feat.end])

            if not s_feat.name or len(s_feat.name) < 1 or interval <= 0:
                # Add features without a GNIS name to list. Also add to list if not segmenting
                all_features.append(s_feat)
            else:
                # Build separate lists for each unique GNIS name
                if s_feat.name not in named_features:
                    named_features[s_feat.name] = [s_feat]
                else:
                    named_features[s_feat.name].append(s_feat)

        # Loop over all features with the same GNIS name.
        # Only merge them if they meet at a junction where no other lines meet.
        log.info('Merging simple features with the same GNIS name...')
        for name, features in named_features.items():
            log.debug('   {} x{}'.format(name.encode('utf-8'), len(features)))
            all_features.extend(features)

        log.info(
            '{:,} features after merging. Starting segmentation...'.format(
                len(all_features)))

        # Segment the features at the desired interval
        # rid = 0
        log.info('Segmenting Network...')
        progbar = ProgressBar(len(all_features), 50, "Segmenting")
        counter = 0

        for orig_feat in all_features:
            counter += 1
            progbar.update(counter)

            old_feat = in_lyr.ogr_layer.GetFeature(orig_feat.fid)
            old_geom = old_feat.GetGeometryRef()
            #  Anything that produces reach shorter than the minimum just gets added. Also just add features if not segmenting
            if orig_feat.length_m < (interval + minimum) or interval <= 0:
                new_ogr_feat = ogr.Feature(out_lyr.ogr_layer_def)
                copy_fields(old_feat, new_ogr_feat, in_lyr.ogr_layer_def,
                            out_lyr.ogr_layer_def)
                # Set the attributes using the values from the delimited text file
                new_ogr_feat.SetField("GNIS_NAME", orig_feat.name)
                new_ogr_feat.SetField("WatershedID", watershed_id)
                new_ogr_feat.SetGeometry(old_geom)
                out_lyr.ogr_layer.CreateFeature(new_ogr_feat)
                # rid += 1
            else:
                # From here on out we use shapely and project to UTM. We'll transform back before writing to disk.
                new_geom = old_geom.Clone()
                new_geom.Transform(transform)
                remaining = LineString(new_geom.GetPoints())
                while remaining and remaining.length >= (interval + minimum):
                    part1shply, part2shply = cut(remaining, interval)
                    remaining = part2shply

                    new_ogr_feat = ogr.Feature(out_lyr.ogr_layer_def)
                    copy_fields(old_feat, new_ogr_feat, in_lyr.ogr_layer_def,
                                out_lyr.ogr_layer_def)

                    # Set the attributes using the values from the delimited text file
                    new_ogr_feat.SetField("GNIS_NAME", orig_feat.name)
                    new_ogr_feat.SetField("WatershedID", watershed_id)

                    geo = ogr.CreateGeometryFromWkt(part1shply.wkt)
                    geo.Transform(transform_back)
                    new_ogr_feat.SetGeometry(geo)
                    out_lyr.ogr_layer.CreateFeature(new_ogr_feat)
                    # rid += 1

                # Add any remaining line to outGeometries
                if remaining:
                    new_ogr_feat = ogr.Feature(out_lyr.ogr_layer_def)
                    copy_fields(old_feat, new_ogr_feat, in_lyr.ogr_layer_def,
                                out_lyr.ogr_layer_def)

                    # Set the attributes using the values from the delimited text file
                    new_ogr_feat.SetField("GNIS_NAME", orig_feat.name)
                    new_ogr_feat.SetField("WatershedID", watershed_id)

                    geo = ogr.CreateGeometryFromWkt(remaining.wkt)
                    geo.Transform(transform_back)
                    new_ogr_feat.SetGeometry(geo)
                    out_lyr.ogr_layer.CreateFeature(new_ogr_feat)
                    # rid += 1

        progbar.finish()

        log.info(('{:,} features written to {:}'.format(
            out_lyr.ogr_layer.GetFeatureCount(), outpath)))
        log.info('Process completed successfully.')
Пример #19
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')
Пример #20
0
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
Пример #21
0
def copy_feature_class(in_layer_path: str, out_layer_path: str,
                       epsg: int = None,
                       attribute_filter: str = None,
                       clip_shape: BaseGeometry = None,
                       clip_rect: List[float] = None,
                       buffer: float = 0,
                       ) -> None:
    """Copy a Shapefile from one location to another

    This method is capable of reprojecting the geometries as they are copied.
    It is also possible to filter features by both attributes and also clip the
    features to another geometryNone

    Args:
        in_layer (str): Input layer path
        epsg ([type]): EPSG Code to use for the transformation
        out_layer (str): Output layer path
        attribute_filter (str, optional): [description]. Defaults to None.
        clip_shape (BaseGeometry, optional): [description]. Defaults to None.
        clip_rect (List[double minx, double miny, double maxx, double maxy)]): Iterate over a subset by clipping to a Shapely-ish geometry. Defaults to None.
        buffer (float): Buffer the output features (in meters).
    """

    log = Logger('copy_feature_class')

    # NOTE: open the outlayer first so that write gets the dataset open priority
    with get_shp_or_gpkg(out_layer_path, write=True) as out_layer, \
            get_shp_or_gpkg(in_layer_path) as in_layer:

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

        transform = VectorBase.get_transform(in_layer.spatial_ref, out_layer.spatial_ref)

        buffer_convert = 0
        if buffer != 0:
            buffer_convert = in_layer.rough_convert_metres_to_vector_units(buffer)

        # This is the callback method that will be run on each feature
        for feature, _counter, progbar in in_layer.iterate_features("Copying features", write_layers=[out_layer], clip_shape=clip_shape, clip_rect=clip_rect, attribute_filter=attribute_filter):
            geom = feature.GetGeometryRef()

            if geom is None:
                progbar.erase()  # get around the progressbar
                log.warning('Feature with FID={} has no geometry. Skipping'.format(feature.GetFID()))
                continue
            if geom.GetGeometryType() in VectorBase.LINE_TYPES:
                if geom.Length() == 0.0:
                    progbar.erase()  # get around the progressbar
                    log.warning('Feature with FID={} has no Length. Skipping'.format(feature.GetFID()))
                    continue

            # Buffer the shape if we need to
            if buffer_convert != 0:
                geom = geom.Buffer(buffer_convert)

            geom.Transform(transform)

            # Create output Feature
            out_feature = ogr.Feature(out_layer.ogr_layer_def)
            out_feature.SetGeometry(geom)

            # Add field values from input Layer
            for i in range(0, out_layer.ogr_layer_def.GetFieldCount()):
                out_feature.SetField(out_layer.ogr_layer_def.GetFieldDefn(i).GetNameRef(), feature.GetField(i))

            out_layer.ogr_layer.CreateFeature(out_feature)
            out_feature = None
Пример #22
0
def raster_warp(inraster: str,
                outraster: str,
                epsg,
                clip=None,
                warp_options: dict = {}):
    """
    Reproject a raster to a different coordinate system.
    :param inraster: Input dataset
    :param outraster: Output dataset
    :param epsg: Output spatial reference EPSG identifier
    :param log: Log file object
    :param clip: Optional Polygon dataset to clip the output.
    :param warp_options: Extra GDALWarpOptions.
    :return: None

    https://gdal.org/python/osgeo.gdal-module.html#WarpOptions
    """

    log = Logger('Raster Warp')

    if os.path.isfile(outraster):
        log.info(
            'Skipping raster warp because output exists {}'.format(outraster))
        return None

    log.info('Raster Warp input raster {}'.format(inraster))
    log.info('Raster Warp output raster {}'.format(outraster))
    log.info('Output spatial reference EPSG: {}'.format(epsg))

    output_folder = os.path.dirname(outraster)
    if not os.path.isdir(output_folder):
        os.mkdir(output_folder)
    warpvrt = os.path.join(os.path.dirname(outraster),
                           'temp_gdal_warp_output.vrt')

    log.info('Performing GDAL warp to temporary VRT file.')

    if clip:
        log.info('Clipping to polygons using {}'.format(clip))
        clip_ds, clip_layer = VectorBase.path_sorter(clip)
        warp_options_obj = gdal.WarpOptions(dstSRS='EPSG:{}'.format(epsg),
                                            format='vrt',
                                            cutlineDSName=clip_ds,
                                            cutlineLayer=clip_layer,
                                            cropToCutline=True,
                                            **warp_options)
    else:
        warp_options_obj = gdal.WarpOptions(dstSRS='EPSG:{}'.format(epsg),
                                            format='vrt',
                                            **warp_options)

    ds = gdal.Warp(warpvrt, inraster, options=warp_options_obj)

    log.info(
        'Using GDAL translate to convert VRT to compressed raster format.')
    translateoptions = gdal.TranslateOptions(
        gdal.ParseCommandLine("-of Gtiff -co COMPRESS=DEFLATE"))
    gdal.Translate(outraster, ds, options=translateoptions)

    # Cleanup the temporary VRT file
    os.remove(warpvrt)

    if ds:
        log.info('Process completed successfully.')
    else:
        log.error('Error running GDAL Warp')
def reach_geometry(flow_lines: Path, dem_path: Path, buffer_distance: float):
    """ Calculate reach geometry BRAT attributes

    Args:
        flow_lines (Path): [description]
        dem_path (Path): [description]
        buffer_distance (float): [description]
    """

    log = Logger('Reach Geometry')

    # Determine the best projected coordinate system based on the raster
    dataset = gdal.Open(dem_path)
    geo_transform = dataset.GetGeoTransform()
    xcentre = geo_transform[0] + (dataset.RasterXSize * geo_transform[1]) / 2.0
    epsg = get_utm_zone_epsg(xcentre)

    with rasterio.open(dem_path) as raster:
        bounds = raster.bounds
        extent = box(*bounds)

    # Buffer the start and end point of each reach
    line_start_polygons = {}
    line_end_polygons = {}
    reaches = {}
    with get_shp_or_gpkg(flow_lines) as lyr:

        # Transformations from original flow line features to metric EPSG, and to raster spatial reference
        _srs, transform_to_metres = VectorBase.get_transform_from_epsg(lyr.spatial_ref, epsg)
        _srs, transform_to_raster = VectorBase.get_transform_from_raster(lyr.spatial_ref, dem_path)

        # Buffer distance converted to the units of the raster spatial reference
        vector_buffer = VectorBase.rough_convert_metres_to_raster_units(dem_path, buffer_distance)

        for feature, _counter, _progbar in lyr.iterate_features("Processing reaches"):
            reach_id = feature.GetFID()
            geom = feature.GetGeometryRef()
            geom_clone = geom.Clone()

            # Calculate the reach length in the output spatial reference
            if transform_to_metres is not None:
                geom.Transform(transform_to_metres)

            reaches[reach_id] = {'iGeo_Len': geom.Length(), 'iGeo_Slope': 0.0, 'iGeo_ElMin': None, 'IGeo_ElMax': None}

            if transform_to_raster is not None:
                geom_clone.Transform(transform_to_raster)

            # Buffer the ends of the reach polyline in the raster spatial reference
            pt_start = Point(VectorBase.ogr2shapely(geom_clone, transform_to_raster).coords[0])
            pt_end = Point(VectorBase.ogr2shapely(geom_clone, transform_to_raster).coords[-1])
            if extent.contains(pt_start) and extent.contains(pt_end):
                line_start_polygons[reach_id] = pt_start.buffer(vector_buffer)
                line_end_polygons[reach_id] = pt_end.buffer(vector_buffer)

    # Retrieve the mean elevation of start and end of point
    line_start_elevations = raster_buffer_stats2(line_start_polygons, dem_path)
    line_end_elevations = raster_buffer_stats2(line_end_polygons, dem_path)

    for reach_id, data in reaches.items():
        if reach_id in line_start_elevations and reach_id in line_end_elevations:
            sta_data = line_start_elevations[reach_id]
            end_data = line_end_elevations[reach_id]

            data['iGeo_ElMax'] = _max_ignore_none(sta_data['Maximum'], end_data['Maximum'])
            data['iGeo_ElMin'] = _min_ignore_none(sta_data['Minimum'], end_data['Minimum'])

            if sta_data['Mean'] is not None and end_data['Mean'] is not None and sta_data['Mean'] != end_data['Mean']:
                data['iGeo_Slope'] = abs(sta_data['Mean'] - end_data['Mean']) / data['iGeo_Len']
        else:
            log.warning('{:,} features skipped because one or both ends of polyline not on DEM raster'.format(reach_id))

    write_db_attributes(os.path.dirname(flow_lines), reaches, ['iGeo_Len', 'iGeo_ElMax', 'iGeo_ElMin', 'iGeo_Slope'])