Map Layout Elements In New Layer Automated Using Python

3454
9
12-12-2013 09:37 AM
VikJasrotia
New Contributor
I was wondering if there was a way to put a title and integer data from an attribute table in a new layer created in Python. I was able to create a new layer using copy features, zoom to the selected features, and export the zoomed features in a map as a PNG all using arcpy, but I also wanted a title at the top and data from the attribute table of that specific layer in the map layout before it was exported. Is it possible to get all this automated using Python?
Tags (2)
0 Kudos
9 Replies
JoshuaChisholm
Occasional Contributor III
Hello Vikrant,

As I understand it, you are trying to add elements to the layout and include specific data in that layout element.

If that is the case, you cannot do that with arcpy. It does not allow you to create layout edit, only edit them. There are several workarounds that are pretty simple to use.

One is to create the layout items manually and then simple edit it using arcpy. I would do something like this:
import arcpy
mxd = arcpy.mapping.MapDocument("C:\\Path\\To\\Map.mxd")
#Get layout elements:
elemList=arcpy.mapping.ListLayoutElements(mxd)
#Find title element:
for elem in elemList:
    if elem.name=="Title": #Make sure you manually set the Element Name to "Title" (see attached screenshot)
        titleElem=elem
#Edit text:
titleElem.text="This is the Title!"
mxd.save()
del titleElem, mxd

Note: you can move the title element off the layout area if you what it to 'disappear' from your PNG export. Use 'titleElem.elementPositionX' and 'titleElem.elementPositionY' to move it.

If you what to add text to the title element based on some data, I'd suggest a Search Cursor:
import arcpy
fc="C:\\Path\\To\\FeatureClass.shp"
cursor=arcpy.SearchCursor(fc)
for row in cursor:
    if row.Field1=="Taret": #i'm not sure how you wanted to pick a record, but this would find where Field1 is "Target"
        targetInteger=row.Field2 #change 'Field2' to your integer data field
        break #ends for loop

titleText="This is Title number: "+str(targetInteger)
del row,cursor
#Then add the code above to edit the title element in the mxd.

Note: there are shortcuts if you're using Data Driven Pages.

Let us know how it goes! Sorry if I misunderstood your question.
Good luck!
~Josh
0 Kudos
VikJasrotia
New Contributor
I think this works for a layer that is already current but what I was trying to do was automate this from a new layer I created in the same Python process. I had a user input that selected census tract attributes and calculated population density. I then wanted tthe specific tract number and density data from the attribute table from the new layer in the map layout and then exported in a PNG. After the zoom, I wanted to create the layout and export it. Someone mentioned that I could do this using a Search Cursor, but I wasn't sure how. Here is my code:

#user selects tract, Select by Attribute tool runs
arcpy.MakeFeatureLayer_management("plano_tract", "plano_tract_lyr")
arcpy.SelectLayerByAttribute_management("plano_tract_lyr", "NEW_SELECTION", "\"Tract_Number\" = "+ tract)
arcpy.CopyFeatures_management("plano_tract_lyr", "plano_tract_select")

#add field
arcpy.AddField_management("plano_tract_select", "density", "DOUBLE")

#calculate field
arcpy.CalculateField_management("plano_tract_select", "density", "[DP0010001] / [sq_mile_1]", "VB", "")
print "executed successfully"

#zoom to selected features
df = arcpy.mapping.ListDataFrames(mxd, "Layers")[0]
lyr = arcpy.mapping.ListLayers(mxd, "plano_tract_select", df) [0]
df.extent = lyr.getExtent(True)
arcpy.RefreshActiveView()
print "zoomed to layer"
0 Kudos
JoshuaChisholm
Occasional Contributor III
Hello Vikrant,

I think you can use search cursor on 'lyr'. Like this (insert before 'arcpy.RefreshActiveView()' in your code):
cursor=arcpy.da.SearchCursor(lyr)
for row in cursor: #there should only be one row in your layer
    density=row.density
del row,cursor

mxd = arcpy.mapping.MapDocument("C:\\Path\\To\\Map.mxd")
#Get layout elements:
elemList=arcpy.mapping.ListLayoutElements(mxd)
#Find title element:
for elem in elemList:
    if elem.name=="Title": #Make sure you manually set the Element Name to "Title" (see attached screenshot)
        titleElem=elem
#Edit text:
titleElem.text="Census tract: "+str(tract)+" - Density: "+str(density)
mxd.save()
del titleElem, mxd
0 Kudos
VikJasrotia
New Contributor
I am getting a syntax error on the titleElem.text line and not sure where the mistake is.


#Edit text:
titleElem.text="Census tract: "+str(tract)+" - "Density: "+str(density)
mxd.save()
del titleElem, mxd


Thank you so much for your help, btw Josh. I am learning at a much quicker pace now.

And I was wondering, how do I manually set the title element for a new layer? Can't I only do that with an existing layer?
0 Kudos
JoshuaChisholm
Occasional Contributor III
Sorry Vikrant,

Sorry, that was my mistake. I had an extra " in there. That line should read:
titleElem.text="Census tract: "+str(tract)+" - Density: "+str(density)

*I also fixed my original post

One quick clarification: you've mentioned 'new layer' a few times when I think you are referring to a layout element. Although illustrators would understand that, I think most GIS people think of a layer as something that could appear in the table of contents and has data associated with it (like a shapefile or another feature class). A layout item (like a title, legend, north arrow, etc.) doesn't represent data or appear in the table of contents.

To manually insert a title element:

  1. Switch to Layout View (either click the small button below the map at the bottom of ArcMap OR click "View">"Layout View")

  2. Click "Insert">"Title" OR "Text" (The 'Title' is dynamically generated using the metadata of the MXD document. It doesn't really matter because the 'titleElem.text' line would over right the dynamic propertied of the 'Title' option anyway.)


I'm happy to help; good luck!
0 Kudos
VikJasrotia
New Contributor
The traceback is saying

Traceback (most recent call last):
  File "C:\Python27\ArcGIS10.1\Lib\site-packages\Pythonwin\pywin\framework\scriptutils.py", line 326, in RunScript
    exec codeObject in __main__.__dict__
  File "C:\Users\dallascowboy83\Desktop\final project 2\final project\finalscript2.py", line 37, in <module>
    cursor=arcpy.da.SearchCursor(lyr)
TypeError: Required argument 'field_names' (pos 2) not found

I am guessing the density field is either not defined within the Search Cursor or its not locating the density field.

And the reason I keep saying new layer is because I actually did create a new layer within the same script. I am wanting the user to select census tracts by attributes (tract number), which copies the features of the tract and then creates a new feature class of just that specific tract. I then want the script to work by adding a title and density data of that specified tract to the map layout of the new layer (plano_tract_select) and then export to a PNG. I just don't know if this can be achieved with the Search Cursor or in arcpy at all.
0 Kudos
VikJasrotia
New Contributor
If I can't get the density data to show up as elements on a PNG, would this Search Cursor at least get me the tract number and density information in the Python window after the program runs?
0 Kudos
JoshuaChisholm
Occasional Contributor III
Ok Vikrant,

I think I understand what you're trying to do now. Everything should be doable in arcpy.

I would take a slightly different approach though. What if you simply modified the definition query of the census tracts layer instead of adding a new layer. This way you wouldn't have to worry about creating a new feature class and applying symbology. If you wanted all census tracts as a background layer, you could copy the layer and give it a new name.

As for density, it seems like you don't even need to create a new field. Would you be ok if it was not in the attribute table and was just displayed on the exported PNG?

If that approach is acceptable, you can use the code below, otherwise, you can still use arcpy, but with some modifications.
import arcpy

#User defines tract number:
tract=raw_input("Enter census tract number:")

mxdPath="C:\\Path\\To\\Map.mxd"
mxd=arcpy.mapping.MapDocument(mxdPath) #use "CURRENT" if running in ArcMap Python Window or as a tool

#Get Lyr Files
allLyrs=arcpy.mapping.ListLayers(mxd)

for lyr in allLyrs:
    if lyr.name=="Target Tract": #must be one and only one layer called "Target Tract" in your mxd's TOC
        targetLyr=lyr

#if tract number is stored as a number use this line: (and remove the other one)
targetLyr.definitionQuery='"Tract_Num" = '+str(tract) #change Tract_Num to name of tract field

#if tract number is stored as a string/text use this line: (and remove the other one)
tractsLyr.definitionQuery='"Tract_Num" = \''+str(tract)+'\'' #change Tract_Num to name of tract field

cursor=arcpy.SearchCursor(targetLyr)
for row in cursor:
    density=row.DP0010001/row.sq_mile_1

#Get layout elements:
elemList=arcpy.mapping.ListLayoutElements(mxd)
#Find title element:
for elem in elemList:
    if elem.name=="Title": #Make sure you manually set the Element Name to "Title" (see attached screenshot)
        titleElem=elem
#Edit text:
titleElem.text="Census tract: "+str(tract)+" - Density: "+str(density)


#zoom to selected features
df = arcpy.mapping.ListDataFrames(mxd, "Layers")[0]
df.extent = targetLyr.getExtent(True)

mxd.save() #remove this line if you're using "CURRENT" MapDocument
arcpy.RefreshActiveView()

del mxd, df, row, cursor, lyr, targetLyr, allLyrs

print "executed successfully"

Let me know if this works!
0 Kudos
JoshuaChisholm
Occasional Contributor III
Hello Vikrant,

Where you able to get the right PNGs exported to suit your needs?
0 Kudos