Hi all ๐
I want to make a set of user friendly custom tools for Web Maps in a Web Experience that each correspond to one layer and one field, and the user selects a value from a dropdown menu of unique values for that field to filter it. So for example, I'd make a tool called "Filter by State" with a dropdown box that says "Ohio", "Pennsylvania" and "Kentucky". The user would make their selection and click "Run" and the layer I hardcode would be filtered with a Definition Query for that state and display accordingly.
However, what was pretty trivial to do in ArcGIS Pro has proved to be impossible for me in the context of a Web Experience. I can't get it to apply it as a filter that the user can see, the same way if they applied a filter.
I am in an organization so the login information reflects that, if I'm doing it wrong please let me know.
# ---------- 3. Execute ----------
def execute(self, params, messages):
p_layer, p_field, p_op, p_val = params
layer_name = (getattr(p_layer.value, "name", None)
or p_layer.valueAsText
or str(p_layer.value))
lyr_obj = p_layer.value
fld_real = _base_field(p_field.valueAsText or "")
# Validate we have all required inputs
if not (lyr_obj and fld_real and p_op.value and p_val.value):
raise RuntimeError("Required parameters missing.")
# Build the SQL clause
ftype = next((f.type for f in arcpy.ListFields(lyr_obj, fld_real)),
"String")
sql = f"{fld_real} {p_op.value} {self._val_sql(p_val.value, ftype)}"
# Get the simple layer name (without path)
# This handles cases where layer name has path format: "Group\LayerName"
simple_layer_name = layer_name.split("\\")[-1] if "\\" in layer_name else layer_name
messages.addMessage(f"Layer name: {layer_name}")
messages.addMessage(f"Simple layer name: {simple_layer_name}")
# First try the local approach (ArcGIS Pro)
applied = False
try:
aprx = arcpy.mp.ArcGISProject("CURRENT")
for m in aprx.listMaps():
for lyr in m.listLayers():
# Try both full name and simple name
if (lyr.name == layer_name or lyr.name == simple_layer_name) and lyr.supports("DEFINITIONQUERY"):
lyr.definitionQuery = sql
applied = True
messages.addMessage(f"Applied filter to layer '{lyr.name}' in ArcGIS Pro")
break
if applied:
break
except Exception as e:
messages.addWarningMessage(f"Local application failed: {str(e)}")
# If local application failed, try to apply to web map
if not applied:
try:
messages.addMessage("Attempting to apply filter to web map...")
# Request webmap title from user
webmap_title = arcpy.GetParameterAsText(4) or None
if not webmap_title:
messages.addWarningMessage("No web map title provided - skipping web map update.")
else:
messages.addMessage(f"Attempting to update web map: {webmap_title}")
# Import the GIS modules required for web map updates
import copy
from json import dumps
from arcgis.gis import GIS
# Connect to the GIS using the current Pro connection
messages.addMessage("Connecting to GIS using active ArcGIS Pro connection...")
gis = GIS("pro")
messages.addMessage(f"Connected as: {gis.properties.user.username}")
# Search for the web map by title
messages.addMessage(f"Searching for web map: {webmap_title}")
webmap_search = gis.content.search(
query=f"title:\"{webmap_title}\"", item_type="Web Map"
)
if not webmap_search:
messages.addWarningMessage(f"No web maps found with title '{webmap_title}'")
else:
# Find exact match for the web map
webmap_item = None
for item in webmap_search:
if item.title == webmap_title:
webmap_item = item
break
if not webmap_item:
messages.addWarningMessage(f"No exact match for web map title '{webmap_title}'")
else:
messages.addMessage(f"Found web map: {webmap_item.title} (ID: {webmap_item.id})")
# Get the web map JSON
webmap_data = webmap_item.get_data()
if not webmap_data or "operationalLayers" not in webmap_data:
messages.addWarningMessage("Web map data is invalid or has no operational layers")
else:
# Make a copy for modification
updated_map_json = copy.deepcopy(webmap_data)
# Print all operational layer titles for debugging
messages.addMessage("Available layers in web map:")
for idx, lyr in enumerate(updated_map_json["operationalLayers"]):
messages.addMessage(f" {idx}: {lyr.get('title', 'Unnamed')}")
# Try multiple approaches to find the target layer
target_layer = None
# 1. Try exact match on full name
target_layers = [
lyr for lyr in updated_map_json["operationalLayers"]
if lyr.get("title") == layer_name
]
# 2. If not found, try with simple name (no path)
if not target_layers:
target_layers = [
lyr for lyr in updated_map_json["operationalLayers"]
if lyr.get("title") == simple_layer_name
]
# 3. If still not found, try case-insensitive match
if not target_layers:
target_layers = [
lyr for lyr in updated_map_json["operationalLayers"]
if lyr.get("title", "").lower() == simple_layer_name.lower()
]
# 4. If still not found, try partial match (layer name is contained in title)
if not target_layers:
target_layers = [
lyr for lyr in updated_map_json["operationalLayers"]
if simple_layer_name.lower() in lyr.get("title", "").lower()
]
if not target_layers:
messages.addWarningMessage(f"Layer '{layer_name}' not found in web map using any matching method")
else:
# Get the first matching layer
target_layer = target_layers[0]
messages.addMessage(f"Found matching layer: {target_layer.get('title')}")
# Make sure layerDefinition exists
if "layerDefinition" not in target_layer:
target_layer["layerDefinition"] = {}
# Set the definition expression
target_layer["layerDefinition"]["definitionExpression"] = sql
messages.addMessage(f"Setting definition expression: {sql}")
# Update the web map
webmap_item.update(item_properties={"text": dumps(updated_map_json)})
messages.addMessage("<span class="lia-unicode-emoji" title=":white_heavy_check_mark:">โ
</span> Filter applied to web map layer successfully")
applied = True
except Exception as e:
messages.addWarningMessage(f"Failed to update web map: {str(e)}")
# Try applying directly to the layer object as a last resort
try:
if hasattr(lyr_obj, "definitionQuery"):
lyr_obj.definitionQuery = sql
messages.addMessage("Applied filter directly to layer object")
applied = True
except Exception as ex:
messages.addWarningMessage(f"Failed to apply to layer object: {str(ex)}")
if not applied:
raise RuntimeError(f"Layer '{layer_name}' not found or unsupported.")
messages.addMessage(f"<span class="lia-unicode-emoji" title=":white_heavy_check_mark:">โ
</span> SQL applied:\n {sql}")