There is not a lot of information on this site or any site on how to correct monotonic errors for line vectors in a GIS context. It seems like such a simple problem with a simple fix. Monotonicity is required when a z-enabled feature is required to be ever increasing or decreasing i.e. the z values at each vertex should be consistently going down or up. This is most often seen in hydrology networks than need to be monotonic for network and flow. A monotonic error is flagged when there is a vertex that goes opposite of this trend. See the before and after examples from ESRI below.

Edit Sketch Properties Example 1

Edit Sketch Properties Example 2

Correcting monotonicity errors page from ESRI

Data Reviewer as well as other topology checks can find them. Defense Mapping for ArcGIS claims to fix them by interpolating the z-values before and after the errors to be consistent with the downward or upward slope. After several attempts, The Fix Monotonicity tool in Defense Mapping only makes my lines monotonic by applying one z-value to the entire line essentially flattening it.

Are there any tools or techniques in ArcGIS Desktop that will automatically correct these errors?


Here is an ArcToolbox ready script for a tool that runs on an entire feature layer, or a selection if you happen to have features selected. The tool assumes the start point has a z lower than the end point for ascending monotonic vertices, or a start point with a higher z than the end point for descending monotonic vertices. Single vertices that do not fit the monotonic trend are given an average z value of the neighbouring vertices. If two or more consecutive non-monotonic vertices are found, the first are "flattened" the the z of the previous vertices. Flat line parts are skipped all together.

This is not pretty (thanks to arcpy geometry objects), and there's probably a better solution out there, but it works fairly well.

import arcpy
inFeat = arcpy.GetParameterAsText(0)   #Feature Layer
with arcpy.da.UpdateCursor(inFeat, ["OID@", "SHAPE@"]) as cursor:
    for row in cursor:
        geom = row[1]
        partNum = 0
        isIncreasing = None
        newGeom = arcpy.Array()
        for part in geom:
            newPart = arcpy.Array()
            # detemine increasing/decreasing
            if part[0].Z < part[len(part) - 1].Z:
                isIncreasing = True
            elif part[0].Z > part[len(part) - 1].Z:
                isIncreasing = False
                #flat line
                arcpy.AddMessage("Line {0} part {1} is flat. Skipping...".format(row[0],partNum))
                partNum += 1
            zList = []
            for i in xrange(len(part)):
                pnt = part[i]
            for i in xrange(len(part)):
                pnt = part[i]
                z = zList[i]
                if (i != 0) and (i != len(part) - 1):
                    zN = zList[i+1]
                    zP = zList[i-1]
                    if isIncreasing:
                        if z < zP:
                            if zN > zP:
                                # interpolate Z (i.e. average)
                                zList[i] = (zP + zN) / 2
                                arcpy.AddMessage("Modified line {0}, part {1}, vertex {2}...".format(row[0], partNum, i))
                                # Next Z is greater than Prev Z
                                # set Z equal to previous point
                                zList[i] = zP
                                arcpy.AddMessage("Modified line {0}, part {1}, vertex {2}...".format(row[0], partNum, i))
                        if z > zP:
                            if zN < zP:
                                zList[i] = (zP + zN) / 2
                                arcpy.AddMessage("Modified line {0}, part {1}, vertex {2}...".format(row[0], partNum, i))
                                zList[i] = zP
                                arcpy.AddMessage("Modified line {0}, part {1}, vertex {2}...".format(row[0], partNum, i))
                newPnt = arcpy.Point(part[i].X, part[i].Y, zList[i])
        newShape = arcpy.Polyline(newGeom, None, True)
        row[1] = newShape             

I'm unaware of any out of the box tool or third party tools. Not saying there are none, I just don't do much in Z aware geometry. But I have done work using M aware geometry and used various objects in ArcObjects. I think this requires a custom ArcObjects solution that manipulates Z values at the vertex level. If you look at the IZ Interface in the API help, this is a starting point on accessing various functions that can manipulate Z values.

If Arcobjects is too much for you, but python is OK then an approach is to explode the polyline into a sequence of XYZ and then write some fancy code that can fixes the error as it reconstructs the polyline Z geometry.

I think you should edit your question to indicate the range of errors, in your example it is happening at the end of the polyline, is that always the case or can it happen at the start and/or middle?


The answer submitted by @Barbarossa does exactly what I was asking in the question but I want to submit my own answer in addition to his as an alternative. It also contributes to the body of knowledge on the subject and is an option for the users that have a LP360 license or want to purchase one.

The solution I found is only partially true to the question and is not free. GeoCue, a small software vendor located in Huntsville, Alabama has a solution included in their LP360 software. LP360 is available as both a standalone Windows application and as an extension to ArcGIS. The Feature Analyst tool in LP360 includes a function they are calling Monotonic Hydro Drainage (Downhill/Uphill). This lets you test features for flow downhill and uphill and lets you see them in a table of vertices. It also provides the ability to interpolate the non-monotonic vertex to correct the error and ensure monotonicity.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.