Wednesday 25 March 2015

"Field Mapping" in ArcGIS 10 - ArcPy


I've wrote a Python script that does a spatial join and some simple calculations. My problem is with setting the merge rule for one specific field, and leaving the rest of the fields as is. For example, I have a population field that when joined by spatial location uses the merge rule "First" which grabs the first occurring of a Population count. I want to be able to set the merge rule to "Sum" to add up the population values between all of the polygons found in the spatial extent of another polygon.


I have done some intense tinkering with field maps and field mapping objects, but can't seem to get it working properly. Specifically I have tried the method: popFieldMap.mergeRule = 'Sum' to set the mergeRule, but it always reverts to "First".


Any ideas how I can programmatically change the merge rule for one field in a Spatial Join?


Thanks!


Here is my code (keep in mind that it is pretty specific to my data and contains lines to test certain stages of the script):



import arcpy,sys,os

#Get the Files involved, set some variables.
SectorTable = sys.argv[1]
SectorShape = sys.argv[2]
MaxDev = sys.argv[3]
PopulationFC = sys.argv[4]
OutputFC = sys.argv[5]
DeviationField ="Angle_Deviation"
ID = "SectorID"

newID = "BP_ID"
mxd = arcpy.mapping.MapDocument('CURRENT')
df = arcpy.mapping.ListDataFrames(mxd)[0]

#Check to see if ID field types and name match
try:
SectorShapeFields = arcpy.ListFields (SectorShape,ID)
SectorTableFields = arcpy.ListFields (SectorTable,ID)
arcpy.AddMessage("Finished Listing Fields")
nameCheck = SectorShapeFields[0].name == SectorTableFields[0].name

typeCheck = SectorShapeFields[0].type == SectorTableFields[0].type
except:
arcpy.AddMessage("Failed to List Fields")

#If they do not match, add new fields to correct.
if not nameCheck:
arcpy.AddMessage("Field Names do not match! Adding new field to circumvent...")
if not typeCheck:
arcpy.AddMessage("Field Types do not match! Adding new field to circumvent...")
if SectorShapeFields[0].type != SectorTableFields[0].type:

try:
arcpy.AddField_management(SectorShape, newID, SectorTableFields[0].type,10)
arcpy.CalculateField_management(SectorShape, newID,"!"+ID+"!", "PYTHON")
arcpy.RefreshTOC()
except:
arcpy.AddMessage("Error in Creating Field. Does it already exist?")


#Join the two together
arcpy.AddMessage("Performing Join")

arcpy.AddJoin_management( SectorShape, newID, SectorTable, ID)
arcpy.SelectLayerByAttribute_management (SectorShape,"NEW_SELECTION","Angle_Deviation>"+str(MaxDev))
df.zoomToSelectedFeatures()

#Field Mapping ...
myMap = arcpy.FieldMappings()
myMap.addTable(PopulationFC)
myMap.addTable(SectorShape)

#Verify the field merge rule for the pop10 field.

fIndex = myMap.findFieldMapIndex("POP10")
arcpy.AddMessage(str(fIndex))
popFieldMap = myMap.getFieldMap(fIndex)
arcpy.AddMessage(str(popFieldMap.mergeRule))
popFieldMap.mergeRule = 'Sum'
arcpy.AddMessage(str(popFieldMap.mergeRule))
popFieldMap2 = popFieldMap

##Test
fIndex = myMap.findFieldMapIndex("POP10")

arcpy.AddMessage(str(fIndex))
popFieldMap = myMap.getFieldMap(fIndex)
arcpy.AddMessage(str(popFieldMap.mergeRule))

#Perform Spatial Join
arcpy.AddMessage("Performing Spatial Join")
arcpy.SpatialJoin_analysis(SectorShape, PopulationFC, OutputFC,"JOIN_ONE_TO_ONE","",myMap,"INTERSECT")

#Add Result and Symbolize
arcpy.mapping.AddLayer(df,arcpy.mapping.Layer(OutputFC))

translayer = arcpy.mapping.ListLayers(mxd,"",df)[0]
translayer.transparency = 50

arcpy.RefreshActiveView()

EDIT - Below is the code with the solution implemented!


import arcpy,sys,os

#Get the Files involved, set some variables.
SectorTable = sys.argv[1]

SectorShape = sys.argv[2]
MaxDev = sys.argv[3]
PopulationFC = sys.argv[4]
OutputFC = sys.argv[5]
DeviationField ="Angle_Deviation"
ID = "SectorID"
newID = "BP_ID"
mxd = arcpy.mapping.MapDocument('CURRENT')
df = arcpy.mapping.ListDataFrames(mxd)[0]


#Check to see if ID field types and name match
try:
SectorShapeFields = arcpy.ListFields (SectorShape,ID)
SectorTableFields = arcpy.ListFields (SectorTable,ID)
arcpy.AddMessage("Finished Listing Fields")
nameCheck = SectorShapeFields[0].name == SectorTableFields[0].name
typeCheck = SectorShapeFields[0].type == SectorTableFields[0].type
except:
arcpy.AddMessage("Failed to List Fields")


#If they do not match, add new fields to correct.
if not nameCheck:
arcpy.AddMessage("Field Names do not match! Adding new field to circumvent...")
if not typeCheck:
arcpy.AddMessage("Field Types do not match! Adding new field to circumvent...")
if SectorShapeFields[0].type != SectorTableFields[0].type:
try:
arcpy.AddField_management(SectorShape, newID, SectorTableFields[0].type,10)
arcpy.CalculateField_management(SectorShape, newID,"!"+ID+"!", "PYTHON")
arcpy.RefreshTOC()

except:
arcpy.AddMessage("Error in Creating Field. Does it already exist?")


#Join the two together
arcpy.AddMessage("Performing Join")
arcpy.AddJoin_management( SectorShape, newID, SectorTable, ID)
arcpy.SelectLayerByAttribute_management (SectorShape,"NEW_SELECTION","Angle_Deviation>"+str(MaxDev))
df.zoomToSelectedFeatures()


#Field Mapping ...
myMap = arcpy.FieldMappings()
myMap.addTable(PopulationFC)
myMap.addTable(SectorShape)

#Verify the field merge rule for the pop10 field.
fIndex = myMap.findFieldMapIndex("POP10")
arcpy.AddMessage(str(fIndex))
popFieldMap = myMap.getFieldMap(fIndex)
arcpy.AddMessage(str(popFieldMap.mergeRule))

popFieldMap.mergeRule = 'Sum'
arcpy.AddMessage(str(popFieldMap.mergeRule))

myMap.replaceFieldMap(fIndex,popFieldMap)

##Test
fIndex = myMap.findFieldMapIndex("POP10")
arcpy.AddMessage(str(fIndex))
popFieldMap = myMap.getFieldMap(fIndex)
arcpy.AddMessage(str(popFieldMap.mergeRule))


#Perform Spatial Join
arcpy.AddMessage("Performing Spatial Join")
arcpy.SpatialJoin_analysis(SectorShape, PopulationFC, OutputFC,"JOIN_ONE_TO_ONE","",myMap,"INTERSECT")

#Add Result and Symbolize
arcpy.mapping.AddLayer(df,arcpy.mapping.Layer(OutputFC))
translayer = arcpy.mapping.ListLayers(mxd,"",df)[0]
translayer.transparency = 50


arcpy.RefreshActiveView()

Answer



I think you need to actually use FieldMappings.replaceFieldMap to get it to persist. Example from help topic Mapping input fields to output fields:


# First get the TRACT2000 fieldmap. Then add the TRACTCODE field
# from Blocks2 as an input field. Then replace the fieldmap within
# the fieldmappings object.
#
fieldmap = fieldmappings.getFieldMap(fieldmappings.findFieldMapIndex("TRACT2000"))
fieldmap.addInputField(fc2, "TRACTCODE")
fieldmappings.replaceFieldMap(fieldmappings.findFieldMapIndex("TRACT2000"), fieldmap)


Another thought is that when testing field maps, I like to use a script tool and custom script tool behavior so I can visually inspect the field map instead of trying to decode the crazy long strings they produce.


Example ToolValidator class I used to conclude that yes, replaceFieldMap is required for it to be persisted in the parameter value:


class ToolValidator:
"""Class for validating a tool's parameter values and controlling
the behavior of the tool's dialog."""

def __init__(self):
"""Setup arcpy and the list of tool parameters."""
import arcpy

self.params = arcpy.GetParameterInfo()

def initializeParameters(self):
"""Refine the properties of a tool's parameters. This method is
called when the tool is opened."""
return

def updateParameters(self):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parmater

has been changed."""
if (not self.params[0].hasBeenValidated
or not self.params[1].hasBeenValidated):
targetFeatures = self.params[0].value
joinFeatures = self.params[1].value
fieldMappings = arcpy.FieldMappings()
if targetFeatures:
fieldMappings.addTable(targetFeatures)
if joinFeatures:
fieldMappings.addTable(joinFeatures)

idx = fieldMappings.findFieldMapIndex("PRIORITY")
fieldMap = fieldMappings.getFieldMap(idx)
fieldMap.mergeRule = 'Sum'
fieldMappings.replaceFieldMap(idx, fieldMap) # if this line is commented out, the merge rule reverts to 'First'
self.params[3].value = fieldMappings.exportToString()
return

def updateMessages(self):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""

return

No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...