Select to view content in your preferred language

Configure Layer Form based on Layer's Popup

233
2
3 weeks ago
GISDataTasPorts
Occasional Contributor

My web map layers all have optimal popups configured, because this is done automatically using arcpy (with CIM), before the layers are automatically published from ArcGIS Pro to AGOL (using the same arcpy script that configures the popups).

In the ArcGIS Online Field Maps Designer, I can interactively create nice forms for these layers by using the 'Convert from Popup' button.

However, I produce these web maps on a regular basis and they have a lot of layers.  I would like to automate the 'Convert from Popup' action (and avoid the group element which this creates).

Is/are there any (set of) function(s) in ArcGIS API for Python to perform the 'Convert from Popup' or do I have to develop this myself? I'd like to avoid a lot of time re-inventing the wheel.

If I have to do this myself, are there any examples of how this can be done in Python?  If I have to do it manually, I guess I would have to cycle through the popup elements, and convert each of them to form elements, but this is not something I've done before so it may take me some time to figure it out.

0 Kudos
2 Replies
Clubdebambos
MVP Regular Contributor

Hi @GISDataTasPorts,

There is no option with the ArcGIS API for Python that mimics that Convert popup (to form). If the maps are being produced regularly with the same layers/tables you can create template JSON files.

  1. Create the forms manually one time
  2. Extract the formInfo for each to a JSON file
  3. Apply to layers/tables programmatically

 

Export JSON file

from arcgis.gis import GIS
import json

## access AGOL
agol = GIS("home")

## get the Web Map as an Item object
wm_item = agol.content.get("WM_ITEM_ID")

## get the Web Map JSON definition as a dictionary
wm_item_data = wm_item.get_data()

## set the output folder
form_output_fldr = r"C:\Path\to\template\folder"

## the layer names as they appear in the Web Map
layer_names = [
    "Layer 1",
    "Layer 2",
    "Layer 3",
    ...,
    "Layer N"
]

## the table names as they appear in the Web Map
table_names = [
    "Table 1",
    "Table 2",
    "Table 3",
    ...,
    "Table N"
]

## for each layer in the Web Map
for lyr in wm_item_data["operationalLayers"]:
    ## if the layer is in the list
    if lyr["title"] in layer_names:

        try:
            ## output file path
            form_output_file = f"{form_output_fldr}\\{lyr['title']}.json"

            ## get the formInfo definition
            form_def = lyr["formInfo"]

            ## export to JSON file with same name as the layer
            with open(form_output_file, 'w', encoding='utf-8') as f:
                json.dump(form_def, f, ensure_ascii=False, indent=4)
        except KeyError:
            print(f"No form for {lyr['title']}")

## for each table in the Web Map
for tbl in wm_item_data["tables"]:
    ## if the tableis in the list
    if tbl["title"] in table_names:

        try:
            ## output file path
            form_output_file = f"{form_output_fldr}\\{tbl['title']}.json"
 
            ## get the formInfo definition
            form_def = tbl["formInfo"]

            ## export to JSON file with same name as the layer
            with open(form_output_file, 'w', encoding='utf-8') as f:
                json.dump(form_def, f, ensure_ascii=False, indent=4)
        except KeyError:
            print(f"No form for {tbl['title']}")

 

Apply JSON to formInfo

from arcgis.gis import GIS
import json

agol = GIS("home")

folder = r"C:\Path\to\template\folder"

## the layer names as they appear in the Web Map
layer_names = [
    "Layer 1",
    "Layer 2",
    "Layer 3",
    ...,
    "Layer N"
]

## the table names as they appear in the Web Map
table_names = [
    "Table 1",
    "Table 2",
    "Table 3",
    ...,
    "Table N"
]

## the web maps to update
wm_ids = {
    "WebMap 1" : "WM_ITEM_ID",
    "WebMap 2" : "WM_ITEM_ID",
    "WebMap 3" : "WM_ITEM_ID"
}

## for each web map
for wm_name, wm_item_id in wm_ids.items():

    print(wm_name)

    ## get the web map as an Item object
    wm_item = agol.content.get(wm_item_id)

    ## get the web map definition as a dictionary
    wm_item_data = wm_item.get_data()

    ## iterate over the layers
    for lyr in wm_item_data["operationalLayers"]:
        try:
            ## if the layer name matches a layer in the list
            if lyr["title"] in layer_names:
                ## get the matching JSON file
                json_definition_file = f"{folder}\\{lyr['title']}.json"
                ## update the formInfo
                with open(json_definition_file) as json_def:
                    form_def = json.load(json_def)
                    lyr["formInfo"] = form_def
        except FileNotFoundError:
            print(f"No form file for {lyr['title']}")

    ## do the same for tables
    for tbl in wm_item_data["tables"]:
        try:
            if tbl["title"] in table_names:
                json_definition_file = f"{folder}\\{tbl['title']}.json"
                with open(json_definition_file) as json_def:
                    form_def = json.load(json_def)
                    tbl["formInfo"] = form_def
        except FileNotFoundError:
            print(f"No form file for {tbl['title']}")

    item_properties = {"text":wm_item_data}
    wm_item.update(item_properties=item_properties)

 

You could also write your own converter using the Form Input classes from the API.

All the best,

Glen

 

 

~ learn.finaldraftmapping.com
0 Kudos
GISDataTasPorts
Occasional Contributor

Here's how I've figured out how to do this (below). It took quite some trial and error, but it works nicely. It even handles both popups stored in the web map layer AND popups stored in the feature service layer which the web map layer uses.

However, I'm quite confused by what is required in the very last line to send the updates back to the web map. It does seem to work as-is, but so do all the other commented out versions of the last line(s). So which is the correct way to run that .update()? (It does not work with no parameters, of course.)

There are still some other tweaks I need to figure out to handle different fields in different ways. At present, the 'inputType' I'm sending seems to be getting ignored. I'm currently hard-coding text-box for all fields, but getting either a text-box, or (if using a domain) a combo-box. This actually is the result I want, but I'd like to know why I can't seem to control this. I will update this post, if I figure it out.

 

import arcpy, arcgis, json

gis = arcgis.GIS(<preferred_connection_details_here>)

map_id = "<web_map_id_here>"

system_expression_infos = [{'expression': 'false',
                                'name': 'expr/system/false',
                                'returnType': 'boolean',
                                'title': 'False'},
                            {'expression': 'true',
                                'name': 'expr/system/true',
                                'returnType': 'boolean',
                                'title': 'True'}]
input_type_date_only = {'includeTime': False,
                        'type': 'datetime-picker'}
input_type_combo_box = {'noValueOptionLabel': 'No value',
                        'showNoValueOption': True,
                        'type': 'combo-box'}
input_type_text_box = {'maxLength': 255,
                        'minLength': 0,
                        'type': 'text-box'}

def service_layer_for_map_layer(lyr):
    layer_id = int(lyr['url'].split("/")[-1])
    service_id = lyr['itemId']
    service = gis.content.get(service_id)
    service_data = service.get_data()
    matching_layers = [l for l in service_data['layers'] if l['id'] == layer_id]
    if not len(matching_layers):
        arcpy.AddWarning(f"Failed to find service layer for map layer {lyr['title']}")
        return None
    return matching_layers[0]

def convert_popup_to_form(lyr):
    popup_info = None
    if "popupInfo" in lyr:
        popup_info = lyr["popupInfo"]
    else:
        s_lyr = service_layer_for_map_layer(lyr)
        if s_lyr is not None and "popupInfo" in s_lyr:
            popup_info = s_lyr["popupInfo"]
    if popup_info is None:
        arcpy.AddWarning(f"Skipping layer with no 'popupInfo':  {lyr['title']}")
        return
    arcpy.AddMessage(f"Converting popup to form for layer '{lyr['title']}'")
    if 'popupElements' in popup_info and len([e for e in popup_info['popupElements'] if 'fieldInfos' in e]):
        field_infos = [e for e in popup_info['popupElements'] if 'fieldInfos' in e][0]['fieldInfos']  #  fieldInfos is overridden in popupElements
    else:
        field_infos = popup_info['fieldInfos']  #  fieldInfos is NOT overridden in popupElements

    form_elements = []
    for f_info in field_infos:
        input_type = input_type_text_box  #  UPDATE THIS DEPENDING ON THE TYPE/DOMAIN OF THE FIELD!!!
        f_element = {
            'editableExpression': 'expr/system/false' if 'isEditable' in f_info and f_info['isEditable'] is False else 'expr/system/true',
            'fieldName': f_info['fieldName'],
            'inputType': input_type,
            'label': f_info['label'],
            'type': 'field'
        }
        form_elements.append(f_element)

    lyr['formInfo'] = {'title': popup_info['title'],
                 'expressionInfos': system_expression_infos,
                 'formElements':  form_elements,
                 }

def convert_all_popups_to_forms(layers):
    for lyr in layers:
        if lyr["layerType"] == "GroupLayer":
            convert_all_popups_to_forms(lyr["layers"])
        if lyr["layerType"] == "ArcGISFeatureLayer":
            convert_popup_to_form(lyr)

webmap_item = gis.content.get(map_id)
map_data = webmap_item.get_data()  #  Get map JSON
map_layers = map_data["operationalLayers"]

convert_all_popups_to_forms(map_layers)

arcpy.AddMessage("Saving changes to the web map")

map_data["operationalLayers"] = map_layers
webmap_item.update(data=map_data)  #  Update map JSON)  #  Update map JSON
# webmap_item.update(data=json.dumps(map_data))  #  Update map JSON
# update_properties = {"text" : json.dumps(map_data)}
# webmap_item.update(item_properties=update_properties)  #  Update map JSON
# update_properties = {"text" : map_data}
# webmap_item.update(item_properties=update_properties)  #  Update map JSON
0 Kudos