ArcGIS Pro 3.2.1
A colleague has built a tool that converts a selection to a definition query.
Resulting definition query:
t_unique_id IN (122, 123, 124, 125, 126)
The tool works well, but it has a limitation: If there are multiple layers in the Contents that have the same name, then the tool has no way to differentiate between the two layers.
Note that since you can only get a layer reference in ArcPy by its name, I had to limit the tool to grabbing the first layer in the Map TOC that has the name [that corresponds to the name] in the tool. So if there are multiple layers or tables with the same name, it will operate on the first one found when looping through them.
In the following example, there are two layers that have the same name, and the second layer is the one that has the selection to be converted to a definition query. When I run the tool, it throws an error because it refers to the first layer, which doesn't have a selection.
As a non-Python person, that seems like an unfortunate limitation of ArcPy, especially since the Layer or Table parameter seems to be aware of the duplicates and refers to them by unique names: "species_records" and "species_records:1". If the parameter can refer to the duplicate layers by unique names, then is there a reason why the Python script can’t differentiate between the two layers as well?
Idea:
Could ArcPy be enhanced so that it can refer to a specific Contents layer, even if there are multiple layers with duplicate names?
Feel free to let me know if I've misunderstood something.
Related:
Tool Requirements:
Selection2Definition.py (old):
import arcpy, sys, traceback
SelectionLayer = sys.argv[1]
Field = sys.argv[2]
try:
#### Get the current active map
aprx = arcpy.mp.ArcGISProject("CURRENT")
map = aprx.listMaps(aprx.activeMap.name)[0]
#### Deal with group layers if needed
if("\\" in SelectionLayer):
arySelectionLayer = SelectionLayer.split('\\')
SelectionLayer = arySelectionLayer[1]
### Get the first layer in the TOC with the name selected
LayersTablesList = map.listLayers(SelectionLayer) + map.listTables(SelectionLayer)
target_layer = LayersTablesList[0]
if not target_layer.getSelectionSet():
raise Exception("No features currently selected.")
#### Collect the selected field's features
lst = []
with arcpy.da.SearchCursor(target_layer, [Field]) as cursor:
for row in cursor:
lst.append(row[0])
#### Build a where clause
where = ""
fieldtype = ""
fields = arcpy.ListFields(target_layer, Field)
for field in fields:
if field.name == Field:
fieldtype = field.type
#### single quote if a text field
if(fieldtype == 'String'):
where = " IN ('" + "','".join(lst) + "')"
else:
where = " IN (" + ",".join([str(i) for i in lst]) + ")"
arcpy.AddMessage(Field + where)
target_layer.definitionQuery = Field + where
except:
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = "PYTHON ERRORS:\n Traceback Info:\n" + tbinfo + "\nError Info:\n " + \
str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"
msgs = "arcpy ERRORS:\n" + arcpy.GetMessages(2) + "\n"
arcpy.AddMessage(pymsg)
Toolvalidator.py (old):
class ToolValidator:
# Class to add custom behavior and properties to the tool and tool parameters.
def __init__(self):
# set self.params for use in other function
self.params = arcpy.GetParameterInfo()
def initializeParameters(self):
param0 = arcpy.Parameter(
displayName="Input Features",
name="in_features",
datatype=["GPFeatureLayer", "GPTableView"],
parameterType="Required",
direction="Input",
multiValue=False)
param1 = arcpy.Parameter(
displayName="Select Field",
name="inputField",
datatype="Field",
parameterType="Required",
direction="Input")
param1.parameterDependencies = [param0.name]
param1.filter.list = []
params = [param0,param1]
return params
def initializeParameters(self):
# Customize parameter properties.
# This gets called when the tool is opened.
return
def updateParameters(self):
# Modify parameter values and properties.
# This gets called each time a parameter is modified, before
# standard validation.
if self.params[0].altered:
layer = self.params[0].value
if layer:
# Get all indexes for the layer
indexes = arcpy.ListIndexes(layer)
# Create a set of all field names that are part of an index
indexed_fields = set()
for index in indexes:
if index.isUnique:
for field in index.fields:
indexed_fields.add(field.name)
# Filter fields to include only those that have an index
fields_with_index = [f.name for f in arcpy.ListFields(layer) if f.name in indexed_fields]
self.params[1].filter.list = fields_with_index
else:
self.params[1].filter.list = []
return
def updateMessages(self):
# def isLicensed(self):
# # set tool isLicensed.
# return True
# def postExecute(self):
# # This method takes place after outputs are processed and
# # added to the display.
return
@Bud This tool is explicitly parsing the non-unique string name of the layer out of the input and then using it to search for the layer again using some list functionality.
What is the roadblock that prevents the tool author from directly using the layer passed to the gp tool?
@SSWoodward provided a solution here: Contents — Unique layer names in Properties window (auto-generated)
...try using the arcpy.GetParameter methods instead of sys.argv. These are super handy, and should be the goto syntax when writing script tools.
When the parameter is fetched in this way it is a layer reference and not a string.
Updated Python Scripts:
(these are the same scripts that are included in the .zip in the original post)
Selection2Definition.py
import arcpy, sys, traceback
SelectionLayer = arcpy.GetParameter(0)
Field = sys.argv[2]
try:
#### Get the current active map
aprx = arcpy.mp.ArcGISProject("CURRENT")
map = aprx.listMaps(aprx.activeMap.name)[0]
if not SelectionLayer.getSelectionSet():
raise Exception("No features currently selected.")
#### Collect the selected field's features
lst = []
with arcpy.da.SearchCursor(SelectionLayer, [Field]) as cursor:
for row in cursor:
lst.append(row[0])
#### Build a where clause
where = ""
fieldtype = ""
fields = arcpy.ListFields(SelectionLayer, Field)
for field in fields:
if field.name == Field:
fieldtype = field.type
#### single quote if a text field
if(fieldtype == 'String'):
where = " IN ('" + "','".join(lst) + "')"
else:
where = " IN (" + ",".join([str(i) for i in lst]) + ")"
definition_query = Field + where
arcpy.AddMessage("DEF: " + definition_query)
SelectionLayer.definitionQuery = definition_query
arcpy.SetParameter(2, SelectionLayer)
except:
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = "PYTHON ERRORS:\n Traceback Info:\n" + tbinfo + "\nError Info:\n " + \
str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"
msgs = "arcpy ERRORS:\n" + arcpy.GetMessages(2) + "\n"
arcpy.AddMessage(pymsg)
Toolvalidator.py
class ToolValidator:
# Class to add custom behavior and properties to the tool and tool parameters.
def __init__(self):
# set self.params for use in other function
self.params = arcpy.GetParameterInfo()
def initializeParameters(self):
param0 = arcpy.Parameter(
displayName="Input Features",
name="in_features",
datatype=["GPFeatureLayer", "GPTableView"],
parameterType="Required",
direction="Input",
multiValue=False)
param1 = arcpy.Parameter(
displayName="Select Field",
name="inputField",
datatype="Field",
parameterType="Required",
direction="Input")
param1.parameterDependencies = [param0.name]
param1.filter.list = []
params = [param0,param1]
return params
def initializeParameters(self):
# Customize parameter properties.
# This gets called when the tool is opened.
return
def updateParameters(self):
# Modify parameter values and properties.
# This gets called each time a parameter is modified, before
# standard validation.
if self.params[0].altered:
layer = self.params[0].value
if layer:
# Get all indexes for the layer
indexes = arcpy.ListIndexes(layer)
# Create a set of all field names that are part of an index
indexed_fields = set()
for index in indexes:
if index.isUnique:
for field in index.fields:
indexed_fields.add(field.name)
# Filter fields to include only those that have an index
fields_with_index = [f.name for f in arcpy.ListFields(layer) if f.name in indexed_fields]
self.params[1].filter.list = fields_with_index
else:
self.params[1].filter.list = []
return
def updateMessages(self):
# def isLicensed(self):
# # set tool isLicensed.
# return True
# def postExecute(self):
# # This method takes place after outputs are processed and
# # added to the display.
return
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.