Update Multiple Web Maps Based on Changes in a "Master" Web Map

1345
7
05-12-2017 01:23 PM
MarissaWalter
New Contributor II

Using the ArcGIS Python API and Jupyter Notebook, I've figured out how to automate the creation of multiple web maps in our portal based on copying a "master" web map. During this process, I update only a few things on the new maps (title, description, initial extent, definition expression on a layer). 

But with the automation of creating 50+ web maps, I am now trying to figure out how I could automate updating all these maps if there were a change in the "master" web map. 

I have a couple similar ideas, but not sure how to implement them, or if they are even possible. Or if there is a more efficient solution to this...

1) Deep copy of the "master" webmap dictionary (ignoring the keys that cannot change in the map to be updated - ex. id, created, title, extent, layer definition expression) and then using the copy to replace all the other keys in my "map-to-be-updated"

2) Comparing the dictionaries of the "master" and "map-to-be-updated", find any differences (again ignoring the keys that cannot change) and taking the differences found and applying them to the "map-to-be-updated"

I'm also not entirely sure how to search for and work with multiple items based on the examples provided... if anyone has info on that as well.

I've reviewed (and used) these examples the most:

Publishing web maps and web scenes | ArcGIS for Developers 

Using and updating GIS content | ArcGIS for Developers  

I'm fairly new to Python as well, so any suggestions would be appreciated!

0 Kudos
7 Replies
KaushikMysorekar
New Contributor III

Hi Marissa,

You should be able to read "date modified" value from the webmap description to know if the master webmap has modified or not. I've talked to my colleague; he might have additional insight on how to achieve above workflow.

MarissaWalter
New Contributor II

Thanks for the feedback! To provide some more details - I have partly figured out the update, but it isn't totally correct.

The idea is that the dictionary from the master web map replaces the map's current dictionary, and then I update the definition expression, title, description, and extent to return the map back to it's pre-updated state with the same details and map id.

The code below successfully updates the maps if there is a change to the master map's basemap. However, I tried changing the symbology (of the layer 0) in the master map and found that this method did not work.

It seems like this line (trying to update the definition expression back to the state that map should show) is causing issues updating the symbology:

web_map_dict['operationalLayers'][0]['layerDefinition'] = {'definitionExpression': "State = \'" + statesList[i] + "\'"}1‍‍‍‍

# Get the dictionary of the master_map_item
# It will replace the dictionary of the maps to update
master_map_dict = master_map_item.get_data(try_json=True)

for i in range(len(titleList)):
    # Search here to get the maps that were created using a list of their titles 
    webmap_search = gis.content.search(titleList[i], item_type="Web Map")
    web_map_item = webmap_search[0]
    
    # Get and set the dictionary of the map to be updated
    web_map_dict = web_map_item.get_data(try_json=True)

    # Set the dictionary of the map to be updated equal to the master item's dictionary
    web_map_dict = master_map_dict
     
    # Change the web_map_dict back to reflect orig state: def expression, title, description, state extent
    # Works by iterating through lists of states, titles, descriptions, and extents that correspond to the 50 states in ABC order 

    web_map_dict['operationalLayers'][0]['layerDefinition'] = {'definitionExpression': "State = \'" + statesList[i] + "\'"}
    
    web_map_properties = {'title': titleList[i],
                          'snippet':descList[i],
                          'extent':stateExtentList[i],
                         'text':json.dumps(web_map_dict)} 

    # Update the item - giving it the "new" - aka original properties back
    web_map_item.update(web_map_properties)‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

This is probably pretty rough as I'm still learning the API and Python, but any suggestions are appreciated. Thanks!

MichaelWilburn
Esri Contributor

Hi Marissa,

Are you persisting the drawingInfo information when updating the layerDefinition? This is what stores the symbology.

You can read more about the properties of the layerDefinition object here: ArcGIS REST API 

MarissaWalter
New Contributor II

Hi Michael - 

Yup, that was the issue. 

This line was replacing all the drawing info with only the definitionExpression:

web_map_dict['operationalLayers'][0]['layerDefinition'] = {'definitionExpression': "State = \'" + statesList[i] + "\'"}

This resolved the symbology issue while still applying the expression: 

web_map_dict['operationalLayers'][0]['layerDefinition']['definitionExpression'] = "State = \'" + statesList[i] + "\'"

So far this update seems to work properly, however I would still like to figure out how to automate this further by eliminating the need to manually change the layer index. Perhaps searching for the expression in a loop and replacing the specific value? Need to spend some more time on that one. 

0 Kudos
MichaelWilburn
Esri Contributor

Nicely done! I didn't do a very good job of explicitly stating what I meant to try, but it seems you made sense of it.

Insofar as avoiding having to manually change the layer index to update the `definitionExpression` of each, you can retrieve the length of the `operationalLayers` list and then automate iterating through the `web_map_dict` as many times as necessary to update each layer. That could look something like this:

num_layers = len(web_map_dict['operationalLayers'])

i = 0
while i < num_layers:
     web_map_dict['operationalLayers'][i]['layerDefinition']['definitionExpression'] = ### apply expression here
     i += 1‍‍‍‍‍‍‍‍‍‍‍‍
MarissaWalter
New Contributor II

Great thanks! I will try that out. 

But first... I did find another issue when updating my definitionExpression with this line: 

web_map_dict['operationalLayers'][0]['layerDefinition']['definitionExpression'] = "State = \'" + statesList[i] + "\'"

This does not work for ArcGISMapServiceLayers because the JSON to get to the definitionExpression is slightly different.

Any ideas on how I need to modify the above line to get "definitionExpression": "ST_ABBREV = 'MN'" to update on this layer?

{
  "operationalLayers": [
    {
      "id": "US_ReferenceGeography_points_2789",
      "layerType": "ArcGISMapServiceLayer",
      "url": "https://.../arcgis/rest/services/US_ReferenceGeography_points/MapServer",
      "visibility": true,
      "opacity": 1,
      "title": "US_ReferenceGeography_points",
      "itemId": "1304c6c5e95f4c36aab5d07838ec31cb",
      "layers": [
        {
          "id": 1,
          "layerDefinition": {
            "definitionExpression": "ST_ABBREV = 'MN'"
          }
        }
      ]
    },‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

I've tried this, but keep getting "TypeError: list indices must be integers, not str."  

web_map_dict['operationalLayers'][0]['layers']['layerDefinition']['definitionExpression'] = "ST_ABBREV = \'" + statesList[i] + "\'"

This also opens the door to potentially adding logic to handle definition expressions on multiple layer types. 

0 Kudos
MichaelWilburn
Esri Contributor

So close! Try instead:

web_map_dict['operationalLayers'][0]['layers'][0]['layerDefinition']['definitionExpression'] = "ST_ABBREV = \'" + statesList[i] + "\'"‍‍

`operationalLayers` is a list of dictionaries. Likewise, `layers` is also a list of dictionaries and not a dictionary itself, hence why you'll have to specify the list position of the item whose layerDefinition you'd like to update.