1

I'm using ArcGIS Pro, and ArcPy.

I have a file geodatabase with 1,000 unique polygons. What I would like to do is export each feature as a PNG image. I'm expecting to have 1,000 image files when it's done.

I have studied the object model and got to point where I am able to export the mapframe.

In ArcGIS Pro desktop, I have setup the print layout, mapframe, map, layer definition query, and everything else. My APRX project file is showing one feature, zoomed to it's extents.

But the exported PNG file is the whole world - 10Mb in size, where I am expecting a small image.

So why does the ArcPy code revert to the whole world, and how can I change it?

layout = proj.listLayouts("FirstPrintLayout")[0]
print (f'2 - Which contains a layout named: {layout.name}')
mapframe = layout.listElements("MAPFRAME_ELEMENT", "Map Frame 1")[0]
print (f'3 - And the layout has a map frame where everything starts...{mapframe.name}')
print (f'4 - The map inside the mapframe is {mapframe.map.name}')

for lyr in mapframe.map.listLayers("MyLayer"):
    if lyr.supports("DEFINITIONQUERY"):
        print(lyr.name, ' has defQuery ', lyr.definitionQuery)
        print ('Excellent! So now lets get the extents')
        mapExtent = mapframe.getLayerExtent(lyr)

print (f'5 - mapFrame  XMin extents: {mapExtent.XMin}')
print (f'Ymin {mapExtent.YMin}')
print (f'XMax {mapExtent.XMax}')
print (f'YMax {mapExtent.YMax}')


mapframe.zoomToAllLayers (selection_only=True)

print ('Should be at the correct level of zoom')

mapframe.exportToPNG(out_png='out_png', resolution=60)
exit()

4 Answers 4

2

I use data driven pages (DDP) to export certain parts of dataframe. Perhaps it will work for you because I don't think that you have 1k of layers, rather 1k polygons sitting in single feature class. I copied polygons to feature class called PAGES and recomputed their shape using:

!Shape!.buffer(20).extent.polygon

enter image description here

Defined DDP and use definition query (pages) on original polygons:

enter image description here

It took 5 minutes to export 100 pages with extremely slow background of aerial image (server)

enter image description here

Script works in ArcGIS:

import arcpy, os
from arcpy import env
env.overwriteOutput = True
outFolder=arcpy.GetParameterAsText(0)
env.workspace = outFolder
dpi=72
tempRaster=outFolder+os.sep+"victim.png"

mxd = arcpy.mapping.MapDocument("CURRENT")
ddp = mxd.dataDrivenPages
thePagesLayer = ddp.indexLayer
df = arcpy.mapping.ListDataFrames(mxd)[0]
fld = ddp.pageNameField.name
for pageID in range(1, ddp.pageCount+1):
    ddp.currentPageID = pageID
    arcpy.mapping.ExportToPNG(mxd,tempRaster,df, df_export_width=1600,
                              df_export_height=1300, world_file=True)
    fName=outFolder+os.sep+str(ddp.pageRow.getValue(fld))+".png"
    shp=ddp.pageRow.getValue("Shape")
    ext=shp.extent
    ext2d = " ".join(str(ext).split(" ")[:4])
    arcpy.Clip_management (tempRaster, ext2d,fName)
    arcpy.AddMessage('%s processed' %fName)
    arcpy.Delete_management(tempRaster)
    

Note you can play with individual scale for each page.

2
2

From my limited understanding of your code.

You are currently setting up a loop through the layers in your Map and checking if there are any Definition Queries associated with the layers. You then get the layer extents.

The loop runs through all the layers and gets the extents for everything, and then exits the loop. You then try set up the mapframe using the max and min x and y coordinates and then export it...

So it looks like the script is doing what you told it to do. To fix this, perhaps try adding the last lines of code to within the loop.

layout = proj.listLayouts("FirstPrintLayout")[0]
print (f'2 - Which contains a layout named: {layout.name}')
mapframe = layout.listElements("MAPFRAME_ELEMENT", "Map Frame 1")[0]
print (f'3 - And the layout has a map frame where everything starts...{mapframe.name}')
print (f'4 - The map inside the mapframe is {mapframe.map.name}')

for lyr in mapframe.map.listLayers("MyLayer"):
    if lyr.supports("DEFINITIONQUERY"):
        print(lyr.name, ' has defQuery ', lyr.definitionQuery)
        print ('Excellent! So now lets get the extents')
        mapExtent = mapframe.getLayerExtent(lyr)

        print (f'5 - mapFrame  XMin extents: {mapExtent.XMin}')
        print (f'Ymin {mapExtent.YMin}')
        print (f'XMax {mapExtent.XMax}')
        print (f'YMax {mapExtent.YMax}')
    
        mapframe.zoomToAllLayers (selection_only=True)

        print ('Should be at the correct level of zoom')

        mapframe.exportToPNG(out_png='out_png', resolution=60)
exit()

You will need to dynamically change the outfile too. Something like:

out_png = "out_png" + lyr.name + ".png" 

Might work.

EDIT

Here is an extract from a script that cycles through a series of polygons and extracts a PDF / JPEG.

Modding it to your code:

layout = proj.listLayouts("FirstPrintLayout")[0]
print (f'2 - Which contains a layout named: {layout.name}')
mapframe = layout.listElements("MAPFRAME_ELEMENT", "Map Frame 1")[0]
print (f'3 - And the layout has a map frame where everything starts...{mapframe.name}')
print (f'4 - The map inside the mapframe is {mapframe.map.name}')

for lyr in mapframe.map.listLayers("MyLayer"):
    if lyr.supports("DEFINITIONQUERY"):
        print(lyr.name, ' has defQuery ', lyr.definitionQuery)
        print ('Excellent! So now lets get the extents')
        mapExtent = mapframe.getLayerExtent(lyr)

        print (f'5 - mapFrame  XMin extents: {mapExtent.XMin}')
        print (f'Ymin {mapExtent.YMin}')
        print (f'XMax {mapExtent.XMax}')
        print (f'YMax {mapExtent.YMax}')

        fid = 0
        sql = """"Title" >= '%s'""" % fid

        arcpy.SelectLayerByAttribute_management(in_layer_or_view=lyr,
                                       selection_type='NEW_SELECTION',
                                       where_clause=sql)

        mapframe.zoomToSelectedFeatures()
        arcpy.RefreshActiveView()
        out_png = r'Path_to_location' + lyr.name + ('.png')

        print ('Should be at the correct level of zoom')
        # proj below is the aprx file
        mapframe.exportToPNG(proj, out_png=out_png', resolution=60)
exit()

I haven't tested it, its using some of my old code for the ExportToPDF function in arcpy.

This should select each polygon where the FID >= 0, zoom to the selection and export. Handling the panning and scaling as it goes. It also creates an "out_png" file based on the layer name.

Hope that at least helps a little.

EDIT 2 - FOR COMPLETENESS

As Mr. Hornby pointed out. There could be a potential bug in ArcGIS Pro when setting the export extent.

I tried a number of interations of the same processes of setting the view extent for the export (I tried PDF, PNG and JPG, all with the same result).

Here is my final code:

import arcpy, os
from arcpy import env
from functools import reduce
env.overwriteOutput = True

aprx = arcpy.mp.ArcGISProject(r"C:\temp\test1.aprx")

m = aprx.listMaps("Map")[0]
lyr = m.listLayers("Depot_Area_Coverage")[0] ## Name of your layer
lyt = aprx.listLayouts("FirstPrintLayout")[0] ## Name of your layout
fc = r"c:\temp\Depot_Area_Coverage.shp"
#mf = lyt.listElements("MAPFRAME_ELEMENT", "Map Frame")[0] ## Name of the map frame

count = int(arcpy.management.GetCount(lyr)[0])

print (count)

for page in range (0, count):
    print (page)
    sql = """"FID" = %s""" % page

    #with arcpy.da.SearchCursor(fc,"SHAPE@",sql) as cur:
    #    fullextent = reduce(arcpy.Geometry.union, (shp for shp, in cur)).extent
    #    print (fullextent)   
        #print (fullextent.XMin)
        #print (fullextent.YMin) 

    #mf.camera.setExtent(fullextent)
    #print (mf.camera.scale)
    #mf.camera.scale = mf.camera.scale*0.5
    #mf.camera.setExtent(mf.getLayerExtent(lyr))
    #print (mf.camera.scale)
    #mapextent = mf.getLayerExtent(lyr)
    #print (mapextent)
    #lyr.definitionQuery = sql
    print (arcpy.GetCount_management(lyr))
    lyr = arcpy.SelectLayerByAttribute_management(lyr,'NEW_SELECTION',sql)[0]
    print (arcpy.GetCount_management(lyr))
           
    mf = lyt.listElements("mapframe_element")[0]
    # Choose the output Height and Width 
    mf.elementHeight = 10
    mf.elementWidth = 10
    
# Set the extent
    mf.camera.setExtent(mf.getLayerExtent(lyr, True, False))
    mf.panToExtent(mf.getLayerExtent(lyr, True, False))

# Clear the selection
    arcpy.SelectLayerByAttribute_management(lyr, "CLEAR_SELECTION")

    #mf.camera.setExtent(fullextent)
    #print (mf.camera.scale)

    #print (mf.camera.Extent)
    out = os.path.join(r"C:\temp",str(page) + "_.jpg")
    #png = mapframe.exportToPNG(out, resolution = 10)
    jpg = mf.exportToJPEG(out, resolution =10, jpeg_quality = 10)
    print (out)
    
    sql = ""
    lyr.definitionQuery = sql

This cycles through the polygons in the input layer. It then removes the polygons not needed for the export using a definition query, selects the polygon and changes the map view based on the selected polygon.

Printing out the various extents along the way shows that the extents do change, and the map scale changes with it. The export however is still at the full extent of the input layer, it just pans around as the polygons change.

It is also very slow at times.

I have kept a few lines of code in there to show some of the experiments I tried, I have commented these out.

Ultimately, I think creating a map series and running the export that way is the only real way to do this in ArcGIS Pro right now. I found a few articles that explained why the functionality from ArcMap was removed in ArcGIS Pro. Interestingly, there are suggestions that some sort of work-arounds being developed for release in ArcGIS Pro 3 (expected around the time of the conference in July 2022).

16
  • Yep, I know that. But what I'm asking is how to reduce the image down to just the feature I need. When I run the script now, it takes 3 minutes to generate one massive image. It's never going to work for a thousand features. So if I can zoom into just the feature extents, then I can reduce processing time as well. Unless there is a whole different approach I could use. I'm open to any advice.
    – Maxcot
    Apr 22 at 6:40
  • In ArcGISPro, I do the equivalent by Menus > Share > Export > PNG and I get an image file of 548kb in about 12 seconds. That is exactly want I want to replicate.
    – Maxcot
    Apr 22 at 6:51
  • Ok. Have you isolated the export into the loop with seperate file names for the out file? Apr 22 at 6:51
  • Yep, that's easy enough. It's the scaling/extents/panning that's baffling me
    – Maxcot
    Apr 22 at 6:51
  • I have an export script I wrote. I will see if I can edit my question accordingly. It exports to pdf, but the same thing Apr 22 at 6:53
1

Since the commentary on Keagan Allan's answer is geting quite lengthy, here is another approach that I have attempted.

One could also use the Desktop ArcGIS Pro application to export PDFs. Then after the PDF's have been exported as one document, or alternatively, as a set of 1 page documents, it would be easy to extract the images from the PDF using this code:

# STEP 1
# import libraries
import fitz
import io
from PIL import Image
  
# STEP 2
# file path you want to extract images from
file = "/content/pdf_file.pdf"
  
# open the file
pdf_file = fitz.open(file)
  
# STEP 3
# iterate over PDF pages
for page_index in range(len(pdf_file)):
    
    # get the page itself
    page = pdf_file[page_index]
    image_list = page.getImageList()
      
    # printing number of images found in this page
    if image_list:
        print(f"[+] Found a total of {len(image_list)} images in page {page_index}")
    else:
        print("[!] No images found on page", page_index)
    for image_index, img in enumerate(page.getImageList(), start=1):
        
        # get the XREF of the image
        xref = img[0]
          
        # extract the image bytes
        base_image = pdf_file.extractImage(xref)
        image_bytes = base_image["image"]
          
        # get the image extension
        image_ext = base_image["ext"]

Source: https://www.geeksforgeeks.org/how-to-extract-images-from-pdf-in-python.

The only problem of course, is that identifying and labelling each invdividual generated image can't be easily done.

I did think of having a second element on the print layout, with the unique identifier that I want to use as the export filename. But the PDF generation of ArcGIS Pro makes only one image for the whole page.

1
  • Interesting approach!
    – Hornbydd
    May 10 at 11:14
1

I have eventually, with some help from colleagues in the office, solve the problem as follows... This answer may have more detail than strictly necessary to experts in the field, but I put it here for the benefit of any novices like me who know very little.

  1. Use ArcGIS Pro (v2.9) to setup an APRX file with

    • a map
    • a layout
    • a map frame
    • a map series
  2. I used the following script in a conda environment command line.

    ##### myScript.py
    def exportThumbnails():
    APRXfile = 'C:/..../ThumbnailGenerator.aprx'
    p = arcpy.mp.ArcGISProject(APRXfile)
    l = p.listLayouts()[0]
    if not l.mapSeries is None:
        ms = l.mapSeries
        if ms.enabled:
            for pageNum in range(1, ms.pageCount + 1):
                ms.currentPageNumber = pageNum
                print("Exporting {0}".format(ms.pageRow.SourceReferenceID))
                pageName = ms.pageRow.SourceReferenceID
                l.exportToPNG(os.path.join(localThumbnailFolder, 
                             f"{ms.pageRow.SourceReferenceID}.png"))
    
    
    exportThumbnails() # run the function
    
  3. Couple of key things to look out for:

    • The paper size of the layout is part of the image
    • Map > Properties > Clip Layers > Exclude layers from clipping .... caused me some issues with hiding the actual image. My map was totally blank.
    • Setup the colours and styling, rounded corners, etc in the APRX project file.
  4. Results:

    • Custom paper size: page setup 100mm W x 120mm H
    • Open street Map basemap in colour
    • Polygon shapes in one additional layer
    • Produced images of size 378 x 454 and 40kb to 150kb
    • At a rate of about 1 PNG image every 2 seconds.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.