681 lines
25 KiB
Python
681 lines
25 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import arcpy # type: ignore
|
|
|
|
|
|
class Toolbox:
|
|
def __init__(self):
|
|
self.label = "Feature Renderer"
|
|
self.alias = "Feature Renderer"
|
|
|
|
# List of tool classes associated with this toolbox
|
|
self.tools = [FeatureRenderer]
|
|
|
|
|
|
class FeatureRenderer:
|
|
def __init__(self):
|
|
self.label = "Feature Renderer"
|
|
self.description = "Unique value renderer for geologic features"
|
|
|
|
def getParameterInfo(self):
|
|
# Define parameter for selecting style files
|
|
style_files = arcpy.Parameter(
|
|
displayName="Select Style Files",
|
|
name="style_files",
|
|
datatype="DEFile",
|
|
parameterType="Required",
|
|
direction="Input",
|
|
multiValue=True,
|
|
)
|
|
|
|
# Set filter to only show .stylx files in the file picker
|
|
style_files.filter.list = ["stylx"]
|
|
|
|
# Define parameter for selecting feature classes or layers
|
|
layers = arcpy.Parameter(
|
|
displayName="Select Feature Classes or Layers",
|
|
name="fc_or_layer",
|
|
datatype="GPFeatureLayer",
|
|
parameterType="Required",
|
|
direction="Input",
|
|
multiValue=True,
|
|
)
|
|
|
|
# Define parameter for selecting a table from the enterprise geodatabase
|
|
table_param = arcpy.Parameter(
|
|
displayName="Select Legend Table",
|
|
name="table",
|
|
datatype="DETable",
|
|
parameterType="Required",
|
|
direction="Input",
|
|
)
|
|
|
|
# Define parameter for selecting a primary key
|
|
field_param_1 = arcpy.Parameter(
|
|
displayName="Select Primary Key Field (LEG_ID)",
|
|
name="primary_key",
|
|
datatype="Field",
|
|
parameterType="Required",
|
|
direction="Input",
|
|
multiValue=False,
|
|
)
|
|
|
|
field_param_1.parameterDependencies = [table_param.name]
|
|
|
|
# Define parameter for selecting a fill symbol code field
|
|
field_param_2 = arcpy.Parameter(
|
|
displayName="Select Fill Symbol Code Field",
|
|
name="fill_symbol",
|
|
datatype="Field",
|
|
parameterType="Required",
|
|
direction="Input",
|
|
multiValue=False,
|
|
)
|
|
|
|
field_param_2.parameterDependencies = [table_param.name]
|
|
|
|
# Define parameter for selecting a stroke symbol code field
|
|
field_param_3 = arcpy.Parameter(
|
|
displayName="Select Stroke Symbol Code Field",
|
|
name="line_symbol",
|
|
datatype="Field",
|
|
parameterType="Required",
|
|
direction="Input",
|
|
multiValue=False,
|
|
)
|
|
|
|
field_param_3.parameterDependencies = [table_param.name]
|
|
|
|
# Define parameter for selecting a marker symbol code field
|
|
field_param_4 = arcpy.Parameter(
|
|
displayName="Select Marker Symbol Code Field",
|
|
name="point_symbol",
|
|
datatype="Field",
|
|
parameterType="Required",
|
|
direction="Input",
|
|
multiValue=False,
|
|
)
|
|
|
|
field_param_4.parameterDependencies = [table_param.name]
|
|
|
|
# Define parameter for selecting a legend text field
|
|
field_param_5 = arcpy.Parameter(
|
|
displayName="Select Legend Text Field 1",
|
|
name="legend_text_1",
|
|
datatype="Field",
|
|
parameterType="Optional",
|
|
direction="Input",
|
|
multiValue=False,
|
|
)
|
|
|
|
field_param_5.parameterDependencies = [table_param.name]
|
|
|
|
# Define parameter for selecting a legend text field
|
|
field_param_6 = arcpy.Parameter(
|
|
displayName="Select Legend Text Field 2",
|
|
name="legend_text_2",
|
|
datatype="Field",
|
|
parameterType="Optional",
|
|
direction="Input",
|
|
multiValue=False,
|
|
)
|
|
|
|
field_param_6.parameterDependencies = [table_param.name]
|
|
|
|
# Define parameter for label field
|
|
label_field_1 = arcpy.Parameter(
|
|
name="label_field_1",
|
|
displayName="Select Label Field 1",
|
|
datatype="Field",
|
|
direction="Input",
|
|
parameterType="Optional",
|
|
category="Define Label",
|
|
)
|
|
|
|
# Define parameter for delimiter
|
|
label_delimiter = arcpy.Parameter(
|
|
name="label_delimiter",
|
|
displayName="Delimiter",
|
|
datatype="GPString",
|
|
direction="Input",
|
|
parameterType="Optional",
|
|
category="Define Label",
|
|
)
|
|
|
|
label_delimiter.value = "-"
|
|
|
|
# Define parameter for label field
|
|
label_field_2 = arcpy.Parameter(
|
|
name="label_field_2",
|
|
displayName="Select Label Field 2",
|
|
datatype="Field",
|
|
direction="Input",
|
|
parameterType="Optional",
|
|
category="Define Label",
|
|
)
|
|
|
|
label_field_1.parameterDependencies = [table_param.name]
|
|
label_field_2.parameterDependencies = [table_param.name]
|
|
|
|
# Define parameter for heading field
|
|
heading_field = arcpy.Parameter(
|
|
name="heading_field",
|
|
displayName="Select Heading Field",
|
|
datatype="Field",
|
|
direction="Input",
|
|
parameterType="Optional",
|
|
category="Define Heading",
|
|
)
|
|
|
|
heading_field.parameterDependencies = [table_param.name]
|
|
|
|
# Define Boolean parameter
|
|
draw_outlines = arcpy.Parameter(
|
|
displayName="Draw outlines",
|
|
name="draw_outlines",
|
|
datatype="GPBoolean",
|
|
parameterType="Required",
|
|
direction="Input",
|
|
)
|
|
|
|
# Default value
|
|
draw_outlines.value = True
|
|
|
|
# Return parameter definitions as a list
|
|
return [
|
|
style_files,
|
|
layers,
|
|
table_param,
|
|
field_param_1,
|
|
field_param_2,
|
|
field_param_3,
|
|
field_param_4,
|
|
label_field_1,
|
|
label_delimiter,
|
|
label_field_2,
|
|
heading_field,
|
|
draw_outlines,
|
|
]
|
|
|
|
def isLicensed(self):
|
|
return True
|
|
|
|
def updateParameters(self, parameters):
|
|
return
|
|
|
|
def updateMessages(self, _):
|
|
return
|
|
|
|
def execute(self, parameters, messages):
|
|
# Retrieve a reference to the current project
|
|
project = arcpy.mp.ArcGISProject("CURRENT")
|
|
|
|
# Retrieve input parameters
|
|
style_files = parameters[0].valueAsText
|
|
input_layers = parameters[1].valueAsText
|
|
table = parameters[2].valueAsText
|
|
primary_key_field = parameters[3].valueAsText
|
|
fill_symbol_code_field = parameters[4].valueAsText
|
|
line_symbol_code_field = parameters[5].valueAsText
|
|
point_symbol_code_field = parameters[6].valueAsText
|
|
label_field_1 = parameters[7].valueAsText
|
|
label_delimieter = parameters[8].valueAsText
|
|
label_field_2 = parameters[9].valueAsText
|
|
heading_field = parameters[10].valueAsText
|
|
draw_outlines = parameters[11].value
|
|
|
|
# Retrieve currently active map
|
|
active_map = project.activeMap
|
|
|
|
# Retrieve project layers
|
|
map_layers = []
|
|
if active_map:
|
|
map_layers = active_map.listLayers()
|
|
else:
|
|
raise ValueError("Execution aborted: please select a map")
|
|
|
|
# Check if selected layers are present in the active map
|
|
input_layers_list = input_layers.split(";")
|
|
layers_to_render = []
|
|
for input_layer in input_layers_list:
|
|
present = False
|
|
for layer in map_layers:
|
|
if layer.name == input_layer:
|
|
layers_to_render.append(layer.name)
|
|
present = True
|
|
break
|
|
|
|
if not present:
|
|
added_layer = active_map.addDataFromPath(input_layer)
|
|
layers_to_render.append(added_layer.name)
|
|
|
|
# Retrieve styles that are currently in the project
|
|
project_styles = project.styles
|
|
|
|
# Check if styles are in the project - add them if not present
|
|
style_files_list = style_files.split(";")
|
|
for style_path in style_files_list:
|
|
if not style_path in project_styles:
|
|
# Add the style to the project
|
|
project_styles.append(style_path)
|
|
project.updateStyles(project_styles)
|
|
|
|
# Retrieve symbol codes from legend table
|
|
symbol_codes = {}
|
|
with arcpy.da.SearchCursor(
|
|
table,
|
|
[
|
|
primary_key_field,
|
|
fill_symbol_code_field,
|
|
line_symbol_code_field,
|
|
point_symbol_code_field,
|
|
],
|
|
) as search_cursor:
|
|
for row in search_cursor:
|
|
symbol_codes[str(row[0])] = {
|
|
"fill": row[1],
|
|
"stroke": row[2],
|
|
"marker": row[3],
|
|
}
|
|
|
|
# Retrieve label texts for custom labels
|
|
add_labels = False
|
|
if label_field_1 or label_field_2:
|
|
add_labels = True
|
|
custom_labels = {}
|
|
labels = [primary_key_field]
|
|
if label_field_1:
|
|
labels.append(label_field_1)
|
|
if label_field_2:
|
|
labels.append(label_field_2)
|
|
|
|
with arcpy.da.SearchCursor(
|
|
table,
|
|
labels,
|
|
) as search_cursor:
|
|
for row in search_cursor:
|
|
custom_labels[row[0]] = f"{row[1]}{label_delimieter}{row[2]}"
|
|
|
|
if heading_field:
|
|
labels = [primary_key_field, heading_field]
|
|
headings = {}
|
|
with arcpy.da.SearchCursor(
|
|
table,
|
|
labels,
|
|
) as search_cursor:
|
|
for row in search_cursor:
|
|
headings[str(row[0])] = row[1]
|
|
|
|
# Start rendering process
|
|
for layer in map_layers:
|
|
if layer.name in layers_to_render:
|
|
messages.AddMessage(f"Rendering layer: {layer.name}")
|
|
|
|
sym = layer.symbology
|
|
layer_desc = arcpy.Describe(layer)
|
|
layer_shape_type = layer_desc.featureClass.shapeType
|
|
|
|
# Check if UniqueValueRenderer is defined
|
|
if sym.renderer == "UniqueValueRenderer":
|
|
|
|
# Regroup items according to heading field
|
|
if heading_field:
|
|
new_item_groups = {}
|
|
old_item_groups = {}
|
|
|
|
for group in sym.renderer.groups:
|
|
old_item_groups[group.heading] = group.items
|
|
|
|
for group in sym.renderer.groups:
|
|
for item in group.items:
|
|
if item.values[0][0] != "<Null>":
|
|
leg_id = str(item.values[0][0])
|
|
else:
|
|
leg_id = None
|
|
|
|
if leg_id in headings:
|
|
heading = headings[leg_id]
|
|
if not heading:
|
|
heading = "Other"
|
|
else:
|
|
heading = "Other"
|
|
|
|
if heading in new_item_groups:
|
|
new_item_groups[heading].append(item)
|
|
else:
|
|
new_item_groups[heading] = [item]
|
|
|
|
# Remove old item groups
|
|
sym.renderer.removeValues(old_item_groups)
|
|
layer.symbology = sym
|
|
|
|
# Add new item groups
|
|
sym.renderer.addValues(new_item_groups)
|
|
layer.symbology = sym
|
|
|
|
# Apply symbols from gallery
|
|
for group in sym.renderer.groups:
|
|
for item in group.items:
|
|
if item.values[0][0] != "<Null>":
|
|
leg_id = str(item.values[0][0])
|
|
else:
|
|
leg_id = None
|
|
|
|
if leg_id in symbol_codes:
|
|
symbol_code = get_symbol_code_for_shape(
|
|
layer_shape_type, symbol_codes[leg_id]
|
|
)
|
|
|
|
if symbol_code == "#":
|
|
continue
|
|
|
|
code_components = get_code_components(symbol_code)
|
|
symbol_key = code_components["symbol_key"]
|
|
|
|
if symbol_key:
|
|
symbols_from_gallery = (
|
|
item.symbol.listSymbolsFromGallery(symbol_key)
|
|
)
|
|
for symbol_from_gallery in symbols_from_gallery:
|
|
if symbol_from_gallery.name == symbol_key:
|
|
item.symbol = symbol_from_gallery
|
|
break
|
|
|
|
# Add user defined labels
|
|
if add_labels:
|
|
if leg_id in custom_labels:
|
|
item.label = custom_labels[leg_id]
|
|
|
|
layer.symbology = sym
|
|
|
|
# Retrieve CIM to add colors
|
|
cim_lyr = layer.getDefinition("V3")
|
|
|
|
# Retrieve stroke symbol properties
|
|
stroke_symbol_props = {"color": {}, "width": 0}
|
|
if draw_outlines:
|
|
for group in cim_lyr.renderer.groups:
|
|
for unique_value_class in group.classes:
|
|
for (
|
|
symbol_layer
|
|
) in unique_value_class.symbol.symbol.symbolLayers:
|
|
if isinstance(
|
|
symbol_layer,
|
|
arcpy.cim.CIMSymbols.CIMSolidStroke,
|
|
):
|
|
stroke_symbol_props["color"] = (
|
|
symbol_layer.color
|
|
)
|
|
stroke_symbol_props["width"] = (
|
|
symbol_layer.width
|
|
)
|
|
break
|
|
|
|
if stroke_symbol_props["color"]:
|
|
break
|
|
|
|
# Set default stroke width and color if unset
|
|
if draw_outlines and stroke_symbol_props["width"] == 0:
|
|
stroke_symbol_props["width"] = 0.7
|
|
|
|
if not stroke_symbol_props["color"]:
|
|
color = arcpy.cim.CreateCIMObjectFromClassName(
|
|
"CIMCMYKColor", "V3"
|
|
)
|
|
color.values = [40, 40, 40, 10, 100]
|
|
stroke_symbol_props["color"] = color
|
|
|
|
for group in cim_lyr.renderer.groups:
|
|
for unique_value_class in group.classes:
|
|
|
|
if unique_value_class.values[0].fieldValues[0] != "<Null>":
|
|
leg_id = str(
|
|
unique_value_class.values[0].fieldValues[0]
|
|
)
|
|
else:
|
|
leg_id = None
|
|
|
|
if leg_id in symbol_codes:
|
|
symbol_code = get_symbol_code_for_shape(
|
|
layer_shape_type, symbol_codes[leg_id]
|
|
)
|
|
if symbol_code == "#":
|
|
continue
|
|
|
|
code_components = get_code_components(symbol_code)
|
|
color_value = code_components["color"]
|
|
symbol_color_value = code_components["symbol_color"]
|
|
|
|
has_color = False
|
|
for (
|
|
symbol_layer
|
|
) in unique_value_class.symbol.symbol.symbolLayers:
|
|
|
|
if symbol_color_value:
|
|
update_symbol_layer_colors(
|
|
symbol_layer, symbol_color_value
|
|
)
|
|
|
|
if color_value:
|
|
# Update colors
|
|
if layer_shape_type == "Polygon":
|
|
if isinstance(
|
|
symbol_layer,
|
|
arcpy.cim.CIMSymbols.CIMSolidFill,
|
|
):
|
|
update_color(symbol_layer, color_value)
|
|
has_color = True
|
|
|
|
if isinstance(
|
|
symbol_layer,
|
|
arcpy.cim.CIMSymbols.CIMSolidStroke,
|
|
):
|
|
symbol_layer.width = (
|
|
stroke_symbol_props["width"]
|
|
)
|
|
symbol_layer.color = (
|
|
stroke_symbol_props["color"]
|
|
)
|
|
|
|
elif layer_shape_type == "Polyline":
|
|
if isinstance(
|
|
symbol_layer,
|
|
arcpy.cim.CIMSymbols.CIMSolidStroke,
|
|
):
|
|
update_color(symbol_layer, color_value)
|
|
has_color = True
|
|
elif layer_shape_type == "Point":
|
|
if isinstance(
|
|
symbol_layer,
|
|
arcpy.cim.CIMSymbols.CIMMarker,
|
|
):
|
|
update_color(symbol_layer, color_value)
|
|
has_color = True
|
|
|
|
# Draw symbol background colors
|
|
if not has_color and color_value:
|
|
if layer_shape_type == "Polygon":
|
|
# Add fill and stroke symbol layers
|
|
fill_symbol = arcpy.cim.CIMSolidFill()
|
|
update_color(fill_symbol, color_value)
|
|
|
|
unique_value_class.symbol.symbol.symbolLayers.append(
|
|
fill_symbol
|
|
)
|
|
|
|
# Add stroke symbol layer
|
|
if (
|
|
draw_outlines
|
|
and stroke_symbol_props["color"]
|
|
):
|
|
stroke_symbol = arcpy.cim.CIMSolidStroke()
|
|
stroke_symbol.color = stroke_symbol_props[
|
|
"color"
|
|
]
|
|
stroke_symbol.width = stroke_symbol_props[
|
|
"width"
|
|
]
|
|
unique_value_class.symbol.symbol.symbolLayers.append(
|
|
stroke_symbol
|
|
)
|
|
elif layer_shape_type == "Polyline":
|
|
# Add stroke symbol layer
|
|
stroke_symbol = arcpy.cim.CIMSolidStroke()
|
|
update_color(stroke_symbol, color_value)
|
|
unique_value_class.symbol.symbol.symbolLayers.append(
|
|
stroke_symbol
|
|
)
|
|
elif layer_shape_type == "Point":
|
|
# Add marker symbol layer
|
|
marker_symbol = arcpy.cim.CIMMarker()
|
|
update_color(marker_symbol, color_value)
|
|
unique_value_class.symbol.symbol.symbolLayers.append(
|
|
marker_symbol
|
|
)
|
|
|
|
# Push changes back to layer object
|
|
layer.setDefinition(cim_lyr)
|
|
|
|
else:
|
|
raise ValueError(
|
|
f"Execution aborted: please apply a Unique Value Renderer to layer {layer.name}"
|
|
)
|
|
|
|
def postExecute(self, _):
|
|
return
|
|
|
|
|
|
def update_color(symbol_layer, color_value):
|
|
color = arcpy.cim.CreateCIMObjectFromClassName("CIMCMYKColor", "V3")
|
|
color.values = color_value["CMYK"]
|
|
symbol_layer.color = color
|
|
|
|
|
|
def update_symbol_layer_colors(symbol_layer, symbol_color_value):
|
|
if isinstance(
|
|
symbol_layer,
|
|
arcpy.cim.CIMSymbols.CIMCharacterMarker,
|
|
):
|
|
for sub_symbol_layer in symbol_layer.symbol.symbolLayers:
|
|
update_color(sub_symbol_layer, symbol_color_value)
|
|
|
|
if isinstance(
|
|
symbol_layer,
|
|
arcpy.cim.CIMSymbols.CIMVectorMarker,
|
|
):
|
|
for marker_graphic in symbol_layer.markerGraphics:
|
|
for sub_symbol_layer in marker_graphic.symbol.symbolLayers:
|
|
update_color(sub_symbol_layer, symbol_color_value)
|
|
|
|
|
|
def get_symbol_code_for_shape(shape_type, symbol_codes):
|
|
if shape_type == "Polygon":
|
|
return symbol_codes["fill"]
|
|
elif shape_type == "Polyline":
|
|
return symbol_codes["stroke"]
|
|
elif shape_type == "Point":
|
|
return symbol_codes["marker"]
|
|
else:
|
|
raise ValueError(
|
|
"Execute error: unknown shape type - allowed types: Point, Polyline and Polygon"
|
|
)
|
|
|
|
|
|
def get_code_components(code):
|
|
code_len = len(code)
|
|
if code_len == 4:
|
|
return {
|
|
"color": decode_color(code),
|
|
"symbol_key": "",
|
|
"symbol_color": {},
|
|
}
|
|
elif code_len == 10:
|
|
return {
|
|
"color": {},
|
|
"symbol_key": code[0:7],
|
|
"symbol_color": get_symbol_color(code[7:]),
|
|
}
|
|
elif code_len == 11:
|
|
return {
|
|
"color": decode_color(code[:4]),
|
|
"symbol_key": code[4:12],
|
|
"symbol_color": {},
|
|
}
|
|
elif code_len == 14:
|
|
return {
|
|
"color": decode_color(code[:4]),
|
|
"symbol_key": code[4:11],
|
|
"symbol_color": get_symbol_color(code[11:]),
|
|
}
|
|
else:
|
|
raise ValueError(f"Execution aborted: unknown symbol code {code}")
|
|
|
|
|
|
def decode_color(color_string):
|
|
return {
|
|
"CMYK": [
|
|
get_percentage_from_letter(color_string[0]),
|
|
get_percentage_from_letter(color_string[1]),
|
|
get_percentage_from_letter(color_string[2]),
|
|
get_percentage_from_letter(color_string[3]),
|
|
100,
|
|
],
|
|
}
|
|
|
|
|
|
def get_percentage_from_letter(letter):
|
|
if letter == "F":
|
|
return 15
|
|
elif letter == "V":
|
|
return 100
|
|
elif letter == "S":
|
|
return 6
|
|
elif letter == "X":
|
|
return 0
|
|
else:
|
|
return int(letter) * 10
|
|
|
|
|
|
def get_symbol_color(color_string):
|
|
if color_string == "BLK":
|
|
return {
|
|
"CMYK": [0, 0, 0, 100, 100],
|
|
}
|
|
elif color_string == "ROT":
|
|
return {
|
|
"CMYK": [0, 100, 100, 21, 100],
|
|
}
|
|
elif color_string == "CYN":
|
|
return {
|
|
"CMYK": [100, 0, 0, 0, 100],
|
|
}
|
|
elif color_string == "MGT":
|
|
return {
|
|
"CMYK": [0, 100, 0, 0, 100],
|
|
}
|
|
elif color_string == "BLA":
|
|
return {
|
|
"CMYK": [70, 30, 0, 21, 100],
|
|
}
|
|
elif color_string == "BRN":
|
|
return {
|
|
"CMYK": [45, 73, 93, 21, 100],
|
|
}
|
|
elif color_string == "GRN":
|
|
return {
|
|
"CMYK": [70, 0, 70, 21, 100],
|
|
}
|
|
elif color_string == "GLB":
|
|
return {
|
|
"CMYK": [0, 0, 100, 0, 100],
|
|
}
|
|
elif color_string == "ORA":
|
|
return {
|
|
"CMYK": [0, 32.16, 100, 0, 100],
|
|
}
|
|
elif color_string == "WHT":
|
|
return {
|
|
"CMYK": [100, 100, 100, 0, 100],
|
|
}
|
|
else:
|
|
raise ValueError(f"Execution aborted: unknown color code {color_string}")
|