Select to view content in your preferred language

extract hex codes from layer symbology using arcpy loop

1262
7
Jump to solution
01-24-2024 12:08 PM
ChrisGiamarino
Occasional Contributor

Hi,

I am a GIS Analyst/Urban Planner working with land use data. I have a standard land use symbology for use in different cities and counties. 

I am trying to extract the hex codes from the layer symbology and add them as a new field. The script from this solution post by @JohannesLindner . The script works to add the label of my symbology layer to a new field: 

import arcpy
layer_names = ["PW Zoning"]
aprx = arcpy.mp.ArcGISProject("current")

for name in layer_names:
    # get the layer
    layer = aprx.activeMap.listLayers(name)[0]
    # get the symbology fields and items
    renderer = layer.symbology.renderer
    fields = renderer.fields
    items = renderer.groups[0].items
    # create a dict {values: label}
    item_dict = {tuple(i.values[0]): i.label for i in items}
    # add a new field
    arcpy.management.AddField(layer, "PW_Zoning_Designation", "TEXT")
    # insert the label values
    with arcpy.da.UpdateCursor(layer, ["PW_Zoning_Designation"] + fields) as cursor:
        for row in cursor:
            key = tuple([str(x) for x in row[1:]])  # item.values stores values as string
            try:
                row[0] = item_dict[key]
                cursor.updateRow(row)
            except KeyError:  # no symbology found
                pass

I am curious how I would add the hex codes into this code to add a new field that would include the six-digit hex codes; RGB codes work too. Thanks for any help. I am new to arcpy and will be attending the Developer Conference in two months.

symbologyHex.png

0 Kudos
1 Solution

Accepted Solutions
JohannesLindner
MVP Frequent Contributor

The original script was for Unique Values from multiple fields. You have Unique Values from one field in groups. These are represented differently in Python.

This should work:

layer_names = ["TestPolygons"]
aprx = arcpy.mp.ArcGISProject("current")

for name in layer_names:
    # get the layer
    layer = aprx.activeMap.listLayers(name)[0]
    # get the symbology fields and items
    renderer = layer.symbology.renderer
    fields = renderer.fields
    items = renderer.groups[0].items
    # create a dict {values: label}
    item_dict = dict()
    for item in items:
        for v in item.values:
            item_dict[v[0]] = item.label
    # add a new field
    arcpy.management.AddField(layer, "LandUseDesignation1", "TEXT")
    # insert the label values
    with arcpy.da.UpdateCursor(layer, ["LandUseDesignation1"] + fields) as cursor:
        for row in cursor:
            key = row[-1]
            try:
                row[0] = item_dict[key]
                cursor.updateRow(row)
            except KeyError:  # no symbology found
                pass

 

We have to change how we build the item_dict (lines 12-15) and how  to get values from it (line 21).


Have a great day!
Johannes

View solution in original post

7 Replies
JohannesLindner
MVP Frequent Contributor

As it turns out, getting a symbol's color is easy, but there's no built-in way to get the hex value...

 

If you have defined all your symbol colors in the RGB system, you're good to go:

layer_names = ["TestPolygons"]
aprx = arcpy.mp.ArcGISProject("current")

for name in layer_names:
    # get the layer
    layer = aprx.activeMap.listLayers(name)[0]
    # get the symbology fields and items
    renderer = layer.symbology.renderer
    fields = renderer.fields
    items = renderer.groups[0].items
    # create a dict {values: iteml}
    item_dict = {tuple(item.values[0]): item for item in items}
    # add a new field
    arcpy.management.AddField(layer, "PW_Zoning_Designation", "TEXT")
    arcpy.management.AddField(layer, "ColorRaw", "TEXT")
    arcpy.management.AddField(layer, "ColorHex", "TEXT")
    
    # insert the label and color values
    with arcpy.da.UpdateCursor(layer, ["PW_Zoning_Designation", "ColorRaw", "ColorHex"] + fields) as cursor:
        for row in cursor:
            # get the item
            key = tuple([str(x) for x in row[3:]])  # item.values stores values as string
            try:
                item = item_dict[key]
            # no item found -> default value and skip to the next feature
            except KeyError:
                row[0] = "No  Symbol"
                cursor.updateRow(row)
                continue
            # store the item's label
            row[0] = item.label
            # get the symbol color
            color = item.symbol.color
            color_type = list(color.keys())[0]
            # if it's rgb, convert to hex
            if color_type == "RGB":
                rgb = color["RGB"][:3]
                row[2] = '#%02x%02x%02x' % tuple(rgb)
            # if it's something else (hsv, hsl, cmyk), you have to convert that in other ways... here I just don't convert
            else:
                row[2] = None
            # store the color value as string for documentation
            row[1] = str(color)
            # write values
            cursor.updateRow(row)

 

Note that we're storing the whole symbology item in the dictionary (line 12), not only the label. This way, we can access the item's symbol color in the for-loop (line 33).

Colors can be defined in different systems (RGB, HSV, HSL, CMYK, ...). Converting from RGB to hex is pretty straightforward (line 38). If you have other color systems, you probably have to convert those values into RGB first (line 41). A good start could be the module colorsys, which is part of the default Python installation. It might be easier to go through your symbology and just switch the system there.

JohannesLindner_0-1706133979264.png

 

JohannesLindner_1-1706134019351.png

 


Have a great day!
Johannes
ChrisGiamarino
Occasional Contributor

whoa! thank you so much @JohannesLindner 

one last quick question...when I run the initial code on Grouped Values, it only searches for the first value in the group and applies the new Label. See screenshot...how can I get the initial code to search through each land use code after the semi-colon and add the new label? 

landUse_loop.png

sample code again:

import arcpy 

layer_names = ["ZoningPlacerCo"]
aprx = arcpy.mp.ArcGISProject("current")

for name in layer_names:
    # get the layer
    layer = aprx.activeMap.listLayers(name)[0]
    # get the symbology fields and items
    renderer = layer.symbology.renderer
    fields = renderer.fields
    items = renderer.groups[0].items
    # create a dict {values: label}
    item_dict = {tuple(i.values[0]): i.label for i in items}
    # add a new field
    arcpy.management.AddField(layer, "LandUseDesignation1", "TEXT")
    # insert the label values
    with arcpy.da.UpdateCursor(layer, ["LandUseDesignation1"] + fields) as cursor:
        for row in cursor:
            key = tuple([str(x) for x in row[1:]])  # item.values stores values as string
            try:
                row[0] = item_dict[key]
                cursor.updateRow(row)
            except KeyError:  # no symbology found
                pass
0 Kudos
JohannesLindner
MVP Frequent Contributor

The original script was for Unique Values from multiple fields. You have Unique Values from one field in groups. These are represented differently in Python.

This should work:

layer_names = ["TestPolygons"]
aprx = arcpy.mp.ArcGISProject("current")

for name in layer_names:
    # get the layer
    layer = aprx.activeMap.listLayers(name)[0]
    # get the symbology fields and items
    renderer = layer.symbology.renderer
    fields = renderer.fields
    items = renderer.groups[0].items
    # create a dict {values: label}
    item_dict = dict()
    for item in items:
        for v in item.values:
            item_dict[v[0]] = item.label
    # add a new field
    arcpy.management.AddField(layer, "LandUseDesignation1", "TEXT")
    # insert the label values
    with arcpy.da.UpdateCursor(layer, ["LandUseDesignation1"] + fields) as cursor:
        for row in cursor:
            key = row[-1]
            try:
                row[0] = item_dict[key]
                cursor.updateRow(row)
            except KeyError:  # no symbology found
                pass

 

We have to change how we build the item_dict (lines 12-15) and how  to get values from it (line 21).


Have a great day!
Johannes
ChrisGiamarino
Occasional Contributor

epic! thank you. I am familiar with geopandas and folium. arcpy is new to me. I appreciate your explanations and comments (#)

0 Kudos
ChrisGiamarino
Occasional Contributor

sorry, quick question @JohannesLindner . how would you combine adding the color code with multiple fields with this new code that searches for grouped values? 

specifically updating the code below with the item_dict (lines 12-15) and line 21:

 

layer_names = ["TestPolygons"]
aprx = arcpy.mp.ArcGISProject("current")

for name in layer_names:
    # get the layer
    layer = aprx.activeMap.listLayers(name)[0]
    # get the symbology fields and items
    renderer = layer.symbology.renderer
    fields = renderer.fields
    items = renderer.groups[0].items
    # create a dict {values: iteml}
    item_dict = {tuple(item.values[0]): item for item in items}
    # add a new field
    arcpy.management.AddField(layer, "PW_Zoning_Designation", "TEXT")
    arcpy.management.AddField(layer, "ColorRaw", "TEXT")
    arcpy.management.AddField(layer, "ColorHex", "TEXT")
    
    # insert the label and color values
    with arcpy.da.UpdateCursor(layer, ["PW_Zoning_Designation", "ColorRaw", "ColorHex"] + fields) as cursor:
        for row in cursor:
            # get the item
            key = tuple([str(x) for x in row[3:]])  # item.values stores values as string
            try:
                item = item_dict[key]
            # no item found -> default value and skip to the next feature
            except KeyError:
                row[0] = "No  Symbol"
                cursor.updateRow(row)
                continue
            # store the item's label
            row[0] = item.label
            # get the symbol color
            color = item.symbol.color
            color_type = list(color.keys())[0]
            # if it's rgb, convert to hex
            if color_type == "RGB":
                rgb = color["RGB"][:3]
                row[2] = '#%02x%02x%02x' % tuple(rgb)
            # if it's something else (hsv, hsl, cmyk), you have to convert that in other ways... here I just don't convert
            else:
                row[2] = None
            # store the color value as string for documentation
            row[1] = str(color)
            # write values
            cursor.updateRow(row)

 

0 Kudos
ChrisGiamarino
Occasional Contributor

@JohannesLindner 

here is my sample code trying to iterate through groups and apply color column value attributes. I am getting an index error saying 'IndexError: list index out of range'

layer_names = ["ZoningPlacerCo"]
aprx = arcpy.mp.ArcGISProject("current")

for name in layer_names:
    # get the layer
    layer = aprx.activeMap.listLayers(name)[0]
    # get the symbology fields and items
    renderer = layer.symbology.renderer
    fields = renderer.fields
    items = renderer.groups[0].items
    # create a dict {values: iteml}
    item_dict = dict()
    for item in items:
        for v in item.vlues:
            item_dict[v[0]] = item.label
    # add a new field
    arcpy.management.AddField(layer, "PW_Zoning_Designation", "TEXT")
    arcpy.management.AddField(layer, "ColorRaw", "TEXT")
    arcpy.management.AddField(layer, "ColorHex", "TEXT")
    
    # insert the label and color values
    with arcpy.da.UpdateCursor(layer, ["PW_Zoning_Designation", "ColorRaw", "ColorHex"] + fields) as cursor:
        for row in cursor:
            # get the item
            key = tuple([str(x) for x in row[3:]])  # item.values stores values as string
            try:
                item = item_dict[key]
            # no item found -> default value and skip to the next feature
            except KeyError:
                row[0] = "No  Symbol"
                cursor.updateRow(row)
                continue
            # store the item's label
            row[0] = item.label
            # get the symbol color
            color = item.symbol.color
            color_type = list(color.keys())[0]
            # if it's rgb, convert to hex
            if color_type == "RGB":
                rgb = color["RGB"][:3]
                row[2] = '#%02x%02x%02x' % tuple(rgb)
            # if it's something else (hsv, hsl, cmyk), you have to convert that in other ways... here I just don't convert
            else:
                row[2] = None
            # store the color value as string for documentation
            row[1] = str(color)
            # write values
            cursor.updateRow(row)

 the error is on line ten: items = renderer.groups[0].items

 

ChrisGiamarino_0-1706202700439.png

 

0 Kudos
JohannesLindner
MVP Frequent Contributor

I'm not quite sure what you mean by combine. Do you want to also get the colors from a symbology with grouped unique values? Or do you have layers with grouped values from multiple fields? Eh, let's do both...

 

To extract the item dict for different combinations of (multiple fields) and (grouped values), we have to generalize a bit. Let's take a look at how these combinations are represented in Python:

 

# 4 layers with different Unique Value symbologies
layer_names = [
    "unique",  # just a plain UV symbology
    "unique_grouped",  # UV from a single field, but in groups
    "unique_multiple", # UV from multiple fields, ungrouped
    "unique_multiple_grouped",  # UV from multiple fields, grouped
    ]
aprx = arcpy.mp.ArcGISProject("current")

for name in layer_names:
    layer = aprx.activeMap.listLayers(name)[0]
    renderer = layer.symbology.renderer
    first_item = renderer.groups[0].items[0]
    print(f"layer:\t{layer.name}\nfields:\t{renderer.fields}\nvalues:\t{first_item.values}\n\n")

 

 

layer:	unique
fields:	['TextField1']
values:	[['A']]


layer:	unique_grouped
fields:	['TextField1']
values:	[['A'], ['B']]


layer:	unique_multiple
fields:	['TextField1', 'IntegerField1']
values:	[['A', '12345']]


layer:	unique_multiple_grouped
fields:	['TextField1', 'IntegerField1']
values:	[['A', '12345'], ['C', '2']]

 

values is a list of lists. There is an inner list for each value in the group. In these lists, there is a (string!) value for each field.

We want to use these lists as dictionary keys, but that's impossible, because keys have to be immutable, and lists are mutable, so we change them to immutable tuples. Also, we have to have a separate key for each value in a group.

def get_item_dict(symbology):
    if symbology.renderer.type != "UniqueValueRenderer":
        raise ValueError("symbology has to be Unique Value!")
    item_dict = dict()
    for group in symbology.renderer.groups:
        for item in group.items:
            for value_group in item.values:
                item_dict[tuple(value_group)] = item
    return item_dict

 

Also, let's put the color conversion into a separate function to make the code a bit cleaner...

def convert_to_hex(color):
    color_type = list(color.keys())[0]
    if color_type == "RGB":
        rgb = color["RGB"][:3]
        return "#%02x%02x%02x" % tuple(rgb)
    else:
        return None

 

And with these two functions, we can concentrate on the actual workflow:

layer_names = ["TestPolygons"]
aprx = arcpy.mp.ArcGISProject("current")

for name in layer_names:
    # get the layer
    layer = aprx.activeMap.listLayers(name)[0]
    # create a dict {values: item}
    item_dict = get_item_dict(layer.symbology)
    # add a new field
    arcpy.management.AddField(layer, "PW_Zoning_Designation", "TEXT")
    arcpy.management.AddField(layer, "ColorRaw", "TEXT")
    arcpy.management.AddField(layer, "ColorHex", "TEXT")
    
    # insert the label and color values
    with arcpy.da.UpdateCursor(layer, ["PW_Zoning_Designation", "ColorRaw", "ColorHex"] + layer.symbology.renderer.fields) as cursor:
        for row in cursor:
            # get the item
            key = tuple([str(x) for x in row[3:]])  # item.values stores values as string
            try:
                item = item_dict[key]
            # no item found -> default value and skip to the next feature
            except KeyError:
                row[0] = "No  Symbol"
                cursor.updateRow(row)
                continue
            # store the item's label
            row[0] = item.label
            # store the raw color
            row[1] = str(item.symbol.color)
            # store the hex value
            row[2] = convert_to_hex(item.symbol.color)
            # write values
            cursor.updateRow(row)

 


Have a great day!
Johannes
0 Kudos