Select to view content in your preferred language

Update raster symbology using CIM in Pro

860
4
01-03-2023 05:09 PM
ZacharyUhlmann1
Frequent Contributor

As the title says I want to update break values via the CIM (preferably).  Ultimately I need to develop the skillset to update the break values of rasters in an Arc Pro map based off of different distributions and numbers of breakpoints.  I am struggling to understand the CIM, particularly with rasters.  I am very experienced using Python - looping, conditional statements, data types, and indexing of different data models (lists, arrays, etc.).  Can somebody pretend they are me they have an existing map with a raster stretch symbology and 7 classes.  How do I replace these with the values in this list and update the layer?

p = arcpy.mp.ArcGISProject(r'path/to/aprx')
m = p.listMaps("Map")[0]
l =  m.listLayers(r"raster_layer")[0]
symb = l.symbology
break_vals = [-3,-2,-1,0,2,4,6]
# 7 break points
symb.break_vals = 7
for idx, brk in enumerate(symb.colorizer.classBreaks):
  brk.upperBound = break_vals(idx)

# Do I have to save a copy?  Why not set definition as with l.getDefinition('V2')
p.saveACopy('path/to/aprx_copy.aprx')

 Note that I haven't been able to execute this yet because the colorizer property in my layer does not have a classBreaks attribute for some reason?  So line 9 (brk.upperBound) generates an Error.

AttributeError: 'RasterStretchColorizer' object has no attribute 'classBreaks' 

Symbology Properties from the raster in my TOC.  It's a File Geodatabase Raster

raster_colorramp.png

I am loosely following the second code sample here:

https://pro.arcgis.com/en/pro-app/latest/arcpy/mapping/rasterclassifycolorizer-class.htm

Thanks - Zach

 

Tags (3)
0 Kudos
4 Replies
DanielMiranda2
Regular Contributor

The error mentions that your raster layer is using a RasterStretchColorizer and not a RasterClassifyColorizer, which is why there is no upper bound. Are you sure you are setting your l variable to the correct layer? That's the only obvious problem that comes to mind.

 

To apply the symbology and not save a copy of the project, you could do something like this after your loop. No need to retrieve the CIM, which as a general rule, I avoid at all costs if I can.

l.symbology = symb
p.save()

 

0 Kudos
ZacharyUhlmann1
Frequent Contributor

Hi @DanielMiranda2 - I was able to set my classBreaks.  Not sure the issue, but when loading the map like this:

p = arcpy.mp.ArcGISProject(r'current')

as opposed to this:

p = arcpy.mp.ArcGISProject(r'path/to/aprx')

Allowed me to access the CIM for the current project.  I knew this was the real issue - that I was not accessing my project and no updates were being saved to the CIM despite assigning new attributes to l.symbology or via the getDefinition method (see below). Still not sure why providing the full path as with my second code snippet did not work??

But!  I did not used the "getDefintion" method.  

cim_lyr = l.getDefinition('V2')
cim_lyr.colorizer.classBreaks

NOT the symbology class

l.symbology = symb
p.save('path/to/pro_project_copy.aprx')

 THAT difference I still don't understand?  Why not just use getDefinition?  I don't want to save a copy, instead I want to update the map.  

If you understand the difference between those two CIM access methods OR can identify why full/path/to/aprx argument did not work, that would be appreciated.

Thanks - Zach

0 Kudos
DanielMiranda2
Regular Contributor

When you use the 'Current' keyword, you have access to the project exactly as you see it in your ArcGIS Pro session. If you access it from a file path, it is accessing whatever version is on the disk. So, perhaps, if changes were made in Pro but not saved, then the script was accessing a "stale" version still on disk?

It is hard to tell for sure. But, your script with the file path was still finding a raster, it just used stretch symbology instead of classify based on your script error. So maybe the project on disk had yet to be saved with the raster having classify symbology?

In regard to saving a copy of the project vs not, the .save() method will just save the current project without making a copy. Whereas .saveACopy(file_name) will save a new copy to the file_name path. So, you have the option to do either or.

This will apply your symbology and save the project without making a copy:

 

l.symbology = symb
p.save()

 

Note that if you are using the 'Current' project, you do not need to save the project to actually see the changes in your session. You can just use line 1. Though, at some point, the changes need to be saved.

 

This will apply your symbology saved to a copy of the project:

 

l.symbology = symb
p.saveACopy('path/to/pro_project_copy.aprx')

 

 

For the CIM, there is nothing stopping you from going that way, but with all the warnings posted in the official documentation about the possibility of breaking things, I personally only use it if the "standard" arcpy objects and methods won't get me what I need.

0 Kudos
ZacharyUhlmann1
Frequent Contributor

Hi @DanielMiranda2 thanks again for the great direction.  In regards to saving the aprx prior to loading it...not the case.  Definitely saved - multiple times.  Perhaps there is an error/bug in Pro (3.0.2) or a different issue with my code.  I'll troubleshoot next time I attempt this workflow.  

Aaaahh - so there is simply a .save() method?!  Seemed all the ESRI documentation and forum resources I found solely used .saveAsCopy() so I assumed that was the only protocol.

Big tip on the CIM frontier - only use when not included in "standard arcpy objects".  I certainly disregarded the warnings in documentation and forums on the perils of editing via the CIM.  I need to do more research, but that's a great tip.  Having said that, in this case I used the CIM because that's where I found the most success with the .classBreaks property and also my familiarity with accessing the CIM vs. l.symbology.  I may respond if I get another chance to explore l.symbology and standard arcpy for the same solution outside of the CIM frontier.