Automatically export species distribution maps

1505
7
03-14-2021 02:22 PM
Labels (1)
MladenZadravec
New Contributor II

Hello everyone,

I'm fairly new to this and when trying to google my way towards a solution, I've gotten stuck - so I'd appreciate any and all constructive feedback and guidance you could give me.

ArcGIS Pro version: 2.7.1

The situation: I have a shapefile which contains distribution data (points) for many species. From it I need to get distribution maps (.jpg), in a way that there's one species shown on each map. The points need to be represented with the same symbol, but different colour (based on the value in a specific field in the attribute table, the values being "published" and "unpublished"). The extent, scale bar, north arrow and legend are all the same, with the name of the species for which the map is being written either in the legend, or in a dynamic text field.

I know I can do that manually, by setting up an SQL definition query, varying the species name and exporting each map manually, but I have hundreds of species in the shapefile and that would take a lot of time... so I'm looking for a smarter, automated, solution.

What I tried: At first I tried using Map Series, but that returns one map per data point (thousands of maps), all with different extents. It didn't matter if I set the species' name in the Group By field, I got the same result. I'm hoping that there's a way to get what I need through Python. To that extent, I tried cobbling up a script and got this far:

Spoiler

# import arcpy module:
import arcpy

# overwrite output:
arcpy.env.workspace = "CURRENT"
arcpy.env.overwriteOutput = True

# Output path:
OutPath = r'G:\01_Fast_DATA\ArcGisPro\Kristian_diplomski\Kristian_diplomski.gdb'

# Use this if you need to check the exact spelling of the field name:
for field in fields:
print("{0} is a type of {1} with a length of {2}"
.format(field.name, field.type, field.length))

# Get list of unique values that will be used for iteration:
fc = r"G:\01_Fast_DATA\ArcGisPro\Kristian_diplomski\Kristian_diplomski.gdb\extract_KM_20210211_bezRegija"
field = 'CrossVal_samo_vrsta'
values = [row[0] for row in arcpy.da.SearchCursor(fc, field)]
uniqueValues = set(values) # uniqueValues is now the list of unique values -> that's the variable that needs to be called
# print(uniqueValues) # this is not necessary, but may be useful in some other cases

# iterate definition query for each unique value in uniqueValue and export the map using a specific map layout:
# error trapping:
try:

for uniqueValue:

# Make a layer from the feature class
layer = arcpy.management.MakeFeatureLayer('extract_KM_20210211_bezRegija',"CrossVal_samo_vrsta" = uniqueValues)

except:
print(arcpy.GetMessages())

Where I think I'm stuck: I'm not sure how to proceed with the for loop in the script. I know(?) I need to write something like "for every unique species name in the uniqueValue list make a selection using the first name from the list, with the layout set the way it has to be and the species' name spelled out somewhere on the map... then export that as a .jpg at a specified location... then make a new selection, using the next name on the list, repeat the other steps... and then keep looping until it's been done for the last species on the list", but how to get that into Python - that's where I'm at a loss.

 

Any help would be much appreciated.

Thanks,

M

0 Kudos
7 Replies
DavidPike
MVP Frequent Contributor

I've never used arcpy in Pro for exporting map series so I've just cobbled together some stuff I googled, it's probably along the right lines but may also be nonsense and is untested.

I wonder if you can kinda 'cheat' the map series by dissolving your current feature class by the fieldname, and selecting 'create multipart' - then you can just run the map series against those multipart features.

here's what I'm imaging for the script:

import arcpy
import os

out_folder = r'c:\folderforoutJPEGs'

aprx = arcpy.mp.ArcGISProject("CURRENT")

layout = aprx.listLayouts('yourlayoutname')[0]
mp = aprx.listMaps('yourmapname')[0]
lyr = mp.listLayers('yourlayername')[0]

for unique_value in uniqueValues:
    #have used " " to contain fieldname as is sql format for shapefile (i think thats right??)
    lyr.definitionQuery = '"CrossVal_samo_vrsta" = {0}'.format(unique_value)
    layout.exportToJPEG(os.path.join(out_folder, (unique_value + '.jpg'))

 (obviously add in your previous parts)  NB I don't see where you have use listFields() to do the field.name etc print statments.

MladenZadravec
New Contributor II

Dear David,

thank you for your suggestions. I added your lines of code where they seemed to be appropriate and after commenting out the try: and except: lines (I kept getting a syntax error on except:, "unexpected EOF while parsing") and then resolving the unexpected indent errors I kept getting on the for statement, I got the following error:

File "<string>", line 36
SyntaxError: keyword can't be an expression

Haven't resolved that yet, since I had to go to work, but I will be playing around with it this afternoon again. Current state of the code is as follows:

# import arcpy & os modules:
import arcpy
import os

# overwrite output:
arcpy.env.workspace = "CURRENT"
arcpy.env.overwriteOutput = True

# Output path:
OutPath = r'G:\01_Fast_DATA\ArcGisPro\Kristian_diplomski\Kristian_diplomski.gdb'

# Use this if you need to check the exact spelling of the field name:
for field in fields:
    print("{0} is a type of {1} with a length of {2}"
    .format(field.name, field.type, field.length))

# Get list of unique values that will be used for iteration:
fc = r"G:\01_Fast_DATA\ArcGisPro\Kristian_diplomski\Kristian_diplomski.gdb\extract_KM_20210211_bezRegija"
field = 'CrossVal_samo_vrsta'
values = [row[0] for row in arcpy.da.SearchCursor(fc, field)]
uniqueValues = set(values) # uniqueValues is now the list of unique values -> that's the variable that needs to be called
# print(uniqueValues) # this is not necessary, but may be useful in some other cases

# defining a few more variables:
layout = aprx.listLayouts('yourlayoutname')[0]
mp = aprx.listMaps('yourmapname')[0]
lyr = mp.listLayers('yourlayername')[0]

# iterate definition query for each unique value in uniqueValue and export the map using a specific map layout:
    # error trapping:
#try:
  
for unique_value in uniqueValue:
       
   # Make a layer from the feature class
   layer = arcpy.management.MakeFeatureLayer('extract_KM_20210211_bezRegija',"CrossVal_samo_vrsta" = uniqueValues)
            
   lyr.definitionQuery = '"CrossVal_samo_vrsta" = {0}'.format(unique_value)
   layout.exportToJPEG(os.path.join(OutPath, (unique_value + '.jpg')))

(had to add an additional closing parenthesis on the end there)

 

0 Kudos
DavidPike
MVP Frequent Contributor

roger to the parentheses.  The make feature layer syntax you have is a bit funky, also I'm applying a definition query on the basis that this is already a layer in the .aprx (current) to which the definition query is iteratively being applied and the layout exported.  Basically get rid of the entire makefeaturelayer() method.

 

TomBole
Esri Regular Contributor

Hi Mladen, 

Have you looked at the ArcPy samples? In particular, the sample titled "ThematicMapSeries_Pro25"? The answer may be within the sample.

Tom

MladenZadravec
New Contributor II

Dear David and Tom,

thank you both for your suggestions and guidance. I was able to cobble together a script that seems to be doing exactly what I need. Here's the code snippet:

# this script is useful to export e.g. species distribution maps, when each species is its own shapefile - when the extent, scale and other stuff is the same, but the species needs to be switched

# import relevant modules:
import arcpy, os

# set output folder:
outputFolder = '' # enter the file path between the single quotes

#Reference the current project:
p = arcpy.mp.ArcGISProject('current')

#Reference the appropriate map and layout:
m = p.listMaps('Map')[0]
lyt = p.listLayouts("Layout")[0]

#Create a list the represents the order of maps in the final output
#Each item in the list is the name of a feature layer to be exported
pageName = ['Cetonia_aurata__Linnaeus__1761_', 'Protaetia_cuprea__Fabricius__1775_', 'Oxythyrea_funesta__Poda__1761_'] # input the list of unique species names within the square brackets; each name must be enclosed in single quotes

#Iterate through each page in the list (although technically there are no pages any more):
for pageName in pageName:
	#Iterate through each layer in the map
  for l in m.listLayers():

    # Toggle visibility of group layers
    if l.isFeatureLayer:

      #If the group layer name is the correct pageName, then make visible
      #Otherwise toggle all other gropu layers off
      if l.name == pageName or l.name == 'RH_granica_2018':  # using OR you can add additional layers which should always be visible   
        l.visible = True
      else:
        l.visible = False
  #Export each map:
  print('Exporting: ' + pageName)
  lyt.exportToJPEG(os.path.join(outputFolder, pageName +  '.jpg'), resolution = 300) # resolution currently set to 300 DPI

 

I still plan to add  another feature layer which should be always visible and I want to experiment with a dynamic text field so that each map has the name of the species written on it. But I'll leave that for tomorrow.

It's probably not the most elegant solution, but it works for now. If anyone has suggestions for improvement, either now or in the future, I'd be more than happy to hear them.

RioButton
New Contributor

Thank you so much! I have a similar challenge ahead of me and this is useful. I need to group my species in folders by family (which is also a variable in my point attribute table). Thanks, Rio

0 Kudos
CiprianSamoila
New Contributor II

Hi @MladenZadravec ! Do you have an update on your script with the latter wishes implemented in it?

0 Kudos