Map Automation in ArcGIS Pro
Automated map production workflows are "machine-driven processes that result in the completion of tasks that relate to the compilation, construction, or output of a map product" (Buckley and Watkins 2009).
The ArcPy module is primarily useful for automating detailed, repetitive, or tedious tasks (Zandbergen 2020, pp 356), making it the ideal vehicle for automating map production workflows in ArcGIS Pro.
This tutorial will demonstrate an automated workflow for producing multiple neighborhood views of a single map, and exporting those views to PDF files.
Automating Map Views
There are occasions where you may need to create multiple smaller views of a larger map that permit closer examination of detail in specific areas. While this type of mapping is less common with the advent of interactive web mapping, sequences of map views may still have value for creation of reports that need to be printed and / or retained for archival purposes.
Main Map
The map used for this example is a land use map with layers for roads, railroads, water bodies, and land use using open data from the city of Spokane, WA.
Make sure the map uses an appropriate State Plane Coordinate System.
Neighborhood Polygons
The views of this example map will be based on neighborhood boundaries downloaded from the City of Spokane open data portal.
- Download and unzip the shapefile.
- Use the Export Features tool to copy the shapefile into your project database with a meaningful name.
- Symbolize the boundaries with dashed lines and label them with semitransparent labels so they are clearly identifiable on the individual area maps.
- Remove the base map to avoid cluttering the small area.
- Note that although neighborhood boundary data has clear, unambiguous borders, neighborhoods are vernacular areas whose boundaries are subjective and evolve over time under the influence of ethnic groups, real estate developers, and urban planners (city government). Neighborhoods often grow and shrink over time as neighborhoods change over time. Residents in adjacent homes may think of themselves as living in different neighborhoods even if there are no clear physical markers to indicate any difference between the neighborhood identity of their properties.
Key Map
A key map is a small map placed in a layout that gives a broader context for where the mapped area is located relative to the surrounding areas.
- Insert a new Map.
- Rename the map Key Map so the script will be able to find it.
- Add Data the neighborhood boundaries and give them a light gray fill, and dark gray outline.
- Copy / paste to duplicate the layer, and symbolize it with the highlighted color (red).
- Name the red layer Highlight so the script can find and modify it.
- Right click on the layer in the Contents pane, select Properties and Definition Query and select a specific Name. This will be changed by the script as it maps individual neighborhoods.
- Turn off the base maps.
Make sure the map uses the same State Plane Coordinate System as the main map.
Template Layout
The first step is creating a template map that defines the symbology for the sequence of maps, and a template layout that defines the layout for each of the maps.
Because ArcPy won't allow creation of maps, layouts, or map elements, you will need to set up a template layout that will be used for all output maps.
- Add a 8.5 x 11" landscape layout and give it an internal name of Template that will be used by the automation script.
- Insert a map frame and give it an internal name of Main Map.
- Insert the title text box and give it an internal name of Title.
- Insert a legend and format it.
- Insert text boxes for the credits and the disclaimer.
- Insert a map frame for the key map and give it an internal name of Key Map.
- Insert neat lines for the legend and metadata.
- Insert a scale bar.
Automated Extent
The following script will loop through the neighborhood polygons, zoom and pan to center the neighborhood in the map frame (with padding around the edges), and export to PDF files based on the neighborhood name.
- Insert, Python, New Notebook
- Close the notebook, View the Catalog Pane, Rename the notebook, and double click it to reopen the renamed notebook.
- Copy the automated extent script into the notebook.
- Run the notebook
- Monitor the script name output and the PDF files as they are created.
The parameters are based on the names used in the maps and layouts above.
import arcpy # Parameters neighborhoods = 'Neighborhoods' name_field = 'Name' main_frame_name = 'Main Map' key_map_name = 'Key Map' key_frame_name = 'Key Map' highlight_layer_name = 'Highlight' template_name = 'Template' title_textbox_name = 'Title' title_prefix = 'Spokane Neighborhoods:\n' output_folder = 'U:\\Downloads\\' # Find all the layout elements aprx = arcpy.mp.ArcGISProject('current') layout = aprx.listLayouts(template_name)[0] main_frame = layout.listElements('MapFrame_Element', main_frame_name)[0] title_textbox = layout.listElements('Text_Element', title_textbox_name)[0] key_map = aprx.listMaps(key_map_name)[0] highlight_layer = key_map.listLayers(highlight_layer_name)[0] # Define the cursor for the neighborhood polygons and loop through neighborhoods cursor = arcpy.da.SearchCursor(neighborhoods, ['SHAPE@', name_field]) for map_number, neighborhood in enumerate(cursor): print(neighborhood[1]) # Create and zoom to a padded extent for the neighborhood extent = neighborhood[0].extent padding = (extent.XMax - extent.XMin) / 20 new_extent = arcpy.Extent(extent.XMin - padding, extent.YMin - padding, extent.XMax + padding, extent.YMax + padding, None, None, None, None, extent.spatialReference) main_frame.camera.setExtent(new_extent) # Change the title title_textbox.text = title_prefix + neighborhood[1] # Update the key map highlight_layer.definitionQuery = "(" + name_field + " = '" + neighborhood[1] + "')" # Export the PDF outname = output_folder + '{:02d}'.format(map_number) \ + '_' + str(neighborhood[1]).replace(' ', '_').replace('/', '_') layout.exportToPDF(outname) del cursor
Script Components
Layout Elements
The ListLayouts() method of the ArcGISProject object can be used to find layouts.
- If a string parameter is given, the method searches for layouts with internal names that match that string.
- The method always returns an array, so you need to index by zero [0] to get the first layout object found that matches the search criteria.
The listElements() can then be used to search the layout for specific elements on a layout.
- The first parameter is the type of element to search for. Typical values include MAPFRAME_ELEMENT, TEXT_ELEMENT, and LEGEND_ELEMENT.
- If given, the second parameter is an internal name to search for.
- The method always returns an array, so you need to index by zero [0] to get the first layout found that matches the search criteria.
import arcpy aprx = arcpy.mp.ArcGISProject("current") layout = aprx.listLayouts(template_name)[0] main_frame = layout.listElements("MapFrame_Element", main_frame_name)[0] title_textbox = layout.listElements("Text_Element", title_textbox_name)[0]
Textbox Text
Layout TextElement objects have a text property that can be changed to change the text displayed in the text box.
title_textbox.text = title_prefix + neighborhood[1]
Map Layers
The ListMaps() method of the ArcGISProject object can be used to find maps.
The listLayers() method can then be used to search the map for a specific layer.
key_map = aprx.listMaps(key_map_name)[0] highlight_layer = key_map.listLayers(highlight_layer_name)[0]
Enumeration
The Python enumerate() function returns an enumerate object that can be used with a for loop to keep track of the index of iterated values.
Each iteration of enumerate() returns a tuple with the index and the value. Tuples are a basic data type in Python that stores multiple values in one variable.
For example:
fruits = ["Pineapple", "Pear", "Lychee", "Mango" ] for number, fruit in enumerate(fruits): print(str(number + 1) + ") " + fruit)
Will display:
1) Pineapple 2) Pear 3) Lychee 4) Mango
enumerate() is used in the automation script to get a unique number for each neighborhood that is used later to create numbered PDF output file names.
cursor = arcpy.da.SearchCursor(neighborhoods, ['SHAPE@', name_field]) for map_number, neighborhood in enumerate(cursor): print(neighborhood[1])
Setting Extents
ArcPy geometries returned by cursors with the SHAPE@ special attribute have an extent property that returns an Extent object defining a rectangle for the entire area covered by the geometry.
The corners of an Extent object can be found using the Xmin, Xmax, Ymin, and Ymax parameters.
Extents have a spatialReference parameter that gives an object indicating the coordinate system used for the X and Y values.
The automation script creates a new extent padded 200 meters on each side from the extent of each neighborhood feature returned by the cursor.
That new extent can then be applied to a map frame using the setExtent() method of the map frame's camera, which defines the view given in the map frame.
padding = 200 extent = neighborhood[0].extent new_extent = arcpy.Extent(extent.XMin - padding, extent.YMin - padding, extent.XMax + padding, extent.YMax + padding, None, None, None, None, extent.spatialReference) main_frame.camera.setExtent(new_extent)
String Replacement and Formatting
Python strings have a replace() method that allows you to replace all occurrances of a string specified as the first parameter with the string specified in the second parameter.
- In the automation script, spaces in neighborhood names are replaced with underscores (_) and slashes (/) are replaced with underscores to create file names that are valid on all operating systems.
Strings also have a format() method that allows you to format numbers with specific numbers of decimal places or leading zeroes.
- The sequential map numbers are formatted with leading zeros with the {:02d} string so that the file names appear in sequential order.
outname = "U:\\Downloads\\" + "{:02d}".format(map_number) \ + "_" + str(neighborhood[1]).replace(" ", "_").replace("/", "_")
Export PDF
Layout objects have an exportToPDF() method that can be used to export a layout to a PDF file.
layout.exportToPDF(outname)