Correcting monotonicity errors using ArcGIS Desktop
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
else:
#flat line
arcpy.AddMessage("Line {0} part {1} is flat. Skipping...".format(row[0],partNum))
partNum += 1
newGeom.add(part)
continue
zList = []
for i in xrange(len(part)):
pnt = part[i]
zList.append(pnt.Z)
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))
else:
# 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))
else:
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))
else:
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])
newPart.add(newPnt)
newGeom.add(newPart)
newShape = arcpy.Polyline(newGeom, None, True)
row[1] = newShape
cursor.updateRow(row)