Select to view content in your preferred language

updateLayer loop with .lyr does not use target field

3298
9
05-17-2017 12:36 PM
RaleighMyers
Emerging Contributor

As a python noob, I'm attempting to create pdfs of layouts in which polygon features are symbolized with the same colors for classes, but the assignments change in each of 1,000 fields in the feature class. That is, I want to loop through each field (00000, 00001, 00002...), symbolize the categories (1-13) as set up in a .lyr file, change the map title to match the field name, print a pdf of the layout, & move to the next field to do it again.

What I have will change the map title & print the pdf with the title. The problem is every map is the same, except for the title. The polygons aren't given symbology according to the .lyr file, they're identical to the .lyr file.

I don't think it should matter, but the shapefile I'm symbolizing has 2692 rows. I need to display one field at a time, with each polygon (row) in the field shaded a specific color, depending on it's value, 1-13.

Help please?

>>> import arcpy  
... import os  
... mxd = arcpy.mapping.MapDocument("CURRENT")   
... df = arcpy.mapping.ListDataFrames(mxd, "*")[0]    
... featureclass = arcpy.mapping.ListLayers(mxd, "*", df)[2]   
... symbologyLyr = arcpy.mapping.Layer(r"\\path\Maps\SymbologyShp.lyr")  
... field_names = [f.baseName for f in arcpy.ListFields(featureclass)] 
... for f in field_names:          
...     if f.startswith("Map"):        
...                 arcpy.mapping.UpdateLayer(df, featureclass, symbologyLyr, True)       
...                 TextElement = arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT", "MapTitleText")[0]               
...                 TextElement.text = f                 
...                 arcpy.mapping.ExportToPDF(mxd, r"\\path\outputPDF\Map_" + f)
0 Kudos
9 Replies
BlakeTerhune
MVP Regular Contributor

At first glance, you could include that if statement in the generator expression so you only get the "Map" fields to loop through.

field_names = [f.baseName for f in arcpy.ListFields(featureclass) if f.startswith("Map")]

I don't see an issue with the rest of your code but I'm not an arcpy.mapping expert.

0 Kudos
IanMurray
Honored Contributor

A layer file usually has the symbology based on either the feature itself or a specific field.  I'm not sure how a single layer file could possibly take care of what you are needing it to do, since you would only be able to select a single field, not iterate over them.  You quite literally would need a layer file for each field to do things as you are currently proposing.

http://desktop.arcgis.com/en/arcmap/10.3/tools/data-management-toolbox/apply-symbology-from-layer.ht...

"The field in the Input Layer that will be displayed must have the same name as that of the corresponding Symbology Layer field. If this field is missing, the output data is drawn with default symbology."

Actually I see you used updateLayer, but see this line

"Depending on the renderer (for example, unique value using a particular attribute), the attribute definitions also need to be the same. "

A possible solution for you to look into is to use the Unique Values Symbology Class with your feature class layer(Assuming you are using unique values).  Once the symbology has been applied from the layer file(you should only need to do this once), you can switch which field is used for the symbology, but since you are using the same 1-13 values for each field, it may carry the symbology over when you set the new field for symbolizing.

http://desktop.arcgis.com/en/arcmap/10.3/analyze/arcpy-mapping/uniquevaluessymbology-class.htm

See example 1 about how to access and change the symbology field.

I've never tried this or tested it, but I think it would work for you.  Any additional information about the type of symbology you are using, example of the output of your script, and how your layer file is set-up would be useful if that doesn't work. 

Also if it does work, please make sure to Refresh the active view prior to each export.

http://desktop.arcgis.com/en/arcmap/10.3/analyze/arcpy-functions/refreshactiveview.htm

Edit: Similar threads relevant to question

https://community.esri.com/thread/79316

https://community.esri.com/thread/64140

RaleighMyers
Emerging Contributor

Ian, thank you very much for your reply. I looked into your suggestions and tried using Unique Values Symbology. I symbolized on field 'Map00001' using the .lyr file with the desired colors. Then I ran this:

>>> import arcpy
... mxd = arcpy.mapping.MapDocument("CURRENT")  
... df = arcpy.mapping.ListDataFrames(mxd, "*")[0]   
... featureclass = arcpy.mapping.ListLayers(mxd, "*", df)[2]
... if featureclass.symbologyType == "UNIQUE_VALUES":
...     featureclass.symbology.valueField = "Map00002"
...     featureclass.symbology.addAllValues()
... arcpy.RefreshActiveView()    
... arcpy.RefreshTOC()

It changed the symbology to match Map00002 polygons, but it did not use the .lyr file symbology. It used the first color ramp available in the pulldown in the symbology tab of layer properties. In other words, the geography is right but none of the correct colors are used at all.

I'll attempt to answer your questions. The symbology is RGB. 1= 0, 73, 73. 2= 0, 146, 146. 3= 255, 109, 182. 4= 255, 182, 219. 5= 0, 109, 219. etc. for each of the 13 classes. (color blindness acceptable colors). To create the layer file I opened the layer properties dialog to the symbology tab for the shapefile. I set it up with Categories set to unique values, with the value field set to Map00001. I set the color for each of the 13 classes manually to the RGB values like those above. I then right-clicked on the shapefile in the TOC and chose save as layer file.

As for an example of output from the script would you want to see images or pdfs of the layout?

I wonder if there's a way to code which RGB value each class 1-13 should be?

0 Kudos
IanMurray
Honored Contributor

Thanks for the additional information.

"I wonder if there's a way to code which RGB value each class 1-13 should be?"

Not with arcpy, unless you want to get into ArcObjects from my understanding

"It changed the symbology to match Map00002 polygons, but it did not use the .lyr file symbology. It used the first color ramp available in the pulldown in the symbology tab of layer properties."

I just tested this, changing the value field to another field with the same values for my dataset, but I was hoping without addallvalues() it would keep the same symbology, but unfortunately it does not.  Once you change the field it removes the old symbology.

There are some ways around this of course but will take a fair bit more of coding.

The limit of the .lyr file is that it is designed to work off a single field, and in your case you have 1000 fields you want to symbolize with in turn.

One way would be to use the make feature layer tool, delete all the fields in that output feature layer but the one you want to symbolize off of, then rename that field to the one used in the .lyr file and turn off the existing layer on the map.  In theory that could do what you need.  It would require writing a new field info object each time to use in the make feature layer.

http://desktop.arcgis.com/en/arcmap/10.3/tools/data-management-toolbox/make-feature-layer.htm#ESRI_U...

http://pro.arcgis.com/en/pro-app/arcpy/classes/fieldinfo.htm

Another would to make a copy of the dataset you are using, then read the values for each of the fields into memory, then use an update cursor to overwrite the field the values in the field used by your symbology .lyr with the values from the other fields one at a time (Don't want to ruin your original dataset).  I'd probably write them to a dictionary and use that with the update cursor to overwrite the field values

http://desktop.arcgis.com/en/arcmap/10.3/analyze/arcpy-data-access/updatecursor-class.htm

/blogs/richard_fairhurst/2014/11/08/turbo-charging-data-manipulation-with-python-cursors-and-diction...

Anyone else with suggestions?

Dan_Patterson‌, xander_bakker‌, bixb0012‌, rfairhur24

XanderBakker
Esri Esteemed Contributor

Normally when you apply the symbology from a layer or layer file, you can select the field to base the symbology on. The method arcpy.mapping.UpdateLayer does not have a parameter to do that. 

What you could do, although it sounds little efficient, is to create a temp field, base the symbology on that field and fill the field with the values of the field you are trying to map.

IanMurray
Honored Contributor

I'm not sure within the Apply Symbology from Layer Tool you can specify the field.

From the Apply Symbology from Layer

"The field in the Input Layer that will be displayed must have the same name as that of the corresponding Symbology Layer field. If this field is missing, the output data is drawn with default symbology."

"You can name a field in the Input Layer to match the Symbology Layer field name using the Make_Feature_Layer tool."

Hence why I suggested a work-around of use Make Feature Layer.

IanMurray
Honored Contributor

By the way, if you need any help trying to write any of the code let me know I'd be glad to help out with it.

JoshuaBixby
MVP Esteemed Contributor

After reading through the comments, I wasn't sure what comment to nest mine under, so I decided to reply to the original question.

There are a couple, at least a couple I know of, ways to accomplish what you want.  One is a bit, just a bit, kludgy but it mostly uses supported/documented functions of the API.  The other is very straightforward but heavily relies on undocumented aspects of the API.

First way... udpateLayerFromJSON.  The updateLayerFromJSON method has been around since 10.3.x, and it is quite robust, but there are a couple of issues with it.  First, there is a bit of a learning curve, especially if you aren't familiar with JSON.  Second, there is no layerFromJSON equivalent to see a layer's structure in JSON.  It is always easier to modify valid JSON than to gin up JSON from scratch based on documentation.

The first issue I can't help with, there is no getting around the learning curve.  There is a way around the second issue; however, it relies on using an undocumented part of the ArcPy API.

import json

lyr =       # Layer object, typically retrieved using 
            #   arcpy.mapping.ListLayers (arcpy._mapping.Layer)
new_field = # New field for symbolizing layer (string)

sym_dict = json.loads(lyr._arc_object.getsymbology())
sym_dict["renderer"]["field1"] = new_field
lyr.updateLayerFromJSON(json.dumps({"drawingInfo": sym_dict}))
arcpy.RefreshActiveView()

Second way... _arc_object.renderer .  Some could argue the following is even more straightforward than the above, but it does rely even more on an undocumented part of the ArcPy API.  It is for this reason alone I favor the first method.

lyr =       # Layer object, typically retrieved using 
            #   arcpy.mapping.ListLayers (arcpy._mapping.Layer)
old_field = # Old field for symbolzing layer (string)
new_field = # New field for symbolizing layer (string)

sym_xml = lyr._arc_object.renderer
lyr._arc_object.renderer = sym_xml.replace(old_field, new_field)
arcpy.RefreshActiveView()

The above two examples are for changing only the symbology field and leaving everything else the same.  If there is a problem applying the symbology to a new field, the result is usually a fallback to single symbol.  As I already mentioned, I feel more comfortable using the first method becuase it only relies on an undocumented method to retrieve information, not to update it, but it still relies on an undocumented method.

Enguerranddes_Vaux
Regular Contributor

This is the most awsome answer that i seen on genoet since i'm on it. Thank you so much !!!