Output symbology in scripts

The symbology property of a script tool parameter allows you to associate a single layer file (.lyrx) with an output parameter. When the script tool is run, the output is added to the display using the symbology from the layer file. You can also set the symbology property in the script's code with the Parameter object's symbology property as shown below.

import arcpy

# Use the GetParameterInfo function to access tool parameters
params = arcpy.GetParameterInfo()
infc = params[0]
outfc = params[1]

# Copy features
arcpy.management.CopyFeatures(infc.valueAsText, outfc.valueAsText)

# Apply symbology to the copied features
outfc.symbology = "C:/Tools/Extractor/ToolData/polygon.lyrx"

Symbology can be persisted within the script's code by defining a string JSON Cartographic Information Model (CIM) or Esri Web Map specification object and passing it into the symbology argument of the SetParameterSymbology function. This eliminates the need of having to include a .lyrx file with the toolbox. The JSON string must be preceded with JSONRENDERER=, JSONCIMDEF=, or JSONCLASSDEF=, depending on the type of object and schema. There are several options for obtaining the JSON string of an object.

CIM schema symbology

Layer (.lyrx) files store the CIM content information in JSON format and can be opened with a text editor. The following example shows a modified JSON representation of a layer file containing four layers, with ellipses (...) indicating omitted portions.

Nota:

Note that .lyrx files can store symbology for multiple layers and group layers, so the entire JSON object cannot be used directly in SetParameterSymbology. The individual layer objects are listed under the layerDefinitions key of the JSON CIMLayerDocument.

{
  "type" : "CIMLayerDocument",
  "version" : "2.6.0",
  "build" : 23961,
  "layers" : [
    "CIMPATH=map/samplepoly.xml",
    "CIMPATH=map/samplepoint.xml",
    "CIMPATH=map/sampleline.xml",
    "CIMPATH=map/sampleraster.xml"
  ],
  "layerDefinitions" : [
    {
      "type" : "CIMFeatureLayer",
      "name" : "SamplePolygon",
      ...
      "renderer" : {
        ...
      }
    },
    {
      "type" : "CIMFeatureLayer",
      "name" : "SampleLine",
      ...
      "renderer" : {
        ...
      }
    },
    {
      "type" : "CIMFeatureLayer",
      "name" : "SamplePoint",
      ...
      "renderer" : {
        ...
      }
    },
    {
      "type" : "CIMRasterLayer",
      "name" : "SampleRaster",
      ...
      "colorizer" : {

        ...
      }
    }
  ],
  ...
}

The JSON CIM definition of an individual layer is everything within the containing brackets for the layer type, such as CIMFeatureLayer or CIMRasterLayer, including the portions omitted. Each layer object contains several other root level attributes as well as several nested objects, including the CIM "renderer" object, which describes the symbology of the layer. Note that for raster layers, the corresponding object is the colorizer. The following example shows the full JSON CIM renderer object for a simple line layer.

{
  "type" : "CIMSimpleRenderer",
  "patch" : "Default",
  "symbol" : {
    "type" : "CIMSymbolReference",
    "symbol" : {
      "type" : "CIMLineSymbol",
      "symbolLayers" : [
        {
          "type" : "CIMSolidStroke",
          "enable" : true,
          "capStyle" : "Round",
          "joinStyle" : "Round",
          "lineStyle3D" : "Strip",
          "miterLimit" : 10,
          "width" : 1,
          "color" : {
            "type" : "CIMRGBColor",
            "values" : [
              255,
              0,
              0,
              100
            ]
          }
        }
      ]
    }
  }
}

The JSON CIM object of a vector or raster layer can be used with SetParameterSymbology in its entirety with JSONCIMDEF=, or alternatively only the renderer object can be passed with JSONRENDERER=. A complete listing of specifications for CIM objects can be found under the Types page of the CIM Specification.

Nota:

The colorizer object cannot be passed with JSONRENDERER=.

Web Map schema symbology

Nota:
Note that the CIM specification is intended for ArcGIS Pro while the Web Map specification is intended for ArcGIS Online. The CIM and Web Map specifications are cross-platform, however, conversion between the two can be lossy. Therefore, the CIM specification is recommended for best results in ArcGIS Pro.

Web maps store content information according to the Web Map Specification in JSON format. A Web Map renderer object can be obtained from a feature service hosted on ArcGIS Online using the API REST de ArcGIS as follows, see API REST de ArcGIS Feature Service to learn more.

import json
import requests

# endpoint URL queried to expect a JSON formatted response
html = "https://services.arcgis.com/vHwtjnCAEWmDBZo5/arcgis/rest/services/My_Hosted_Feature/FeatureServer/0?f=json"

# Obtain JSON response object with requests.get()
response = requests.get(html, verify=False).json()

# Retrieve the renderer object nested within the response JSON
jsonren = response["drawingInfo"]["renderer"]

# Dump renderer JSON to a string using json.dumps()
renderer = json.dumps(jsonren)

The response object is a JSON Web Map layerDefinition. The example below shows a shortened representation of the entire response object.

Nota:

The layerDefinition cannot be used in SetParameterSymbology in its entirety. In the above example, the renderer object is retrieved from the response JSON.

{
  "currentVersion": 10.7,
  "id": 0,
  "name": "TestPoly",
  "type": "Feature Layer",
  ...
  "drawingInfo": {
    "renderer": {
      ...
    },
    ...
  },
  ...
}

The layerDefinition object contains several other root level attributes as well as several objects nested within it, including the Web Map drawingInfo object which contains the Web Map renderer object. The following is an example of the full JSON Web Map renderer object for a simple polygon layer.

{
  "type": "simple",
  "symbol": {
    "type": "esriSFS",
    "style": "esriSFSSolid",
    "color": [
      255,
      0,
      0,
      255
    ],
    "outline": {
      "type": "esriSLS",
      "style": "esriSLSSolid",
      "color": [
        110,
        110,
        110,
        255
      ],
      "width": 0.7
    }
  }
}

The Web Map renderer object can be passed into SetParameterSymbology with JSONRENDERER=. A complete listing of specifications for Web Map renderers can be found on the Web Map Specification page for renderer objects.

Classification definitions

The Web Map specification also supports generating classified or unique value renderers dynamically using JSON classification definition objects. These can be passed into SetParameterSymbology with JSONCLASSDEF=.

Set output symbology

When setting symbology either via the symbology property or the SetParameterSymbology function, you can only associate one layer file or JSON object string with the output parameter. Having only one symbology works well if the output is well defined. But what if your output isn't well-defined? For example, you know your output is a feature class, but you don't know if it will contain point, polyline, or polygon features until the tool is run. Layer files and JSON objects are dependent on geometry type, which means you cannot have one symbology that will symbolize multiple feature types. In this case, you need to have three layer files or JSON objects, one for each geometry type, and associate the correct symbology based on the output geometry type. The following code snippet demonstrates this using the symbology property.

# Set the symbology of the output. 

output = self.params[1].value
if output:
    desc = arcpy.Describe(output)
    
    if desc.shapeType == "Polygon":
        self.params[2].symbology = "C:/Tools/Extractor/ToolData/polygon.lyrx"
    elif desc.shapeType == "Polyline":
        self.params[2].symbology = "C:/Tools/Extractor/ToolData/polyline.lyrx"
    else:
        self.params[2].symbology = "C:/Tools/Extractor/ToolData/point.lyrx"

The logic in the above script is fairly simple: test the geometry (shape) type and set the symbology accordingly. Even if you have more complex logic, the pattern remains the same:

  • Create a layer file or JSON object string that will symbolize each possible output.
  • Based on logic in your script, determine which layer file or JSON object should be used and set it using the symbology property of the parameter.

Set symbology in a script versus the ToolValidator class

If you're familiar with programming tool validation logic in a ToolValidator class, you can see that the above code snippet can be rewritten for use in the updateParameters method. In fact, if you only need to reference one layer file or JSON object, you should do so either in the script tool's properties or in the initializeParameters method. But if you need to set symbology to any one of several layer files or JSON objects, you do so in the script tool. Putting such logic in the ToolValidator class unnecessarily lengthens your code with logic that has nothing to do with tool validation, and in some cases, you may not know which layer file to use until tool execution.

Example script

The script below creates features based on their distance from parks in the City of Portland. There are three parameters: the input features, the distance, and the output features. The output features are symbolized so that they are distinguishable from other features on the map (something other than the default symbology, which can be difficult to distinguish). Since the input features can be either point, polyline, or polygon, three layer files are needed.

This script also demonstrates several coding techniques for portability. The portability techniques used are as follows:

  • Using __file__ to retrieve the full path to the script file
  • Using the Python os module to create paths to data
  • Using the CreateScratchName function to create a scratch feature class

# ExtractData.py
# Description: Script that will extract features from an input layer within a 
#              specified distance from a park.
# Parameters:
#  0 - input features
#  1 - distance from parks (linear units)
#  2 - output feature class

import arcpy
import os

arcpy.env.overwriteOutput = True

# This tool uses a system folder with a Scripts and ToolData subfolder. 
# You can discover the pathname of this folder using the Python __file__
# attribute, which is the pathname to the script 
# (example: 'E:\examples\symbology\scripts\ExtractData.py'.)  You
# then use the toolSharePath variable to create paths to your 
# shapefile data and layer files ('E:\examples\symbology\ToolData\points.lyrx').

scriptPath = __file__
toolSharePath = os.path.dirname(os.path.dirname(scriptPath))
dataPath = os.path.join(toolSharePath, 'ToolData')
parkPath = os.path.join(dataPath, 'PortlandParks.shp')
pointLyrPath = os.path.join(dataPath, 'point.lyrx')
polygonLyrPath = os.path.join(dataPath, 'polygon.lyrx')
polylineLyrPath = os.path.join(dataPath, 'polyline.lyrx')
    
# Buffer the parks by the specified distance.  The output is a scratch
#  feature class in the same workspace as the output feature class
arcpy.SetProgressorLabel('Buffering parks ...')
scrname = arcpy.CreateScratchName('xxx', '', 'featureclass', 
                                  os.path.dirname(arcpy.GetParameterAsText(2)))
arcpy.Buffer_analysis(parkPath, scrname, arcpy.GetParameterAsText(1))

# Clip the defined layer with the buffered parks
arcpy.SetProgressorLabel('Clipping {} ...'.format(arcpy.GetParameterAsText(0)))
output = arcpy.Clip_analysis(arcpy.GetParameterAsText(0), scrname, 
                             arcpy.GetParameterAsText(2))

# Delete the intermediate dataset
try:
    arcpy.Delete_management(scrname)
except:
    pass

# Set the symbology of the output. 
params = arcpy.GetParameterInfo()
desc = arcpy.Describe(output)
if desc.shapeType == 'Polygon':
    params[2].symbology = polygonLyrPath
elif desc.shapeType == 'Polyline':
    params[2].symbology = polylineLyrPath
else:
    params[2].symbology = pointLyrPath