Hello - I have been using the lovely Terrain Tools for a spat of recent topographic maps with great success, however I ran into some issues regarding the "Cluster Hillshade" function and can't figure out why.
No specific error code, but opening up the details shows this:
'C:\Users\accov\Documents\ArcGIS\Projects\WatershedsCAD\Terrain Mapping.tbx\ClusterHillshade'
isLicensed Syntax Error: File "C:\Users\accov\Documents\ArcGIS\Projects\WatershedsCAD\Terrain Mapping.tbx#ClusterHillshade.InitializeParameters.py", line 21
if inDEM:
TabError: inconsistent use of tabs and spaces in indentation
One thing I noticed is there was no alias for the toolbox, and the name had a space. So I created an alias called "TerrainMapping" to see if that changed anything. No such luck. Another thing is the "isLicensed Syntax Error" but I am logged into my organization with a spatial analyst license (among many others).
So I went into VSCode and looked for anything strikingly obvious, and everything looks decent. There were no errors, and the indents were consistent from my general python knowledge:
And here is the code sample in its entirety (it's not overly long).
# ---------------------------------------------------------------------------
# ClusterHillshade.py
#
# Based on a technique developed by:
# Fabio Veronesi and Lorenz Hurni (2014) and used with permission.
# Programmed by Linda Beale, Esri Inc
#
# Description:
# Further details are published in their paper:
# Veronesi, F. and Hurni, L. (2014) Changing the light azimuth in shaded relief
# representation by clustering aspect, The Cartographic Journal, 51, 4 pp291-300
# ---------------------------------------------------------------------------
import math
import arcpy
from arcpy import env
from arcpy.sa import *
def clusterHillshade(inDEM, outRaster, zFactor, majFW, meanFW, minL, maxL):
"""
clusterHillshade: calculates a hillshade raster from a DEM
Required arguments:
Inputs:
inDEM -- Input DEM.
zFactor -- No. of ground x,y units in one surface z unit.
majFW -- majority filter smoothing.
meanFW -- mean filter smoothing.
minL -- maximum light direction.
maxL -- minimum light direction.
Outputs:
outRaster -- cluster shaded relief.
"""
try:
# Check out the Spatial Analyst license
arcpy.CheckOutExtension("Spatial")
# Inputs
zenAngle = 45
# Degrees to radians calculation
deg2rad = math.pi / 180
# Process: Aspect and slope
AspectDeg = Aspect(inDEM)
AspectClass = IsoClusterUnsupervisedClassification(AspectDeg, 4, 20, 10)
# 9.01248403045503E-06
SlopeDeg = Slope(inDEM, "DEGREE", zFactor)
SlopeRad = SlopeDeg * deg2rad
# Process: Zenith and smoothing
Zenith = Con(SlopeDeg >= 0, (90 - int(zenAngle)) * deg2rad)
FocalAspect = FocalStatistics(AspectClass, majFW, "MAJORITY")
MeanCluster = FocalStatistics(FocalAspect, meanFW, "MEAN")
# Process: Azimuth Calculator
Azimuth = ((float(maxL) - float(minL)) / 2) * Sin(math.pi * MeanCluster + 1.570796) + (float(maxL) - ((float(maxL) - float(minL)) / 2))
# Process: Calculate shaded relief
Shade = ((Cos(Float(Zenith)) * Cos(Float(SlopeRad))) + (Sin(Float(Zenith)) * Sin(Float(SlopeRad)) * Cos((Float(Azimuth) * deg2rad) - Float(AspectDeg * deg2rad))))
ShadeRelief = Con(Float(Shade) >= 0, 255 * Float(Shade), 0)
# Process: Save outputs
ShadeRelief.save(outRaster)
arcpy.SetParameterAsText(1, outRaster)
except arcpy.ExecuteError:
arcpy.AddMessage(arcpy.GetMessages(2))
# End main function
if __name__ == '__main__':
args = tuple(arcpy.GetParameterAsText(i) for i in range(arcpy.GetArgumentCount()))
clusterHillshade(*args)
So at the bottom theres the *args that is looking for parameters from clusterHillshade, and I suspect something is going on with the inDEM parameter but that's as far as I can take it. The main hangup appears to be something where "inDEM" is not accessed whereas "inDEM" isn't greyed out for a different script.
if __name__ == '__main__':
args = tuple(arcpy.GetParameterAsText(i) for i in range(arcpy.GetArgumentCount()))
clusterHillshade(*args)
def clusterHillshade(inDEM, outRaster, zFactor, majFW, meanFW, minL, maxL):
"""
clusterHillshade: calculates a hillshade raster from a DEM
Required arguments:
Inputs:
inDEM -- Input DEM.
zFactor -- No. of ground x,y units in one surface z unit.
majFW -- majority filter smoothing.
meanFW -- mean filter smoothing.
minL -- maximum light direction.
maxL -- minimum light direction.
Outputs:
outRaster -- cluster shaded relief.
"""
If anyone has any insight that would be wonderful! Hopefully it isn't just outdated and incompatibly with ArcGIS Pro 3.2.2
Thanks in advance!
The error is pretty straightforward, it looks like. (And I get bitten by it all the time in the Python Window).
Python relies on indentation to determine loops, etc.
You can use either tabs or spaces. A tab frequently looks like the same space as 4 or 8 spaces depending on the program you're using, but is considered to be an entirely different length.
To fix it, open up the script in a text editor (I like Notepad++) and make sure that you can view whitespace chars. Find and replace all the tabs with spaces.
As an aside, many text editors will just turn tab key presses into spaces to avoid this problem. That, or if you have a tab, tabbing the line will turn it into spaces.
I have an update, while poking around the validate tab on the script properties, I noticed some errors with regards to the spacing and indents:
import arcpy
class ToolValidator(object):
"""Class for validating a tool's parameter values and controlling
the behavior of the tool's dialog."""
def __init__(self):
"""Setup arcpy and the list of tool parameters."""
self.params = arcpy.GetParameterInfo()
def initializeParameters(self):
"""Refine the properties of a tool's parameters. This method is
called when the tool is opened."""
return
def updateParameters(self):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
inDEM = self.params[0].value
if self.params[2].value:
if inDEM:
if arcpy.Describe(inDEM).spatialReference.type == "Geographic":
self.params[2].value = 9.01248403045503E-06
if arcpy.Describe(inDEM).spatialReference.type == "Projected":
self.params[2].value = 1
return
def updateMessages(self):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
inFC = self.params[0].value
self.params[0].clearMessage()
if self.params[0].value is None:
self.params[0].clearMessage()
else:
if inFC:
desc = arcpy.Describe(inFC)
sr = desc.spatialReference
if sr.type == "Geographic":
self.params[0].setErrorMessage("Data needs to be in a Projected Coordinate System")
else:
self.params[0].clearMessage()
return
So the error lies within the validation. It seems that there is an included validation step for "inDEM" to ensure it is in a projected coordinate system, and to convert it to a projection. All I ended up doing was replacing the the updateParameters so that it showed this:
def updateParameters(self):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
return
Instead of this:
def updateParameters(self):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
inDEM = self.params[0].value
if self.params[2].value:
if inDEM:
if arcpy.Describe(inDEM).spatialReference.type == "Geographic":
self.params[2].value = 9.01248403045503E-06
if arcpy.Describe(inDEM).spatialReference.type == "Projected":
self.params[2].value = 1
return
Seems to work now, hope this helps people in a similar boat!