Select to view content in your preferred language

How do I load a layer file (.lyrx) into multiple maps and update its source to point to an input feature class?

94
3
yesterday
BenBlowers2
Occasional Contributor

I have a script that needs to:

  1. Load a specific .lyrx file into multiple maps
  2. Update each loaded layer's source to point to an input parameter
  3. Maintain the symbology from the .lyrx file

Current attempts result in broken layers. Here's my current approach:

def _ensure_site_layer(map_obj, site_fc_path, layer_name=SITE_LAYER_NAME):
"""Add site boundary layer to map"""
try:
# Remove any existing site boundary layers
for lyr in list(map_obj.listLayers()):
if lyr.name in (layer_name, "SiteBoundary_Export", "HE_SiteBoundary"):
map_obj.removeLayer(lyr)

# Trying to add layer file and update its source
SPECIFIC_LF = r"lyrx source removed"
lf = arcpy.mp.LayerFile(SPECIFIC_LF)
map_obj.addLayer(lf, "TOP")

# Need help with this part - updating the source
# site_fc_path contains the input parameter path

return added_layer
except Exception as e:
arcpy.AddWarning(f"_ensure_site_layer failed: {str(e)}")
return None

How the Function is Called:

# Example of how this function is used in the script
prof_map = _add_layers_to_profile_map(site_fc_path, tran_fc_path)
he_map = _he_add_layers_to_map(site_fc_path, he_buffer_fc_path, he_layer_paths)
# etc...

Does anyone know how i can get this script to load the lyrx correctly and change its source? 

Tags (2)
0 Kudos
3 Replies
HaydenWelch
MVP Regular Contributor

You can directly update the CIM using the getDefinition and setDefinition options, I tend to have more luck with that. Once the layer is added, you can access it using the map definition directly.

You can also use the updateConnectionProperties method that's exposed by arcpy.mp for Layer objects. You'll need to also use the connectionProperties attribute to get the current connection. This method basically does the same thing as the CIM option, but has some added checks in place.

 

def _ensure_site_layer(map_obj: arcpy.mp.Map, site_fc_path: str, layer_name: str=SITE_LAYER_NAME):
    """Add site boundary layer to map"""
    try:
        # Remove any existing site boundary layers
        for lyr in list(map_obj.listLayers()):
            if lyr.name in (layer_name, "SiteBoundary_Export", "HE_SiteBoundary"):
                map_obj.removeLayer(lyr)

        # Trying to add layer file and update its source
        SPECIFIC_LF = r"lyrx source removed"
        lf = arcpy.mp.LayerFile(SPECIFIC_LF)
        
        # Docs say that addLayer returns a list of layers,
        # if you are adding a group layer from the LYRX, there
        # will be GroupLayer + sublayer layers here I believe
        #
        # The URI is the assigned CIMPATH of the layer.
        # If you unzip an aprx, you can see the CIMPATH is 
        # just a relative filepath to the layer/lyrx definition
        # in the zipped directory from the root
        added_layer = map_obj.addLayer(lf, "TOP").pop() # .pop() grabs first layer
        path_to_gdb = r'C:\Path\To\GDB.gdb'
        
        USE_CIM = True
        
        if USE_CIM:
            lyr_cim = added_layer.getDefinition('V3')

            # This example re-paths to a GDB (Relative paths are valid)
            # If you want to change the workspace type, you'll need to also
            # update the dataConnection.workspaceFactory string to the correct magic
            # type string

            # Here's an example of the CIM for dataConnection
            # "dataConnection": {
            #     "featureDataset": "<FEATURE_DATASET>"|null,
            #     "workspaceConnectionString": "DATABASE=C:\\Path\\To\\GDB.gdb",
            #     "workspaceFactory": "FileGDB",
            #     "dataset": "<FC_NAME>",
            #     "datasetType": "esriDTFeatureClass",
            #     "customParameters": [],
            #     "type": "CIMFeatureDatasetDataConnection"
            # },
            lyr_cim.dataConnection.workspaceConnectionString = f'DATABASE={path_to_gdb}'
            added_layer.setDefinition(lyr_cim)
        else:
            # You can also try using the builtin updateConnectionProperties and connectionProperties
            new_conn = added_layer.connectionProperties
            new_conn['connection_info']['database'] = path_to_gdb
            added_layer.updateConnectionProperties(added_layer.connectionProperties, new_conn)
        
        return added_layer
    except Exception as e:
        arcpy.AddWarning(f"_ensure_site_layer failed: {str(e)}")
        return None

 

At the end of the day, almost everything in the mapping module is just editing the JSON definition of layers in an aprx or on a webservice. The CIM is just raw access to those definitions. aprx, mapx, and lyrx files can all be opened directly in a text editor and read as json data. The only have a small CIMLayerDocument wrapper around the CIM for the contained objects that tells Arc how to structure the contained layers:

HaydenWelch_0-1761055114286.png

 

0 Kudos
RichardHowe
Frequent Contributor

Think I'd be tempted to work it the other way

Add the layers you want to the map and then use the ApplySymbologyFromLayer tool, pointed at your layer file to update the symbology

Clubdebambos
MVP Regular Contributor

I second this approach and it is one I employ. Add the layers and update symbololgy from the .lyrx library. If symbology changes, update the .lyrx and apply across the layers in multiple maps as required.

~ learn.finaldraftmapping.com