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.

Example land use map

Neighborhood Polygons

The views of this example map will be based on neighborhood boundaries downloaded from the City of Spokane open data portal.

Downloading neighborhood polygons for Spokane, WA

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.

Creating a key 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.

Creating the template layout

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.

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
Running the automated extent script

Script Components

Layout Elements

The ListLayouts() method of the ArcGISProject object can be used to find layouts.

The listElements() can then be used to search the layout for specific elements on a layout.

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.

Strings also have a format() method that allows you to format numbers with specific numbers of decimal places or leading zeroes.

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)