Automating Map Export Loops with ArcPy and PyQGIS

Automating Map Export Loops with ArcPy and PyQGIS

GIS Coding Tutorial · Python Automation · ArcGIS Pro · QGIS

Introduction

Imagine you have 50 administrative districts, and each district requires its own map showing boundaries, roads, settlements, a north arrow, a scale bar, a legend, and a descriptive title. When dealing with dozens of administrative districts, learning Automating Map Export Loops with ArcPy and PyQGIS becomes essential for efficient spatial data processing.

This is where Automate Map Export Loop ArcPy PyQGIS workflows become useful. Python allows you to iterate through spatial features, adjust map extents, update map titles or filters, and export each map automatically.

Instead of repeating the same operation 50 times, you define the workflow once and let the script handle every district. When working with enterprise spatial geodatabases, Automating Map Export Loops with ArcPy and PyQGIS ensures your multi-district map production runs without manual bottlenecks.

This tutorial demonstrates two approaches:

  • ArcPy with ArcGIS Pro: Open an existing project, loop through administrative features, update a layer’s definition query and map extent, and export individual PDF or PNG files.

  • PyQGIS with QGIS: Create a print layout programmatically, loop through vector features, adjust the layout map item to each feature’s extent, and export each map.

Both workflows reuse a single layout structure to maintain consistent map design across the entire batch.

What you need before starting

For the ArcGIS Pro workflow, prepare an .aprx project containing a map, a district polygon layer, and a layout with a map frame and title element.

For the QGIS workflow, prepare a project with a valid district polygon layer and the contextual map layers you want to appear in each export.

The examples use the following assumed fields and names. Replace them with the names in your own project.

Setting Example
District layer Districts
Unique district key DISTRICT_ID
District name field DISTRICT_NAME
Map layout District Layout
Output format PDF or PNG
Export resolution 300 DPI

Important distinction: ArcGIS Pro’s native Map Series and QGIS’s Atlas can already produce one page per spatial feature. Use those when your layouts follow a standard feature-driven workflow. Custom Python loops are especially useful when you need additional logic, custom filenames, extra processing, feature-specific settings, or bespoke export behavior.

 

Automating Map Export Loops with ArcPy and PyQGIS

 

Section 1: ArcPy map export loop in ArcGIS Pro

ArcPy’s arcpy.mp module provides access to ArcGIS Pro projects, maps, layers, layouts, map frames, and export functions. You can use it to automate operations that would otherwise require repeated manual interaction with the interface.

Documentation
+1

 

Step 1: Understand the project structure

Before writing code, open your ArcGIS Pro project and confirm that it contains these elements:

  • A map named District Map.

  • A district polygon layer named Districts.

  • A layout named District Layout.

  • A map frame named District Map Frame.

  • A text element named Title.

To inspect or rename layout elements, open the layout and select each item. Use the Element pane to give important elements distinct names.

The Python script will reference these names, so they must match the project exactly.

Step 2: Access the ArcGIS Pro project and layout

The first step is to load the project and obtain references to the map, layer, layout, map frame, and title.

import arcpyimport osimport re# Path to the existing ArcGIS Pro project.APRX_PATH = r"C:\GIS\DistrictProject.aprx"# Names of the map, layer, layout, and layout elements.MAP_NAME = "District Map"LAYER_NAME = "Districts"LAYOUT_NAME = "District Layout"MAP_FRAME_NAME = "District Map Frame"TITLE_ELEMENT_NAME = "Title"# Open the ArcGIS Pro project.aprx = arcpy.mp.ArcGISProject(APRX_PATH)# Retrieve the map by its project name.map_obj = aprx.listMaps(MAP_NAME)[0]# Retrieve the district layer from that map.district_layer = next(    layer    for layer in map_obj.listLayers()    if layer.name == LAYER_NAME)# Retrieve the map layout.layout = aprx.listLayouts(LAYOUT_NAME)[0]# Retrieve the map frame inside the layout.map_frame = layout.listElements(    "MAPFRAME_ELEMENT", MAP_FRAME_NAME)[0]# Retrieve the text element used for the map title.

How it works:

arcpy.mp.ArcGISProject() opens the existing project file. The listMaps(), listLayouts(), and listElements() methods retrieve the project components needed for automation.

The next() expression retrieves the first matching layer from the map. If you have multiple layers with the same name, use a more specific layer-selection rule.

ArcGIS Pro also supports arcpy.mp.ArcGISProject("CURRENT") when running the script in the Python window or notebook inside the open application.

 

Step 3: Configure the batch export parameters

Next, define the input feature class, fields, output directory, and file formats.

# Path to the district polygon feature class.DISTRICT_FEATURES = r"C:\GIS\Data\Districts.gdb\Districts"# Field containing a unique identifier for each district.ID_FIELD = "DISTRICT_ID"# Field containing the district's descriptive name.NAME_FIELD = "DISTRICT_NAME"# Directory where exported map files will be saved.OUTPUT_DIR = r"C:\GIS\Exports"# Export settings.DPI = 300EXPORT_PDF = TrueEXPORT_PNG = True# Create the output directory if it does not exist.os.makedirs(OUTPUT_DIR, exist_ok=True)# Define a helper function for safe output filenames.def safe_filename(value):    # Replace invalid or inconvenient filename characters.    cleaned = re.sub(r"[^A-Za-z0-9_-]+", "_", str(value))    # Remove unwanted separators from the beginning and end.    cleaned = cleaned.strip("_-.")    # Return a fallback name if the value is empty.    return cleaned[:80] or "district"

Why use a helper function?

District names may contain spaces, slashes, punctuation, or other characters that are unsuitable for filenames. The helper creates predictable filenames without changing the district names stored in the data.

The unique district ID will also be included in the output filename to reduce the risk of collisions between similar names.

Step 4: Loop through the district features

This is the core of the automation workflow.

For each district, the script will:

  1. Read the district ID, name, and geometry.

  2. Construct an SQL expression for the layer’s definition query.

  3. Update the layout title.

  4. Project the district geometry to the map’s coordinate system if required.

  5. Set the map frame extent.

  6. Export the layout as PDF and/or PNG.

ArcGIS Pro supports changing a feature layer’s definitionQuery, adjusting a map frame’s camera extent, and exporting a layout through arcpy.mp.

Complete ArcPy export loop

The following script extends the setup above. It exports one file per district and restores the original definition query and title after the batch finishes.

# Store the layer's original definition query for restoration.original_query = district_layer.definitionQuery or ""# Store the original title text so the project can be restored.original_title = title_element.text# Identify the map coordinate system.target_sr = map_obj.spatialReference# Count successful and failed district exports.success_count = 0failure_count = 0try:    # Read the source feature class rather than the filtered map layer.    # This keeps the cursor independent of changing definition queries.    with arcpy.da.SearchCursor(        DISTRICT_FEATURES,        [ID_FIELD, NAME_FIELD, "SHAPE@"]    ) as cursor:        # Process each district record.        for district_id, district_name, geometry in cursor:            try:                # Skip records without a usable identifier or geometry.                if district_id is None or geometry is None:                    print("Skipping a record with missing ID or geometry.")                    continue                # Use the ID as a fallback when the name is missing.                name_for_map = (                    str(district_name)                    if district_name not in (None, "")                    else str(district_id)                )

Understanding the important operations

Updating the definition query

The line:

district_layer.definitionQuery = district_clause

filters the district layer to the current feature. This is useful for keeping the map focused on the district being exported.

For example, a district with ID D014 might use:

DISTRICT_ID = 'D014'

The script accounts for text IDs and numeric IDs, because text comparisons require quotation marks. It also preserves any original filter condition.

If the project contains multiple saved definition queries or an advanced filtering setup, test the query behavior before running the entire batch. Some projects may need a dedicated layer or a native Map Series rather than repeatedly changing a heavily configured layer.

Updating the map extent

The following line uses the current polygon geometry to position the map:

map_frame.camera.setExtent(geometry.extent)

The extent is recalculated for each district. Multiplying the scale by 1.10 adds a little breathing room around the feature.

Very narrow, long, or highly irregular districts may need a custom extent-padding function or scale rules to ensure consistent framing.

Exporting high-resolution files

The script uses ArcGIS Pro’s export format objects and layout.export(). The PDF and PNG exports are set to 300 DPI.

PDF is suitable for printable maps and preserves vector content where possible. PNG is useful for websites, reports, and image-based publishing. For most cartographic deliverables, 300 DPI is a reasonable starting point, though the final resolution should reflect the intended page size and output use.

Important coordinate-system note: The script projects district geometries into the map’s spatial reference when their CRS names differ. If the datasets use different datums, supply an appropriate geographic transformation rather than relying on an unsuitable default.

An alternative: ArcGIS Pro Map Series

If your layout already has a spatial Map Series configured, you may not need to manually update each extent or definition query. The map series automatically moves through index features and can update dynamic layout elements.

For example, to export a configured Map Series to a single PDF:

import arcpyimport os# Open the project and retrieve the configured layout.aprx = arcpy.mp.ArcGISProject(    r"C:\GIS\DistrictProject.aprx")layout = aprx.listLayouts("District Layout")[0]# Retrieve the existing spatial map series.map_series = layout.mapSeries# Check that the map series exists and is enabled.if map_series and map_series.enabled:    # Define the output PDF path.    output_pdf = os.path.join(        r"C:\GIS\Exports", "All_Districts.pdf"    )    # Create the PDF format and set the resolution.    pdf = arcpy.mp.CreateExportFormat("PDF", output_pdf)    pdf.resolution = 300    # Export all map series pages into one document.    map_series.export(pdf)    print("Map Series exported:", output_pdf)else:    print("No enabled Map Series found.")

The Map Series must be authored and enabled in the layout before this script runs. Current ArcGIS Pro documentation supports Map Series exports to PDF, PNG, JPEG, and TIFF using export format objects.

Use the custom loop when you specifically need individual per-feature processing or bespoke export behavior; use Map Series when the objective is simply to produce one consistent page per index feature.

Section 2: PyQGIS map layout loop in QGIS

PyQGIS provides access to QGIS projects, vector features, print layouts, map items, and export settings. The essential classes for this workflow are:

PyQGIS class Purpose
QgsProject Access the current QGIS project
QgsPrintLayout Create a printable map layout
QgsLayoutItemMap Control the map frame and its extent
QgsLayoutItemLabel Update the map title
QgsLayoutExporter Export the layout as PDF or an image
QgsCoordinateTransform Transform feature geometry into the map CRS

The export process is similar to ArcPy, but the object model is different. In QGIS, the extent must be set on the layout’s map item to control the map being exported. Changing only the live map canvas extent does not necessarily update an existing layout map item.

api.qgis.org
+2

 

Step 1: Open the QGIS Python Console

  1. Open your QGIS project.

  2. Load the district polygon layer and any contextual layers you want to display.

  3. Confirm that the project and district layer use valid coordinate reference systems.

  4. Open Plugins → Python Console. In versions where the menu differs, look for the Python Console command under the plugin or View menus.

  5. Paste and run the following script in the console.

This example creates a new A4 landscape layout and prepares it for batch exporting.

Step 2: Initialize the project, layout, and map frame

import osimport re# Import QGIS classes for projects, layouts, maps, and exporting.from qgis.core import (    QgsProject,    QgsPrintLayout,    QgsLayoutItemMap,    QgsLayoutItemLabel,    QgsLayoutPoint,    QgsLayoutSize,    QgsLayoutItemPage,    QgsLayoutExporter,    QgsCoordinateTransform,    QgsGeometry,    QgsRectangle)# Import Qt classes for title styling and alignment.from qgis.PyQt.QtGui import QFontfrom qgis.PyQt.QtCore import Qt# Access the currently open QGIS project.project = QgsProject.instance()# Define the name of the district layer.DISTRICT_LAYER_NAME = "Districts"# Define the fields used for map titles and filenames.ID_FIELD = "DISTRICT_ID"NAME_FIELD = "DISTRICT_NAME"# Define the destination folder for exported maps.OUTPUT_DIR = r"C:\GIS\QGIS_Exports"# Set the desired image and PDF resolution.

The map frame and title are created once. The export loop will update the title and extent without rebuilding the page.

The script intentionally uses the currently visible map-canvas layers for the map item. Make sure the district layer and the intended context layers are visible when you run it. If your project needs a fixed layer set independent of the canvas, specify the desired layers explicitly.

Step 3: Iterate through the district features

Next, create the loop that processes every polygon.

Each iteration will:

  1. Read one feature and its attributes.

  2. Copy and transform the geometry into the project CRS.

  3. Compute an expanded bounding rectangle.

  4. Set the layout map item’s extent.

  5. Update the title.

  6. Export the result to a separate file.

QGIS’s zoomToExtent() is useful for this task because it fits the requested rectangle inside the existing map frame without changing the frame’s physical dimensions.

 

Complete PyQGIS export loop

Run this code after the initialization script above.

# Create a coordinate transform from the district layer CRS# to the layout map item's CRS.transform = QgsCoordinateTransform(    districts.crs(),    project.crs(),    project)# Prepare the PDF exporter settings.pdf_settings = QgsLayoutExporter.PdfExportSettings()pdf_settings.dpi = DPI# Prepare the raster image exporter settings.image_settings = QgsLayoutExporter.ImageExportSettings()image_settings.dpi = DPI# Create one exporter for the existing layout.exporter = QgsLayoutExporter(layout)# Initialize counters for the batch.success_count = 0failure_count = 0# Loop through the district features.for feature in districts.getFeatures():    try:        # Read the district ID and name attributes.        district_id = feature[ID_FIELD]        district_name = feature[NAME_FIELD]        # Use the provider feature ID if the stored ID is missing.        if district_id is None or str(district_id) == "":            district_id = feature.id()        # Fall back to the ID if the district name is empty.

This workflow creates a PDF and PNG for each valid district feature when both export options are enabled. QgsLayoutExporter supports both output types and reports a result code that can be checked to detect failed exports.

Optional: Show only the current district

The example changes the map extent but retains the selected project layers. Consequently, neighbouring district boundaries may remain visible, which is often desirable for context.

If each map must display only the current district polygon, consider using a dedicated highlight layer, a controlled layer subset, or a QGIS Atlas configuration. If using a subset filter, save the original subset expression and restore it after the batch. Avoid changing the source layer’s filter before the feature loop is complete if doing so could affect which features are returned.

Optional: Export PDF only

For a PDF-only workflow, set:

EXPORT_PDF = TrueEXPORT_PNG = False

For PNG-only output:

EXPORT_PDF = FalseEXPORT_PNG = True

This avoids producing duplicate deliverables when only one format is required.

Section 3: Debugging and performance optimization

A script that works for three districts may still encounter problems when processing hundreds of features. The best way to make batch export reliable is to anticipate errors and avoid unnecessary operations inside the loop. Setting up automated error handling is a crucial best practice when Automating Map Export Loops with ArcPy and PyQGIS across hundreds of features.

1. Use predictable file naming conventions

Avoid naming every output map.pdf. Each feature should have a stable, unique filename, such as:

Exports/
├── Central_001.pdf
├── Central_001.png
├── Eastern_002.pdf
├── Eastern_002.png
├── Northern_003.pdf
└── Northern_003.png

The examples above combine a sanitized district name and ID. This helps prevent files from being overwritten when two districts have similar names.

Keep a record of the output files and failed features if the exports are part of a formal reporting pipeline.

2. Handle failed exports without stopping the batch

A single feature can fail because of invalid geometry, missing attributes, broken data sources, or export errors.

Use a try/except block inside the feature loop so one failure does not terminate all remaining exports. The complete scripts above demonstrate this pattern.

For production workflows, consider writing errors to a log file with the feature ID, time, output filename, and error message rather than relying only on console output.

3. Avoid unnecessary layout creation

Proper error handling ensures that Automating Map Export Loops with ArcPy and PyQGIS runs smoothly from start to finish. A common performance mistake is rebuilding the entire layout for every district.

Instead:

  • Create one layout and map frame before the loop.

  • Reuse the same layout and exporter objects.

  • Update only the title, definition query, extent, and other properties that change.

  • Read the spatial data once where practical.

  • Avoid repeatedly opening the same project file inside the loop.

The scripts above reuse their layouts. This reduces unnecessary object creation and makes the output more consistent.

4. Manage memory and large batches

Exporting many high-resolution images can consume substantial memory and disk space. However, the loop itself is not automatically a memory leak; repeated allocation of unnecessary objects and retaining large references can be the problem.

ArcPy tips

  • Use with arcpy.da.SearchCursor(...) so the cursor is closed automatically.

  • Reuse the existing project, layout, and map-frame objects.

  • Avoid loading all feature geometries into a Python list when a cursor can process them sequentially.

  • Close references to temporary objects when they are no longer needed, particularly in large or long-running scripts.

  • If exporting hundreds or thousands of maps, monitor memory and test the process in batches.

PyQGIS tips

  • Reuse the same QgsPrintLayout, QgsLayoutItemMap, and QgsLayoutExporter.

  • Prefer streaming features through getFeatures() instead of building large feature lists unnecessarily.

  • Keep export settings outside the loop when they are identical for every map.

  • Avoid creating and deleting dozens of layouts when a single reusable layout is sufficient.

  • Do not force garbage collection after every feature unless profiling shows a specific need.

5. Check the coordinate reference system and map extent

Extent problems are among the most common reasons for maps that export incorrectly.

A district may be displayed in the wrong part of the layout if its geometry coordinates are not in the same CRS as the map frame.

Before a batch export, verify that:

  • The input layer has a valid CRS.

  • The feature geometry is transformed into the intended map CRS.

  • Extents have sensible dimensions.

  • The map scale and margins work for both large and small polygons.

  • The layout displays the intended layers and their correct visibility.

For geographically dispersed or unusually shaped districts, a fixed 10% extent margin may not be ideal. You can implement minimum scale limits, custom padding, or district-specific map framing rules.

6. Test before exporting everything

Start with three to five representative features: one large district, one small district, and one irregularly shaped district.

Check that each output has the correct title, extent, legend, scale bar, coordinate system, and filename. Inspect both PDF and PNG exports at the intended resolution before processing the full dataset.

A short test run can reveal issues that would otherwise be repeated across dozens of output files.

Section 4: ArcPy vs. PyQGIS — which workflow should you use?

Requirement ArcPy PyQGIS
Access existing project arcpy.mp.ArcGISProject() QgsProject.instance()
Filter a feature layer definitionQuery Layer subset or other filtering
Set map extent map_frame.camera.setExtent() map_item.zoomToExtent()
Update title Layout text element’s text property QgsLayoutItemLabel.setText()
Export PDF layout.export() QgsLayoutExporter.exportToPdf()
Export PNG layout.export() QgsLayoutExporter.exportToImage()
Native page-per-feature workflow Map Series Atlas
Main advantage Integration with ArcGIS Pro projects Flexible open-source GIS automation

The choice depends on the software and project you already use. ArcPy is particularly convenient when the maps depend on an established ArcGIS Pro layout. PyQGIS is useful for projects built around QGIS layers, print layouts, and open-source data workflows.

Conclusion: Mastering Automating Map Export Loops with ArcPy and PyQGIS

Automating map exports with Python turns repetitive cartographic production into a repeatable GIS workflow. Instead of manually zooming to 50 administrative districts, updating each title, and exporting every page, you can define those operations once and apply them to every feature.

The essential pattern is:

Access the project → retrieve the layout → iterate through spatial features → update the filter and extent → export the map → record the result.

For ArcGIS Pro, this can be done with arcpy.mp, layer definition queries, map-frame cameras, and export format objects. For QGIS, it can be done with QgsPrintLayout, QgsLayoutItemMap, and QgsLayoutExporter.

For straightforward page-per-feature jobs, consider the native ArcGIS Pro Map Series or QGIS Atlas before implementing a custom loop. For specialized export rules, dynamic filtering, or additional per-district processing, custom scripts provide more control.

The result is not simply faster exporting. It is a reproducible cartographic production pipeline in which every map follows the same layout standards, filenames are predictable, and failures can be identified and corrected systematically. Ultimately, mastering Automating Map Export Loops with ArcPy and PyQGIS saves countless hours of manual cartographic work.

Official documentation