Changing the Symbology to Picture Marker with a loop

218
1
02-08-2024 12:10 PM
Labels (1)
ChadChristensen
New Contributor II

I have a symbology set (unique values) with the default point symbols, based on a name field.  I would like to change them all to be Picture Markers using a CIM definition revision, but I can't get to the "type" attribute of the symbols.  As I get each level deeper in the CIM, there is no "type" but the ones listed below (bold means the next level into which I dig):

  • CIM.renderer.group [heading, classes]
  • class [alternateSymbols, description, editable, label, patch, symbol, values, visible]
  • symbol [maxDistance, maxScale, minDistance, minScale, primitiveOverrides, scaleDependentSizeVariation, stylePath, symbol, symbolName]
  • symbol [angle, angleAlignment, callout, effects, haloSize, haloSymbol, primitiveName, scaleX, Symbol3DProperties, symbolLayers, thumbnailURI, useRealWorldSymbolSizes]
  • symbolLayers [anchorPoint(), anchorPointUnits, angleX, angleY, animatedSymbolProperties, billboardMode3D, colorLocked, colorSubstitutions, depth3D, dominantSizeAxis3d, effects, enable, invertBackfaceTexture, markerPlacement, name, offsetX, offsetY, offsetZ, overprint, primitiveName, rotateClockwise, rotation, scaleX, size, textureFilter, tintColor, url, verticalOrientation3D]

I would just like to do this process, in a loop, to get through all 150 symbols:

  1. cim = mylayer.getDefinition(V3)
  2. myitem = lcim.renderer.groups[0].classes[0].symbol  (this is where I don't know what level to get into)
  3. myitem.type = CIMPictureMarker  (this is what I can't get, because there is no "type"
  4. mylayer.setDefinition(cim)

Many thanks!

 

0 Kudos
1 Reply
RHmapping
New Contributor II

Quite tricky but not impossible .. looks like only by using the CIM can stuff like this be done as you have investigated, methods on the arcpy Symbology class dont seem to have the power. Assuming that you want to change the CIM on the left to be like the CIM on the right in the attached image .. when doing anything like this it always helps to export before and after layer files done manually and investigate the CIM Json in a text editor

To get the marker type this seems to be deep down in the layer Renderer on, basically based on the type of CIM object there e.g. CIMVectorMarker, CIMPictureMarker cim.renderer.groups[n].classes[n].symbol.symbol.symbolLayers[n] (yes symbol dot symbol as you also found) The script below will swap out any existing symbols on point layers with the according picture marker tweak the pictureMarkers config for your own use. Note that setting URL on any new CIMPictureMarker as a path seems not to work, you have to convert the image to a base 64 encoded string 😕 obvs error handling could be improved etc etc but this script worked for myself

import base64
import arcpy

pictureMarkers = [
    {
        "layerName": "POINT_DATA1",
        "valueImageMarkers" : [
            {
                "uniqueValue": "JCN20",
                "path": "C:\\temp\\twenty.png",
                "fmt": "image/png",
                "size": 40
            },
            {
                "uniqueValue": "JCN24",
                "path": "C:\\temp\\twenty-four.png",
                "fmt": "image/png",
                "size": 40
            }            
        ]
    }
]

proj = arcpy.mp.ArcGISProject("CURRENT")
map = proj.activeMap

for lx in map.listLayers() :   
    try :
        if (not(lx.supports("DEFINITIONQUERY"))) : continue # datasets only thanks

        # see if there is a config for this layer
        pm_cfg = None
        for cfg1 in pictureMarkers :
            if (cfg1["layerName"] == lx.name) : # case sensitive
                pm_cfg = cfg1
                break

        if (pm_cfg == None) : continue # no config for this layer
        print(lx.name) 

        # get the CIM object
        lx_cim = lx.getDefinition('V3')
        chgd = False

        grp_idx = 0
        for gx in lx_cim.renderer.groups :
            cls_idx = 0            
            for cx in gx.classes :                 

                # check its as we expect? .. 
                ##if (not(hasattr(cx.symbol, "symbol"))) : raise Exception("CIM hierarchy is not as expected") 
                ## print ( type(cx.symbol.symbol.symbolLayers[0]) )

                # class.symbol.symbol.symbolLayers exposes a list of the symbols, and types e.g. CIMVectorMarker, CIMPictureMarker
                for sym in cx.symbol.symbol.symbolLayers :
                    print(f'Layer {lx.name} (Sym. Group {grp_idx}, Sym Class Index {cls_idx}) Marker Type ={type(sym)}') # print the current symbol types
                
                # class.values[0].fieldValues exposes the actual unique values .. again as a list :^/
                for value_set in cx.values :
                    print(f'Layer {lx.name} (Sym. Group {grp_idx}, Sym Class Index {cls_idx}) Unique Value="{value_set.fieldValues}"')

                # ! assume one symbol and one unique value ..

                # find a match :)
                val_img_cfg = None
                for value_set in cx.values :
                    for cfg_val in pm_cfg["valueImageMarkers"] :
                        # see if our config matches ANY value set on the renderer group class
                        if (cfg_val["uniqueValue"] in value_set.fieldValues) : # case sensitive
                            val_img_cfg = cfg_val
                            break # gotcha

                if (not(val_img_cfg == None)) :

                    # create a picture marker
                    new_sym = arcpy.cim.CreateCIMObjectFromClassName("CIMPictureMarker", "V3")
                    new_sym.size = val_img_cfg['size']
                            
                    # new_sym.url = img_cfg['path'] doesnt work :''(
                    
                    # convert to an encoded string with the image data does
                    with open(val_img_cfg['path'], 'rb') as img_fdata :
                        data_s = None
                        data_s_enc = None
                        try :
                            data_s = img_fdata.read() # read the image data as binary
                            data_s_enc = base64.b64encode(data_s).decode('utf-8') # encode as base 64
                            new_sym.url = f"data:{val_img_cfg['fmt']};base64,{data_s_enc}"
                        finally :
                            # clean up ..
                            if (not(data_s == None)) : del data_s
                            if (not(data_s_enc == None)) : del data_s_enc

                    # assuming one so just replace all marker symbols for this class ..
                    cx.symbol.symbol.symbolLayers = [new_sym] # .. with the new one
                    chgd = True
             
            cls_idx += 1  

        if (chgd) : 
             # ok make the change :)
            lx.setDefinition(lx_cim)      
            print("cim updated")    

    except Exception as ex :
        print(f"ERROR: failed on layer {lx.name} ({str(ex)})")   

 

 

0 Kudos