Skip To Content

Python CIM access

The arcpy.mp module is a course-grained Python API that is designed to provide access to many common map automation tasks. It includes a diverse set of exposed classes, class properties, and helper functions, but it does not provide access to all properties, settings, and capabilities available in ArcGIS Pro. One reason is to keep the API streamlined, simple, and manageable. Another reason is that ArcGIS Pro is being developed at such a rapid pace that the APIs can't keep up. It may take a release or more before you have access through the managed APIs. Starting with ArcGIS Pro 2.4, Python developers will have fine-grained access to the Cartographic Information Model (CIM) and can access many more settings, properties, and capabilities that are persisted in a project or document. The .NET SDK development community has had CIM access since ArcGIS Pro 1.1 and now it is available to the Python development community.

Caution:

The following sections describe what you can do using the CIM, but it is equally important to understand what you shouldn't do. It is recommended that you read the Use caution when modifying the CIM section before attempting to modify an object's CIM document.

What is the CIM

The CIM is the Esri Cartographic Information Model. It is a map content specification used to document how information that describes various project components is persisted when saved, read, referenced, or opened. The specification is represented as JSON and is used for maps, scenes, layouts, layers, symbols, and styles in ArcGIS applications and APIs.

Regardless of its acronym, don't think of the CIM as limited to only cartographic settings. The capabilities exposed to these classes through the CIM extend well beyond that, and the examples below demonstrate some of those extended capabilities.

It is important to understand the JSON structure of an object to successfully modify an object's CIM definition. The structure of JSON parallels the CIM object model exposed to the arcpy.mp module. The access points are through the arcpy.mp classes Layer, Table, Map, and Layout. Each of these classes can be saved to a JSON file format: .lyrx, .mapx, and .pagx. You can open these files in an editor and review how the information is organized and persisted.

Caution:
Do not edit these files directly. Use the API.

For additional information and a detailed specification and GIT repository, see Cartographic Information Model (cim-spec).

Modify a CIM definition

The basic workflow is to return an object's CIM definition using the getDefinition() method on the specific object to be modified, make the appropriate CIM API changes, and push the changes back to the same object using the setDefinition() method. When you want to return an object's CIM definition, you must specify a cim_version. Esri follows the semantic versioning specification. This means that until the next major release—for example, 3.0—when breaking API changes are allowed, the value to be used with cim_version is 'V2'. Once 3.0 is released, a new 'V3' enumeration value will become available. This allows Python script authors control over which version of the CIM is used during script execution if there is a possibility breaking changes may be introduced in the new version.

Once a CIM definition is returned, you can try to navigate its structure by viewing its code completion and its intellisense, or you can use a variety of Python statements to learn more about the available attributes. A useful technique for learning how attributes are organized and how to navigate the CIM object model for each class is to create a corresponding export file. For example a map can be saved to a .mapx file, a layer to a .lyrx file, and a layout to a .pagx file. All of these export files are in JSON format and can be viewed in an editor. Sometimes your editor may display better formatting if you change the .lyrx file extension to *.json.

Example 1: Basic Boolean, root-level properties

The following is a JSON representation of a modified, abbreviated list of root level properties available to CIMLayerDocument (*.lyrx).

{
  "type" : "CIMLayerDocument",
  "version" : "2.4.0",
  "layers" : [
    "CIMPATH=map/greatlakes.xml"
  ],
  "layerDefinitions" : [
    {
      "type" : "CIMFeatureLayer",
      "name" : "GreatLakes",
      "uRI" : "CIMPATH=map/greatlakes.xml",
      "useSourceMetadata" : true,
      "description" : "GreatLakes",
      "layerType" : "Operational",
      "showLegends" : true,
      "visibility" : true,
      "displayCacheType" : "Permanent",
      "maxDisplayCacheAge" : 5,
      "showPopups" : true,
      "serviceLayerID" : -1,
      "autoGenerateFeatureTemplates" : true,
      "featureElevationExpression" : "0",
      "featureTable" : {
      "htmlPopupEnabled" : true,
      "selectable" : true,
      "featureCacheType" : "Session",
      "scaleSymbols" : true,
      "snappable" : true

Basic metadata is displayed at the top of the file that describes the document and layer. There are also many root level layerDefinitions properties. The list will vary depending on the settings persisted with the object. The attributes exposed with your IDE code completion will show many more options than what you see in the JSON file above. This is because the JSON file only stores current settings, not all possible settings. One example of how the JSON file is purposely abbreviated is Boolean values. Only the properties that are currently set to true appear in the JSON file. For example, your code completion should show additional attributes such as expanded or showMapTips, but because they are currently set to false, they don't appear in the above JSON file.

The following arcpy.mp script will modify a few root-level attributes for a CIM layer object (l_cim). The selectable attribute appears in the JSON file above because its current value is true, but it will be set to False in the Python script. The showMapTips and expanded attributes don't appear in the JSON file currently because their values are false, but they will be set to True. If you save the layer to another layer file (*.lyrx), the appropriate changes appear in the JSON file.

# Reference a project, map, and layer using arcpy.mp
p = arcpy.mp.ArcGISProject('current')
m = p.listMaps('Map')[0]
l = m.listLayers('GreatLakes')[0]

# Return the layer's CIM definition
l_cim = l.getDefinition('V2')

# Modify a few boolean properties
l_cim.showMapTips = True  #Turn on map tips for bubble tips to appear
l_cim.selectable = False  #Set the layer to not be selectable
l_cim.expanded = True     #Expand the Layer in the Contents pane

# Push the changes back to the layer object
l.setDefinition(l_cim)

# Save changes
p.save()

Example 2: Modify field properties

The JSON and Python examples above are relatively easy because only root-level attributes were modified. The CIM object model can become nested, and the settings you want to change may become difficult to find. Another advantage of the JSON file is that you can use search for attributes and then determine how to access them using the CIM API.

This example drills into the object model one level lower than root. In the first JSON example above, there is a line with "featureTable" : {. Expanded this, and all of the capabilities it supports are organized using indentation. Everything that's indented the same is available to that CIM object.

"featureTable" : {
        "type" : "CIMFeatureTable",
        "displayField" : "NAME",
        "editable" : true,
        "fieldDescriptions" : [
          {
            "type" : "CIMFieldDescription",
            "alias" : "OBJECTID",
            "fieldName" : "OBJECTID",
            "numberFormat" : {
              "type" : "CIMNumericFormat",
              "alignmentOption" : "esriAlignRight",
              "alignmentWidth" : 0,
              "roundingOption" : "esriRoundNumberOfDecimals",
              "roundingValue" : 0
            },
            "readOnly" : true,
            "visible" : true,
            "searchMode" : "Exact"
          },

A common request is for the ability to modify a table's field alias or visibility, especially when creating a table on the fly using MakeFeatureLayer, for example. This cannot be done using the managed API. This script uses the CIM to access a feature layer's featureTable object and its fieldDescriptions object from which the alias and visible attributes can be set. The Python syntax follows the CIM object model where each dot exposes the next object's set of properties based on the indentation of the JSON structure.

# Reference a project, map, and layer using arcpy.mp
p = arcpy.mp.ArcGISProject('current') 
m = p.listMaps('Map')[0]
lyr = m.listLayers('GreatLakes')[0]

# Get the layer's CIM definition
cim_lyr = lyr.getDefinition('V2')

# Make changes to field properties
for fd in cim_lyr.featureTable.fieldDescriptions:
    if fd.fieldName == "OBJECTID":
        fd.visible = False            #Do not display this field
    if fd.fieldName == "Shape_Area":
        fd.alias = "Area (hectares)"  #Change field alias

# Push the changes back to the layer object
lyr.setDefinition(cim_lyr)

Example 3: Modify layer symbology

This example shows how nested the CIM object model can become and also shows the advantage of CIM-level access. The managedarcpy.mp API has limited access to renderers and depth of properties. It can only modify simple properties and only for the default layer of a symbol. However, the CIM can access a symbol with multiple layers. The following screen shot shows settings in the application that are not available to the arcpy.mp API:

The symbology pane for a polygon feature layer.

This is an edited and simplified JSON file that only displays the renderer information for a layer's symbology. The renderer has a symbol of type CIMSimpleRenderer and that has a symbol of type CIMPolygonSymbol. The polygon symbol has two symbolLayers: CIMSolidStroke and CIMSolidFill, each of which displays the properties that are available in the Symbology pane.

"layerDefinitions" : [
    {
      "renderer" : {
        "type" : "CIMSimpleRenderer",
        "patch" : "Default",
        "symbol" : {
          "type" : "CIMSymbolReference",
          "symbol" : {
            "type" : "CIMPolygonSymbol",
            "symbolLayers" : [
              {
                "type" : "CIMSolidStroke",
                "effects" : [
                  {
                    "type" : "CIMGeometricEffectDashes",
                    "dashTemplate" : [ 5, 5],
                    "lineDashEnding" : "NoConstraint",
                    "controlPointEnding" : "NoConstraint"
                  }
                ],
                "enable" : true,
                "capStyle" : "Round",
                "joinStyle" : "Round",
                "lineStyle3D" : "Strip",
                "miterLimit" : 10,
                "width" : 3,
                "color" : {
                  "type" : "CIMRGBColor",
                  "values" : [0, 0, 0, 100]
                }
              },
              {
                "type" : "CIMSolidFill",
                "enable" : true,
                "color" : {
                  "type" : "CIMRGBColor",
                  "values" : [ 255, 127, 0, 100 ]
                }
              }

this Python code uses CIM access to modify the layer's symbology. Both symbol layers are modified.

# Reference a project, map, and layer using arcpy.mp
p = arcpy.mp.ArcGISProject('current')
m = p.listMaps('Trail Routes')[0]
lyr = m.listLayers('Loops')[0]

# Return the layer's CIM definition
cim_lyr = lyr.getDefinition('V2')

# Modify the color, width and dash template for the SolidStroke layer
symLvl1 = cim_lyr.Renderer.Symbol.Symbol.SymbolLayers[0]
symLvl1.Color.Values = [250, 250, 40, 50]
symLvl1.Width = 8
ef1 = symLvl1.Effects[0]    #Note, deeper indentation
ef1.DashTemplate = [20, 30]

# Modify the color/transparency for the SolidFill layer
symLvl2 = cim_lyr.Renderer.Symbol.Symbol.SymbolLayers[1]
symLvl2.Color.Values = [140, 70, 20, 20]

# Push the changes back to the layer object
lyr.setDefinition(cim_lyr)

Use caution when modifying the CIM

The CIM exposes many useful capabilities, but things can go wrong if you are not careful. The application and managed APIs are designed to block you from making changes that can place the application in a compromised state. The CIM exposes everything, so it's possible to make conflicting changes that may not be possible in the application. It is important that you test scripts that modify the CIM. Ensure that the application doesn't respond oddly after making such changes. Limit yourself to changing settings that don't have dependencies on other settings.

Example 1: You can't change the spatial reference

You may think that changing a maps spatial reference is straight forward since the JSON description includes a tag called spatialReference.

"mapDefinition" : {
    "type" : "CIMMap",
    "name" : "Map",
    "uRI" : "CIMPATH=map/map.xml",
    "metadataURI" : "CIMPATH=Metadata/a7afc904584d1037910b2cfe65fe94f8.xml",
    "useSourceMetadata" : true,
    "illumination" : {
    "layers" : [
    "standaloneTables" : [
    "defaultViewingMode" : "Map",
    "mapType" : "Map",
    "datumTransforms" : [
    "defaultExtent" : {
    "elevationSurfaces" : [
    "spatialReference" : {
      "wkid" : 4326,
      "latestWkid" : 4326
    },

If you attempt to modify only the map's wkid and latestWkid properties, you won't get the expected results. This is because there are many other parts of the application that are associated with the spatial reference, and many changes would need to be made to the map's CIM to get the change to work properly. For example, a change to the spatial reference also affects datum transformations, a number of extents, clip geometries, and so on. This type of operation should be made in the application or with a managed API, where all the appropriate changes will be made.

Example 2: Don't get the application into an odd state

In this example, the expression engine associated with a layer's labeling properties is modified in the CIM. In the screen shot below, the expression engine is changed from Arcade, the default, to Python. Below are graphics of what it looks like in the UI and how the JSON portion appears.

The label class expression pane for a feature layer.

"labelClasses" : [
        {
          "type" : "CIMLabelClass",
          "expression" : "$feature.NAME",
          "expressionEngine" : "Arcade",
          "featuresToLabel" : "AllVisibleFeatures",
          "maplexLabelPlacementProperties" : {
          "name" : "Class 1",
          "priority" : -1,
          "standardLabelPlacementProperties" : {
          "textSymbol" : {
          "useCodedValue" : true,
          "visibility" : true,
          "iD" : -1
        }

The following is a Python script that changes only the expresionEngine and not the expression. This causes the application to behave erratically. For example, after running the code below, when you review the label properties for the layer, the expresionEngine is properly set to Python but the expression still appears in Arcade format. In the user interface, when you change the expression engine back to Arcade, the expression displays in Python format, the opposite of what it should be. To avoid this, it is important that you update both the expresionEngine and expression properties.

#Update the label expression engine from Arcade, the default, to Python.
#You must also update the expression otherwise the UI won't behave correctly after.

p = arcpy.mp.ArcGISProject('current')
m = p.listMaps('Map')[0]
l = m.listLayers()[0]

l_cim = l.getDefinition()

lc = l_cim.labelClasses[0]
lc.expressionEngine = 'Python'    #From 'Arcade'
lc.expression = '[STATE_NAME]'    #From '$feature.STATE_NAME'

l.setDefinition(l_cim)

Tips

Sometimes the attribute tag in the JSON file isn't easy to find because the tag name isn't intuitive, or it can be nested in the object model. One way to approach this is to set a value to something truly unique, and search on that value in the JSON file. For example, you set a width of a layout element to 0.7777 or an RGB color to 111, 111, 111.

You may see in the JSON file an enumeration value you want to change, but you don't know what the new, proper value should be. A solution is to set the correct value in the application, and save the object to a JSON export file and evaluate the updated value.

The ArcGIS.Core.CIM Namespace .NET SDK API Reference help topic provides a list of CIM objects and documentation for each class member.