I thought I had this figured out but it came back again.
In Pro if you try to set symbology outside the layer values it gets mad. This is actually highly annoying because then we can never set consistent colors.
In code it seems to let me do it unless the scale is really small. Like -0.0003 to 0.0003 the classify values below with fail. Again highly annoying.
So here I try to set 10 breaks. If that fails I default to generic symbols. Again I really do not want to do that as then the user cannot compare anything.
sym = rasterLayer.symbology
sym.updateColorizer('RasterClassifyColorizer')
# was Condition Number
sym.colorizer.colorRamp = project.listColorRamps('Prediction')[0]
sym.colorizer.classificationField = "Value"
sym.colorizer.breakCount = 10
#sym.colorizer.noDataColor = {'RGB': [255, 255, 255, 100]}
# manual breaks
# this will fail on small time periods
if len(sym.colorizer.classBreaks) == 10:
sym.colorizer.classBreaks[0].upperBound = -100
sym.colorizer.classBreaks[0].label = "Over -40%"
sym.colorizer.classBreaks[1].upperBound = -40
sym.colorizer.classBreaks[1].label = "-30% to -40%"
sym.colorizer.classBreaks[2].upperBound = -30
sym.colorizer.classBreaks[2].label = "-20% to -30%"
sym.colorizer.classBreaks[3].upperBound = -20
sym.colorizer.classBreaks[3].label = "-10% to -20%"
sym.colorizer.classBreaks[4].upperBound = -10
sym.colorizer.classBreaks[4].label = "0% to -10%"
sym.colorizer.classBreaks[5].upperBound = 10
sym.colorizer.classBreaks[5].label = "0% to 10%"
sym.colorizer.classBreaks[6].upperBound = 20
sym.colorizer.classBreaks[6].label = "10% to 20%"
sym.colorizer.classBreaks[7].upperBound = 30
sym.colorizer.classBreaks[7].label = "20% to 30%"
sym.colorizer.classBreaks[8].upperBound = 40
sym.colorizer.classBreaks[8].label = "30% to 40%"
sym.colorizer.classBreaks[9].upperBound = 100
sym.colorizer.classBreaks[9].label = "Over 40%"
else:
arcpy.AddMessage("Warning Trend Rasters may not work with short time frames. Trying reduced symbols.")
sym.updateColorizer('RasterStretchColorizer')
sym.colorizer.colorRamp = project.listColorRamps('Condition Number')[0]
sym.colorizer.stretchType = "StandardDeviation"
rasterLayer.name = "Total Change"
rasterLayer.symbology = sym
But today we had a case where the value range was -7 to 8 and the breaks did set to -40 to 40 like I want BUT the entire map all draws at a single color. So my check to look for not 10 breaks fix fails. If I ask it how many breaks it does say 10. And my bounds are correct. Worse it sets the color to the first blue - so now the user thinks its all in the >-40% category.
Any ideas on how to fix this? I am pretty confused why symbology will not let me set anything I want. We cannot have all the maps using a different color scale. That is weird.
thanks
Couldn't quite get all the way there, but I was able to consistently get the label classes to generate with this:
I forgot to re-apply raster_sym to raster_layer.symbology. This actually works now
Note: The docstring for build_breaks is outdated, originally I was skipping 0 like in your example, but it seems to add the 0 break to the end if you do that, so now you can pass a 'zero_break' argument to change the label for 0 (default is 'No Change')
import arcpy
from arcpy._mp import ArcGISProject, Layer, ColorRamp, Symbology
from arcpy._symbology import RasterClassifyColorizer
from arcpy.cim import (
CIMRasterLayer,
CIMRasterClassifyColorizer,
CIMRasterClassBreak,
ClassificationMethod,
)
def build_breaks(min_break: int, max_break: int, total_breaks: int,
units: str = '%',
zero_break: str = 'No Change') -> list[tuple[int, str]]:
"""Build breaks and labels for the colorizer
Args:
min_break (int): The minimum break value (> -100)
max_break (int): The maximum break value (< 100)
total_breaks (int): The total number of breaks
skip_zero (bool, optional): Skip the zero break if it is in the step. Defaults to True.
"""
# Raise some errors if the input is invalid
if min_break > max_break:
raise ValueError("min_break must be less than max_break")
if min_break <= -100:
raise ValueError("min_break must be greater than -100")
if max_break >= 100:
raise ValueError("max_break must be less than 100")
# Calculate the step (total_breaks has the 2 extremes removed)
step = int((max_break - min_break) // (total_breaks-2))
breaks = []
# Add minimum extreme
breaks.append((-100, f"Under {min_break}{units}"))
# Build the breaks
for upper_bound in range(min_break, max_break, step):
if upper_bound == 0:
breaks.append((upper_bound, zero_break))
continue
breaks.append((upper_bound, f"{upper_bound+step}{units} to {upper_bound}{units}"))
# Add maximum extreme
breaks.append((100, f"Over {max_break}{units}"))
return breaks
def main():
project: ArcGISProject = ArcGISProject(r"<path_to_aprx>")
raster_layer: Layer = project.listMaps()[0].listLayers('<layer_name>')[0]
layer_cim: CIMRasterLayer = raster_layer.getDefinition('V3')
# Build the class breaks
class_breaks = [CIMRasterClassBreak() for _ in range(10)]
for (val, label), class_break in zip(build_breaks(-40, 40, 10), class_breaks):
class_break: CIMRasterClassBreak = class_break
class_break.label = label
class_break.upperBound = val
# Build the colorizer
colorizer = CIMRasterClassifyColorizer()
colorizer.classificationMethod = ClassificationMethod.Manual
colorizer.classBreaks = class_breaks
colorizer.field = "Value"
layer_cim.colorizer.__dict__.update(colorizer.__dict__)
raster_layer.setDefinition(layer_cim)
raster_sym: Symbology = raster_layer.symbology
sym_colorizer: RasterClassifyColorizer = raster_sym.colorizer
color_ramp: ColorRamp = project.listColorRamps('Prediction')[0]
sym_colorizer.colorRamp = color_ramp
raster_layer.symbology = raster_sym
# Save the project
project.save()
Sorry but I do not get your post. I need the breaks to be exact not based on the data range at all. All rasters need to be exactly the same symbology.
This is getting a lot of attention at the office and I am still stuck. For now I test to see if the range is less than 20 and I set it to a generic scale. Not sure why it ignores it or how to tell it did ignore it.
thanks
I added a check for small ranges but now that is also failing.
arcpy.management.GetRasterProperties(rasterLayer, "MAXIMUM") will fail if the range is really small but works just fine if the range is larger.
Something is up here.
@DougBrowning I've taken a look at this and I'm also bringing this to the attention of the raster team. A few things.
1) With Python, I'm able to standardize symbology across multiple rasters BUT only if I set the lowerBound of the raster. This property was made available on GraduatedColorsRenderer, GraduatedSymbolsRenderer and RasterClassifyColorizer at Pro 3.4.
#Standardize multiple RasterClassifyColorizer elevation datasets
p = arcpy.mp.ArcGISProject('current')
m = p.listMaps('Yosemite Elevation')[0]
for l in m.listLayers('*dem'):
if l.isRasterLayer:
lBound = 0
uBound = 300
sym = l.symbology
sym.colorizer.colorRamp = p.listColorRamps('Elevation #1')[0]
sym.colorizer.breakCount = 10 #final range: 0-3000
sym.colorizer.lowerBound = 0
for brk in sym.colorizer.classBreaks:
brk.upperBound = uBound
brk.label = f"{lBound} - {uBound} meters"
lBound += 300
uBound += 300
l.symbology = sym
2) It appears I can't do this in the UI. The UI does not let me create ranges below the minimum value. It lets me create ranges for upper values that don't exist with no issue. If I try to set the lower bound via the Histogram tab but changing it to Zero has no effect on the colorizer (Unlike the GraduatedColors/symbols renderers). Even if I successfully set symbology via arcpy.mp like above, when I view the symbology pane, I get a warning.
3) There is a bug you may run into concerning labels. During Pro 3.4 development, auto apply labels behavior was added to the UI behavior and it causes arcpy.mp class label modifications to get reset. We have a fix for this at Pro 3.5 and hope to port it to a patch for 3.4. The bug is BUG-000174482.
I hope this helps,
Jeff - arcpy.mp and Layout (SDK) teams