Пример #1
0
def createChannelUnitsJSON(topoDataFolder, visitID, jsonFilePath):

    topo = TopoData(topoDataFolder, visitID)
    topo.loadlayers()
    shpCU = Shapefile(topo.ChannelUnits)

    dUnits = {}
    feats = shpCU.featuresToShapely()
    for aFeat in feats:
        origTier1 = aFeat['fields']['Tier1']
        origTier2 = aFeat['fields']['Tier2']
        UtNum = aFeat['fields']['UnitNumber']

        finalTier1 = None
        for tier1 in dUnitDefs.keys():
            if tier1.lower() == origTier1.lower():
                finalTier1 = tier1
                break

        if not finalTier1:
            print('No Tier 1 channel unit type found')

        dUnits[UtNum] = (finalTier1, origTier2, 1)

    writeChannelUnitsToJSON(jsonFilePath, dUnits)
Пример #2
0
    def calc(self, shpIslands):
        """

        :param shpIslands:
        :return:
        """
        self.metrics = {
            'Count': 0,
            'Area': 0.0,
            'QualifyingCount': 0,
            'QualifyingArea': 0.0
        }

        if not path.isfile(shpIslands):
            return

        shp = Shapefile(shpIslands)
        feats = shp.featuresToShapely()
        if feats:
            for aFeat in feats:

                self.metrics['Count'] += 1
                self.metrics['Area'] += aFeat['geometry'].area

                if 'IsValid' in shp.fields:
                    if aFeat['fields']['IsValid'] == 1:
                        self.metrics['QualifyingCount'] += 1
                        self.metrics['QualifyingArea'] += aFeat[
                            'geometry'].area
Пример #3
0
def vectorToCSV(shapefilePath, outputCSVPath):

    print("Writing vector {0} to {1}").format(os.path.basename(shapefilePath),
                                              outputCSVPath)

    clShp = Shapefile(shapefilePath)
    clList = clShp.featuresToShapely()

    with open(outputCSVPath, 'w') as csvfile:
        # write the header row
        csvfile.write('x,y\n')

        for aLine in clList:
            for aPoint in aLine['geometry'].coords:
                csvfile.write('{0},{1}\n'.format(aPoint[0], aPoint[1]))
Пример #4
0
    def calc(self, sThalwegshp, sDepthRaster, sWaterSurfaceRaster, fDist, visitMetrics):

        if not path.isfile(sThalwegshp):
            raise MissingException("Thalweg shapefile missing")
        if not path.isfile(sDepthRaster):
            raise MissingException("Depth raster missing")
        if not path.isfile(sWaterSurfaceRaster):
            raise MissingException("Surface raster missing")

        wettedMainstemLength = visitMetrics['Wetted']['Centerline']['MainstemLength']

        if wettedMainstemLength is None:
            raise MissingException("No wetted mainstem length found in visit metrics")

        sfile = Shapefile(sThalwegshp).featuresToShapely()

        if len(sfile) < 1:
            raise DataException("Thalweg shapefile has no features")

        thalweg = sfile[0]['geometry']
        depthRaster = Raster(sDepthRaster)
        waterSurfaceRaster = Raster(sWaterSurfaceRaster)
        samplepts = ThalwegMetrics.interpolateRasterAlongLine(thalweg, fDist)
        results = ThalwegMetrics.lookupRasterValues(samplepts, depthRaster)['values']

        # Get the elevation at the first (downstream) point on the Thalweg
        dsElev = waterSurfaceRaster.getPixelVal(thalweg.coords[0])
        usElev = waterSurfaceRaster.getPixelVal(thalweg.coords[-1])

        if (np.isnan(dsElev)):
            raise DataException('nodata detected in the raster for downstream point on the thalweg')
        elif np.isnan(usElev):
            raise DataException('nodata detected in the raster for upstream point on the thalweg')

        waterSurfaceGradientRatio = (usElev - dsElev) / thalweg.length
        waterSurfaceGradientPC = waterSurfaceGradientRatio * 100.0

        # Thalweg straight length and sinuosity
        firstPoint = Point(thalweg.coords[0])
        lastPoint = Point(thalweg.coords[-1])
        straightLength = firstPoint.distance(lastPoint)
        sinuosity = thalweg.length / straightLength

        self.metrics = {
            'Min': np.nanmin(results),
            'Max': np.nanmax(results),
            'Mean': np.mean(results),
            'StDev': np.std(results),
            'Count': np.count_nonzero(results),
            'Length': thalweg.length,
            'WSGradientRatio': waterSurfaceGradientRatio,
            'WSGradientPC': waterSurfaceGradientPC,
            'Sinuosity': sinuosity,
            'CV': 0.0,
            'ThalwegToCenterlineRatio': thalweg.length / wettedMainstemLength
            # , 'Values': results.data
        }
        if self.metrics['StDev'] != 0 and self.metrics['Mean'] != 0:
            self.metrics['CV'] = self.metrics['StDev'] / self.metrics['Mean']
Пример #5
0
    def _getMainstemGeometry(self, shpCenterline):
        """

        :param shpCenterline:
        :return:
        """
        mainstem = None

        shp = Shapefile(shpCenterline)
        feats = shp.featuresToShapely()
        if 'Channel' in shp.fields:
            for featureIndex in range(0, feats.count()):
                if shp.fields[featureIndex] == 'Main':
                    mainstem = shp.features[featureIndex]['geometry']
                    break
        else:
            mainstem = shp.features[0]['geometry'][0]

        return mainstem
Пример #6
0
def processSurveyExtent(projects, outputFolder):

    spatialRef = None
    unionPoly = None
    intersectPoly = None

    for proj in projects:
        extentPath = proj['project'].getpath('SurveyExtent')
        vid = proj['visit']
        if not os.path.isfile(extentPath):
            print("Warning: Missing Survey Extent ShapeFile for Visit {0}").format(vid)
            continue

        seShp = Shapefile(extentPath)
        seList = seShp.featuresToShapely()
        spatialRef = seShp.spatialRef

        for sePolygon in seList:

            if unionPoly:
                unionPoly = unionPoly.union(sePolygon['geometry'])
            else:
                unionPoly = sePolygon['geometry']

            if intersectPoly:
                intersectPoly = intersectPoly.intersection(sePolygon['geometry'])
            else:
                intersectPoly = sePolygon['geometry']


    if not unionPoly:
        raise DataException("No survey extent union polygon derived from visits")

    if not intersectPoly:
        raise DataException("No survey extent intersect polygon derived from visits")

    unionPath = os.path.join(outputFolder, "SurveyExtentUnion.shp")
    print("Writing survey extent union to {0}").format(unionPath)
    writeShapeToShapeFile(unionPath, unionPoly, spatialRef)

    intersectPath = os.path.join(outputFolder, "SurveyExtentIntersect.shp")
    print("Writing survey extent intersection to {0}").format(intersectPath)
    writeShapeToShapeFile(intersectPath, intersectPoly, spatialRef)
Пример #7
0
def writeShapeToShapeFile(outputPath, aPolygon, spatialRef):

    # Write the polygon to ShapeFile (note: ShapeFile handles deleting existing file)
    outShape = Shapefile()
    outShape.create(outputPath, spatialRef, geoType=ogr.wkbPolygon)
    outShape.createField("ID", ogr.OFTInteger)

    # The main centerline gets written first
    featureDefn = outShape.layer.GetLayerDefn()
    outFeature = ogr.Feature(featureDefn)
    ogrPolygon = ogr.CreateGeometryFromJson(json.dumps(mapping(aPolygon)))
    outFeature.SetGeometry(ogrPolygon)
    outFeature.SetField('ID', 1)
    outShape.layer.CreateFeature(outFeature)
Пример #8
0
def export_cad_files(project_xml, out_path):
    """exports dxf files containing tin components of topo tin and Topographic Survey Points, Lines and Survey Extent"""

    log = Logger("CADExport")

    # Load Topo project
    log.info("Load Topo project")
    project = topoproject.TopoProject(project_xml)

    # TIN stuff
    log.info("Beginning TIN Work")
    tin = TIN(project.getpath("TopoTin"))
    dict_tinlayers = {}
    dict_tinlayers["tin_points"] = {
        "layer_type": "POINT",
        "Features": [feat for feat in tin.nodes.values()]
    }
    dict_tinlayers["tin_lines"] = {
        "layer_type": "POLYLINE",
        "Features": [feat['geometry'] for feat in tin.breaklines.values()]
    }  #, "linetype_field":"LineType"}
    dict_tinlayers["tin_area"] = {
        "layer_type": "POLYGON",
        "Features": [feat for feat in tin.hull_polygons.values()]
    }

    out_tin_dxf = export_as_dxf(dict_tinlayers,
                                os.path.join(out_path, "TopoTin.dxf"))

    # Topo Stuff
    log.info("Beginning Topo Work")
    shpTopo = Shapefile(project.getpath("Topo_Points"))
    shpEOW = Shapefile(project.getpath("EdgeofWater_Points"))
    shpCP = Shapefile(project.getpath("Control_Points"))
    shpBL = Shapefile(project.getpath("Breaklines")) if project.layer_exists(
        "Breaklines") else None
    shpExtent = Shapefile(project.getpath("Survey_Extent"))
    dict_topolayers = {}
    dict_topolayers["Topo_Points"] = {
        "layer_type": "POINT",
        "Features": [feat['geometry'] for feat in shpTopo.featuresToShapely()]
    }
    dict_topolayers["EdgeofWater_Points"] = {
        "layer_type": "POINT",
        "Features": [feat['geometry'] for feat in shpEOW.featuresToShapely()]
    }
    dict_topolayers["Control_Points"] = {
        "layer_type": "POINT",
        "Features": [feat['geometry'] for feat in shpCP.featuresToShapely()]
    }
    dict_topolayers["Breaklines"] = {
        "layer_type": "POLYLINE",
        "Features": [feat['geometry'] for feat in shpBL.featuresToShapely()]
    } if shpBL else None
    dict_topolayers["Survey_Extent"] = {
        "layer_type": "POLYGON",
        "Features":
        [feat['geometry'] for feat in shpExtent.featuresToShapely()]
    }

    out_topo_dxf = export_as_dxf(
        dict_topolayers, os.path.join(out_path, "SurveyTopography.dxf"))

    out_topo_csv = exportAsCSV(
        shpTopo.featuresToShapely() + shpEOW.featuresToShapely(),
        os.path.join(out_path, "SurveyTopographyPoints.csv"))
    out_control_csv = exportAsCSV(
        shpCP.featuresToShapely(),
        os.path.join(out_path, "ControlNetworkPoints.csv"))

    topo_rs_project = riverscapes.Project(project_xml)

    out_project = riverscapes.Project()
    out_project.create("CHaMP_Survey_CAD_Export", "CAD_Export", __version__)
    out_project.addProjectMetadata(
        "Watershed", topo_rs_project.ProjectMetadata["Watershed"])

    #  find previous meta tags
    for tagname, tags in {
            "Site": ["Site", "SiteName"],
            "Visit": ["Visit", "VisitID"],
            "Year": ["Year", "FieldSeason"],
            "Watershed": ["Watershed", "Watershed"]
    }.items():
        if tags[0] in topo_rs_project.ProjectMetadata or tags[
                1] in topo_rs_project.ProjectMetadata:
            out_project.addProjectMetadata(
                tagname, topo_rs_project.ProjectMetadata[tags[0]]
                if tags[0] in topo_rs_project.ProjectMetadata else
                topo_rs_project.ProjectMetadata[tags[1]])
        else:
            raise DataException("Missing project metadata")

    out_realization = riverscapes.Realization("CAD_Export")
    out_realization.name = "CHaMP Survey CAD Export"
    out_realization.productVersion = out_project.projectVersion
    ds = []
    ds.append(
        out_project.addInputDataset("TopoTin", "tin", None, None, "TIN",
                                    project.get_guid("TopoTin")))
    ds.append(
        out_project.addInputDataset("Topo_Points",
                                    "topo_points",
                                    None,
                                    guid=project.get_guid("Topo_Points")))
    ds.append(
        out_project.addInputDataset(
            "EdgeofWater_Points",
            "eow_points",
            None,
            guid=project.get_guid("EdgeofWater_Points")))
    ds.append(
        out_project.addInputDataset("Control_Points",
                                    "control_ponts",
                                    None,
                                    guid=project.get_guid("Control_Points")))
    if shpBL:
        ds.append(
            out_project.addInputDataset("Breaklines",
                                        "breaklines",
                                        None,
                                        guid=project.get_guid("Breaklines")))
    ds.append(
        out_project.addInputDataset("Survey_Extent",
                                    "survey_extent",
                                    None,
                                    guid=project.get_guid("Survey_Extent")))
    for inputds in ds:
        out_realization.inputs[inputds.name] = inputds.id

    ds_tin_dxf = riverscapes.Dataset()
    ds_tin_dxf.create("TIN_DXF", "TopoTin.dxf")
    ds_tin_dxf.id = 'tin_dxf'
    ds_topo_dxf = riverscapes.Dataset()
    ds_topo_dxf.create("Topo_DXF", "SurveyTopography.dxf")
    ds_topo_dxf.id = 'topo_dxf'
    ds_topo_csv = riverscapes.Dataset()
    ds_topo_csv.create("Topo_CSV", "SurveyTopographyPoints.csv", "CSV")
    ds_topo_csv.id = 'topo_csv'
    ds_con_csv = riverscapes.Dataset()
    ds_con_csv.create("Control_CSV", "ControlNetworkPoints.csv", "CSV")
    ds_con_csv.id = 'control_csv'
    out_realization.outputs.update({
        "TIN_DXF": ds_tin_dxf,
        "Topo_DXF": ds_topo_dxf,
        "Topo_CSV": ds_topo_csv,
        "Control_CSV": ds_con_csv
    })

    out_project.addRealization(out_realization)
    out_project.writeProjectXML(os.path.join(out_path, "project.rs.xml"))

    return 0
Пример #9
0
 def get_shapefile(self):
     return Shapefile(self.filename)
def channelUnitScraper(outputDir, watersheds):

    visitData = {}
    for watershed, apiName in watersheds.items():
        visitData[watershed] = {}

        loadVisitData(watershed, apiName, visitData[watershed])

    for watershed, visits in visitData.items():
        outPath = os.path.join(outputDir, watershed.replace(' ', '') + ".shp")
        outShape = None

        featureID = 0
        for visitID, visit in visits.items():

            if  len(visit['ChannelUnits']) < 1:
                continue

            try:
                dirpath = tempfile.mkdtemp()

                # Open the visit channel unit shapefile.
                # Need the spatial reference from one of the visits to create the output watershed shapefile
                try:
                    fileJSON, projPath = downloadUnzipTopo(visitID, dirpath)
                    topo = TopoProject(projPath)
                    cuPath = topo.getpath('ChannelUnits')
                except (DataException, MissingException), e:
                    print("Error retrieving channel units ShapeFile for visit {0}").format(visitID)
                    continue

                try:
                    shpCU = Shapefile(cuPath)
                except Exception as e:
                    print("Error OGR opening channel unit ShapeFile for visit {0}").format(visitID)
                    continue

                if not outShape:
                    # Create new ShapeFile for this watershed
                    outShape = Shapefile()
                    outShape.create(outPath, shpCU.spatialRef, geoType=ogr.wkbPolygon)

                    outShape.createField("ID", ogr.OFTInteger)
                    outShape.createField("Watershed", ogr.OFTString)
                    outShape.createField("Site", ogr.OFTString)
                    outShape.createField("VisitID", ogr.OFTInteger)
                    outShape.createField("SampleYear", ogr.OFTInteger)
                    outShape.createField("Org", ogr.OFTString)
                    outShape.createField("UnitNumber", ogr.OFTInteger)
                    outShape.createField("UnitArea", ogr.OFTReal)
                    outShape.createField("Tier1", ogr.OFTString)
                    outShape.createField("Tier2", ogr.OFTString)
                    outShape.createField("AvgSiteWid", ogr.OFTReal)
                    outShape.createField("ReachLen", ogr.OFTReal)

                # Loop over all channel unit polygons for this visit

                feats = shpCU.featuresToShapely()
                for aFeat in feats:
                    featureID += 1
                    cuNumber = aFeat['fields']['UnitNumber']

                    featureDefn = outShape.layer.GetLayerDefn()
                    outFeature = ogr.Feature(featureDefn)
                    outFeature.SetField('ID', featureID)
                    outFeature.SetField('Watershed', visit['Watershed'])
                    outFeature.SetField('Site', visit['Site'])
                    outFeature.SetField('VisitID', visitID)
                    outFeature.SetField('SampleYear', visit['SampleYear'])
                    outFeature.SetField('Org', visit['Organization'])
                    outFeature.SetField('AvgSiteWid', visit['AverageSiteWidth'])
                    outFeature.SetField('ReachLen', visit['TotalReachLength'])
                    outFeature.SetField('UnitNumber', cuNumber)
                    outFeature.SetField('Tier1', visit['ChannelUnits'][cuNumber]['Tier1'])
                    outFeature.SetField('Tier2', visit['ChannelUnits'][cuNumber]['Tier2'])
                    outFeature.SetField('UnitArea', aFeat['geometry'].area)
                    outFeature.SetGeometry(ogr.CreateGeometryFromJson(json.dumps(mapping(aFeat['geometry']))))
                    outShape.layer.CreateFeature(outFeature)

            finally:
Пример #11
0
    def calc(self, crosssections, waterExtent, demPath, stationInterval):

        # Save space by only loading the desired fields from the ShapeFile.
        # We also need the 'Channel' and 'IsValid' fields if they exist.
        desiredFields = list(CrossSectionMetrics.dMetricTypes.keys())
        desiredFields.append('IsValid')

        # Open the cross section ShapeFile & build a list of all features with a dictionary of the desired fields
        clShp = Shapefile(crosssections)
        lChannels = ['Main']

        if not clShp.loaded:
            return

        if "Channel" in clShp.fields:
            desiredFields.append('Channel')
            lChannels.append('Side')

        # Older Cross Section Layers don't have the width and depth fields calculated.
        # So if all the necessary metric fields are present then continue to load
        # ShapeFile features. Otherwise we need to calculate the topometrics from scratch
        bMetricCalculationNeeded = False
        for aMetric in desiredFields:
            if not aMetric in clShp.fields:
                bMetricCalculationNeeded = True
                break

        allFeatures = []
        if bMetricCalculationNeeded:
            # Retrieve the water extent polygon exterior
            rivershp = Shapefile(waterExtent)
            polyRiverShapeFeats = rivershp.featuresToShapely()

            # Try and find a channel shape. Thers's a lot of variance here.
            if len(polyRiverShapeFeats
                   ) == 0 or 'geometry' not in polyRiverShapeFeats[0]:
                raise DataException("No features in crosssection shape file")

            # If there's only one shape then just use it
            elif len(polyRiverShapeFeats) == 1:
                polyRiverShape = polyRiverShapeFeats[0]['geometry']

            # If there's more than one shape then see if
            else:
                channelShapes = [
                    feat['geometry'] for feat in polyRiverShapeFeats
                    if feat['fields']['ExtentType'] == 'Channel'
                ]
                if len(channelShapes) == 0:
                    raise DataException(
                        "No features in crosssection shape file")
                polyRiverShape = channelShapes[0]

            # Calculate the topometrics from scratch for a single cross section
            shpXS = Shapefile(crosssections)
            demRaster = Raster(demPath)

            if shpXS.loaded:
                for aFeat in shpXS.featuresToShapely():
                    # Calculate the topometrics for this cross section. They will be stored on the aFeat dict under key 'topometrics'
                    calcXSMetrics(aFeat, polyRiverShape, demRaster,
                                  stationInterval)

                    # Build the all features dictionary that would be expect had the topometrics already
                    # existed in the XS shapefile and simply got loaded. This is a combination of the new topometrics
                    # and also the existing fields on the XS ShapeFile.
                    singleXSMetrics = copy.deepcopy(aFeat['topometrics'])
                    singleXSMetrics.update(aFeat['fields'])
                    allFeatures.append(singleXSMetrics)

            # Destroying the raster object appears to prevent warning messages on Windows
            demRaster = None
        else:
            allFeatures = clShp.attributesToList(desiredFields)

        # For simple ShapeFiles, make every feature part of the main channel, and
        # set every feature as valid. This helps keep code below generic
        for x in allFeatures:
            if 'Channel' not in x:
                x['Channel'] = 'Main'

            if 'IsValid' not in x:
                x['IsValid'] = 1

        for channelName in lChannels:

            # Filter the list of features to just those in this channel
            # PGB - 24 Apr 2017 - observed NULL values in 'Channel' ShapeFile field in Harold results.
            # Cast field contents to string to avoid crash here.
            channelFeatures = [
                x for x in allFeatures
                if str(x['Channel']).lower() == channelName.lower()
            ]

            # Filter the list of features to just those that the crew considered valid
            validFeatures = [x for x in channelFeatures if x['IsValid'] != 0]

            # Filter the features to just those with a length that is within 4 standard deviations of mean wetted width
            channelStatistics = getStatistics(channelFeatures, 'WetWidth')

            autoFeatures = None
            if channelStatistics['StdDev'] is not None:
                wetWidthThreshold = channelStatistics['StdDev'] * 4
                autoFeatures = [
                    x for x in channelFeatures
                    if abs(x['WetWidth'] -
                           channelStatistics['Mean']) < wetWidthThreshold
                ]

            # Loop over each desired metric and calculate the statistics for each filtering type
            for metricName, bestFiltering in CrossSectionMetrics.dMetricTypes.items(
            ):
                populateChannelStatistics(self.metrics[channelName], 'None',
                                          metricName, channelFeatures)
                populateChannelStatistics(self.metrics[channelName], 'Crew',
                                          metricName, validFeatures)

                if channelStatistics['StdDev'] is not None:
                    populateChannelStatistics(self.metrics[channelName],
                                              'Auto', metricName, autoFeatures)

                self.metrics[channelName]['Best'] = self.metrics[channelName][
                    bestFiltering]

        # The topometrics for the whole channel are always the results for 'Main'.
        # For complex ShapeFiles this will be just the results for the main channel.
        # For simple, single threaded, ShapeFiles this will all cross sections.
        self.metrics['Channel'] = self.metrics['Main']
Пример #12
0
def concatenateControlPoints(projects, outputFolder):

    spatialRef = None
    cpList = []

    for proj in projects:
        controlPointsPath = proj['project'].getpath('Control_Points')
        vid = proj['visit']

        cpShp = Shapefile(controlPointsPath)
        visitCPList = cpShp.featuresToShapely()
        spatialRef = cpShp.spatialRef

        for aPoint in visitCPList:
            cpItems = {}
            cpItems['geometry'] = aPoint['geometry']
            cpItems['fields'] = {}

            cpItems['fields']['VisitID'] = int(vid)
            cpItems['fields']['PointNum'] = getFieldValue(aPoint['fields'], ['Point_Numb', 'POINT_NUMB', 'PointNumb', 'POINT_NUM', 'POINT'])
            cpItems['fields']['Code'] = getFieldValue(aPoint['fields'], ['DESCRIPTIO', 'Descriptio', 'Code'])
            cpItems['fields']['Type'] = getFieldValue(aPoint['fields'], ['Type', 'TYPE'])

            cpList.append(cpItems)

    outputPath = os.path.join(outputFolder, "AllVisitControlPoints.shp")
    print("Writing combined control points to {0}").format(outputPath)
    outShape = Shapefile()
    outShape.create(outputPath, spatialRef, geoType=ogr.wkbPointZM)

    outShape.createField("ID", ogr.OFTInteger)
    outShape.createField("VisitID", ogr.OFTInteger)
    outShape.createField("PointNum", ogr.OFTString)
    outShape.createField("Code", ogr.OFTString)
    outShape.createField("Type", ogr.OFTString)

    id = 1
    for aCP in cpList:

        featureDefn = outShape.layer.GetLayerDefn()
        outFeature = ogr.Feature(featureDefn)
        ogrPolygon = ogr.CreateGeometryFromJson(json.dumps(mapping(aCP['geometry'])))
        outFeature.SetGeometry(ogrPolygon)
        outFeature.SetField('ID', id)
        id += 1

        for fieldName, fieldValue in aCP['fields'].items():
            outFeature.SetField(fieldName, fieldValue)

        outShape.layer.CreateFeature(outFeature)
Пример #13
0
    def calc(self, shpCUPath, shpThalweg, rasDepth, visitMetrics, dUnits, unitDefs):

        if not path.isfile(shpCUPath):
            raise MissingException("Channel units file not found")
        if not path.isfile(shpThalweg):
            raise MissingException("Thalweg shape file not found")
        if not path.isfile(rasDepth):
            raise MissingException("Depth raster file not found")

        siteLength = visitMetrics['Wetted']['Centerline']['MainstemLength']

        if siteLength is None:
            raise DataException("No valid site length found in visit metrics")

        # Give us a fresh template with 0's in the value positions
        self.metrics = self._templateMaker(0, unitDefs)
        dResultsChannelSummary = self.metrics['ResultsChannelSummary']
        dResultsTier1 = self.metrics['ResultsTier1']
        dResultsTier2 = self.metrics['ResultsTier2']
        resultsCU = self.metrics['resultsCU']

        #Load the Thalweg feature
        thalweg = Shapefile(shpThalweg).featuresToShapely()
        thalwegLine = thalweg[0]['geometry']

        # Load the depth raster
        depthRaster = Raster(rasDepth)

        # Load the channel unit polygons and calculate the total area
        # The channel units should be clipped to the wetted extent and so this
        # can be used as the site area wetted
        shpCU = Shapefile(shpCUPath)
        arrCU = depthRaster.rasterMaskLayer(shpCUPath, "UnitNumber")

        feats = shpCU.featuresToShapely()
        for aFeat in feats:
            dResultsChannelSummary['Main']['Area'] += aFeat['geometry'].area

        # Loop over each channel unit and calculate topometrics
        for aFeat in feats:
            nCUNumber  = int(aFeat['fields']['UnitNumber'])

            if nCUNumber not in dUnits:
                self.log.error("Channel Unit: '{0}' not present in the aux data.".format(nCUNumber))
                # Keep it general for the exception so we can aggregate them
                raise DataException("The Channel Unit ShapeFile contains a unit number that is not present in the aux data.")

            tier1Name =  dUnits[nCUNumber][0]
            tier2Name = dUnits[nCUNumber][1]
            nSegment = dUnits[nCUNumber][2]
            #print("Channel Unit Number {0}, Segment {1}, Tier 1 - {2}, Tier 2 - {3}").format(nCUNumber, nSegment, tier1Name, tier2Name)

            unitMetrics = {}
            resultsCU.append(unitMetrics)
            unitMetrics['ChannelUnitNumber'] = nCUNumber
            unitMetrics['Area'] = aFeat['geometry'].area
            unitMetrics['Tier1'] = tier1Name
            unitMetrics['Tier2'] = tier2Name
            unitMetrics['Length'] = None
            unitMetrics['ResidualDepth'] = None
            unitMetrics['DepthAtThalwegExit'] = None
            unitMetrics['ThalwegIntersect'] = 0

            # Get the depth raster for this unit as variable so we can check
            # whether it is entirely masked below.
            depArr = depthRaster.array[arrCU == nCUNumber]
            if depArr.count() == 0:
                unitMetrics['MaxDepth'] = 0
                unitMetrics['Volume'] = 0
            else:
                unitMetrics['MaxDepth'] = np.max(depArr)
                unitMetrics['Volume'] = np.sum(depthRaster.array[arrCU == nCUNumber]) * (depthRaster.cellWidth**2)

            if nSegment != 1:
                dSideChannelSummary = dResultsChannelSummary['SideChannelSummary']
                dMain = dResultsChannelSummary['Main']
                # Side channel summary captures both small and large side channels
                dSideChannelSummary['Area'] += aFeat['geometry'].area
                dSideChannelSummary['Count'] += 1
                dSideChannelSummary['Percent'] = 100 * dSideChannelSummary['Area'] / dMain['Area']
                dSideChannelSummary['Volume'] += unitMetrics['Volume']

                if 'side' in tier1Name.lower():
                    dSmallSideChannel = dResultsChannelSummary['SmallSideChannel']
                    dSmallSideChannel['Area'] += aFeat['geometry'].area
                    dSmallSideChannel['Count'] += 1
                    dSmallSideChannel['Percent'] = 100 * dSmallSideChannel['Area'] / dMain['Area']
                    dSmallSideChannel['Volume'] += unitMetrics['Volume']
                else:
                    dLargeSideChannel = dResultsChannelSummary['LargeSideChannel']
                    dLargeSideChannel['Area'] += aFeat['geometry'].area
                    dLargeSideChannel['Count'] += 1
                    dLargeSideChannel['Percent'] = 100 * dLargeSideChannel['Area'] / dMain['Area']
                    dLargeSideChannel['Volume'] += unitMetrics['Volume']

            if tier1Name is None:
                raise DataException("tier1Name cannot be 'None'")

            if 'side' in tier1Name.lower():
                dResultsChannelSummary['ChannelUnitBreakdown']['SmallSideChannel'] += 1
            else:
                dResultsChannelSummary['ChannelUnitBreakdown']['Other'] += 1

            if (thalwegLine.intersects(aFeat['geometry'])):
                cuThalwegLine = thalwegLine.intersection(aFeat['geometry'])

                exitPoint = None
                if cuThalwegLine.type == 'LineString':
                    exitPoint = cuThalwegLine.coords[0]
                else:
                    exitPoint = cuThalwegLine[0].coords[0]

                # Retrieve a list of points along the Thalweg in the channel unit
                thalwegPoints = ChannelUnitMetrics.interpolatePointsAlongLine(cuThalwegLine, 0.13)
                thalwegDepths = ChannelUnitMetrics.lookupRasterValuesAtPoints(thalwegPoints, depthRaster)
                unitMetrics['MaxDepth'] = np.nanmax(thalwegDepths['values'])
                unitMetrics['DepthAtThalwegExit'] = depthRaster.getPixelVal(exitPoint)
                unitMetrics['ResidualDepth'] = unitMetrics['MaxDepth'] - unitMetrics['DepthAtThalwegExit']
                unitMetrics['Length'] = cuThalwegLine.length
                unitMetrics['ThalwegIntersect'] = 1

            # Tier 1 and tier 2 topometrics. Note that metric dictionary keys are used for XML tags & require cleaning
            tier1NameClean = getCleanTierName(tier1Name)
            self._calcTierLevelMetrics(dResultsTier1[tier1NameClean], tier1Name, unitMetrics, siteLength, dResultsChannelSummary['Main']['Area'])

            tier2NameClean = getCleanTierName(tier2Name)
            self._calcTierLevelMetrics(dResultsTier2[tier2NameClean], tier2Name, unitMetrics, siteLength, dResultsChannelSummary['Main']['Area'])

        # Calculate the average of the channel unit max depths for each tier 1 and tier 2 type
        for tierKey, tierMetrics in { 'Tier1': dResultsTier1, 'Tier2': dResultsTier2}.items():
            for tierName, metricDict in tierMetrics.items():
                maxDepthList = [aResult['MaxDepth'] for aResult in resultsCU if getCleanTierName(aResult[tierKey]) == tierName]
                if len(maxDepthList) > 0:
                    metricDict['AvgMaxDepth']  = np.average(maxDepthList)

        # Convert the sum of residual depth and depth at thalweg exist
        # to average residual depth for each tier 1 and tier 2 type
        for tierMetricDict in [dResultsTier1, dResultsTier2]:
            for tierName, tierMetrics in tierMetricDict.items():
                # channel unit types that don't occur should retain the value None for Residual Depth and Depth at Thalweg exit
                if tierMetrics['Count'] > 0 and tierMetrics['ThalwegIntersectCount'] > 0:
                    for metricName in ['ResidualDepth', 'DepthAtThalwegExit']:
                        if tierMetrics[metricName] is not None and tierMetrics[metricName] != 0:
                            tierMetrics[metricName] = tierMetrics[metricName] / tierMetrics['ThalwegIntersectCount']
                        else:
                            tierMetrics[metricName] = 0