Table of Contents
Introduction
A common task is to calculate the percentage overlap between a polygon feature class overlaying another polygon feature class. One general workflow is to use the (Pairwise) Intersect tool with the Join Attributes parameter set to Only feature IDs, add a field to store the percentage overlap attributes, join the underlay polygons to the output based on Feature IDs, and then use Field Calculator and divide the Shape_Area for the Intersect output by the Shape_Area from the original underlay polygon. Simple stuff really but manually time consuming if you need to perform routinely. Let’s enhance using ArcPy and the Pairwise Intersect tool for ArcGIS Pro to achieve the output from the workflow described above. You can find more about the Pairwise Intersect tool in the Esri documentation here. ArcGIS Pro 3.1.0 was used when implementing the following.
Leverage ArcPy for geospatial data management workflows within ArcGIS Pro. Learn the fundamentals of utilising ArcGIS Pro geoprocessing tools with ArcPy for data management, conversions, and analysis. This course is packed with amazing content that will help you discover the power of automating tasks within the ArcGIS Pro environment. Take your ArcPy skills from beginner to snake charmer. A little code goes a long way, a little ArcPy provides you with an in-demand skill. Sign up now for our highly rated course.
Custom Tool Syntax
The syntax for the Polygon-Polygon Overlap Percentage tool is as follows…
polygonOverlapPercentage(
in_features,
out_feature_class
)
We’re going to need two required user input parameters; in_features (Data Type: Value Table), the Pairwise Intersect tool only allows to Feature Layers as input and we will restrict these to polygons in our tool’s GUI, and out_feature_class (Data Type: Feature Class) which will be the resulting polygon feature class from our workflow.
Import the ArcPy module
ArcPy is a Python site package that enables automation, analysis, and management of geographic data within the ArcGIS software environment.
import arcpy
Define the parameters
## only two input features allowed and must be polygon
in_features = arcpy.GetParameterAsText(0)
## output feature class
out_feature_class = arcpy.GetParameterAsText(1)
Required Python object for the tool
We’ll set some required objects to aid with the workflow. The code is commented, read through and get familiar with each statement.
## we only want the FIDs in the intersect attribute table returned
join_attributes = "ONLY_FID"
## output type is the same as input - Polygon, we will limit this is the
## tool GUI
output_type="INPUT"
## the field name with the FIDs of the underlay polygons
## when you run the intersct tool this is the name of the field created with
## the original FIDs of the 1st feature class entered into the in_features
## eg FID_Name_Of_Feature_Class
fid_fld_name_1 = "FID_{0}".format(arcpy.Describe(in_features.split(";")[0].split("\\")[-1]).baseName)
## the field name with the FIDs of the underlay polygons
## when you run the intersct tool this is the name of the field created with
## the original FIDs of the 2nd feature class entered into the in_features
## eg FID_Name_Of_Feature_Class
fid_fld_name_2 = "FID_{0}".format(arcpy.Describe(in_features.split(";")[1].split("\\")[-1]).baseName)
## the name of the 1st table input
in_tbl = in_features.split(";")[0]
## the name of the 2nd table input
join_tbl = in_features.split(";")[1]
## the OID field name for the join_tbl
join_fld = [fld.name for fld in arcpy.ListFields(join_tbl) if fld.type == "OID"][0]
## these lists will be used in the search and update cursors
search_flds = [join_fld, "SHAPE@AREA"]
update_flds = [fid_fld_name_1, fid_fld_name_2, "overlap_percent"]
Pairwise Intersect
Now we call the big dawg! The Pairwise Intersect tool and we place the output feature class into the memory workspace.
## use the pairwise intersect tool, this will return a polygon feature class
## that contains the interected portions only.
memory_fc = arcpy.analysis.PairwiseIntersect(
in_features = in_features,
out_feature_class = "memory\\pwise_fc",
join_attributes = join_attributes,
output_type = output_type
)
The Main Event: Calculating Polygon Overlap Percentage
Time to work some magic and add a field to store the percentage overlap values and then populate the attributes. Instead of using ArcPy to join tables together, we will use a Python dictionary like a look up table.
## add a field to contain our percentage of overlap
arcpy.management.AddField(
in_table = memory_fc,
field_name = "overlap_percent",
field_type = "DOUBLE"
)
## key (fid_fld_name_1, fid_fld_name_2), value Shape_Area
fid_dict = {(row[0],row[1]):row[2] for row in arcpy.da.SearchCursor(memory_fc, [fid_fld_name_1, fid_fld_name_2, "SHAPE@AREA"])}
## SQL expression for where clause when getting necessary features from the
## first of the in_features
sql_exp = "{0} IN ({1})".format(join_fld, ",".join(str(x[1]) for x in fid_dict.keys()))
## iterate through each feature from the sql_exp for the 2nd in_features
with arcpy.da.SearchCursor(join_tbl, search_flds, sql_exp) as cursor:
## for each row (polygon) in the feature class
for row in cursor:
## iterate over the memory_fc records where the fid_fld_name_2 is equal
## to the FID in the current search cursor.
## in the memory fc
with arcpy.da.UpdateCursor(memory_fc, update_flds, "{0} = {1}".format(fid_fld_name_2, row[0])) as u_cursor:
## for each record
for u_row in u_cursor:
## set the percentage_overlap to be the overlap area / the original
## area for the overlapped polygoon from the 2nd fc from in_features
u_row[2] = fid_dict[(u_row[0], u_row[1])]/row[1]
u_cursor.updateRow(u_row)
Export to Disk and Cleanup Memory Workspace
Our percentage overlaps have been calculated so now we can write the output to disk. The output will simply contain the FIDs of the overlapping polygons and the overlap_percent field that we added. Before writing to disk you could join the tables based on the FIDs for one or both of the original polygon feature classes and also have these in the output, but that’s for another day’s work. And as always, we clean up the memory workspace.
## write output to disk
arcpy.conversion.ExportFeatures(
in_features = memory_fc,
out_features = out_feature_class
)
## clean-up memory workspace by deleting the memory_fc
arcpy.management.Delete(memory_fc)
Mastering ArcGIS Pro ArcPy Search, Insert, & Update Cursors
Unlock the full potential of ArcPy Cursors with our intensive and in-depth course. Designed for GIS professionals, analysts, and enthusiasts, this course delves into the intricacies of Search, Insert, and Update Cursors in the ArcPy library, empowering you to manipulate and manage spatial data with precision and efficiency.
ArcPy Cursors enable you to streamline your GIS workflows, automate repetitive tasks, and witness the full potential of spatial data manipulation in ArcGIS Pro.
Create the tool in ArcGIS Pro
That didn’t take much code now did it? There’s probably more comments in there than actual lines of working code. Save your script and let’s head over to ArcGIS Pro to create our custom tool for use.
Right-click on your toolbox/toolset of choice and select New > Script. The New Script window will appear. In the General tab set Name to polygonOverlapPercentage, Label to Polygon-Polygon Overlap Percentage, and the Description to Polygon-Polygon Overlap Percentage with Pairwise Intersect and ArcPy.
In the Parameters tab set as per below. Set the Input Features with a Data Type of Feature Layer and select Multiple Values, use the Filter to restrict Feature Type to Polygon. For Output Feature Class, set the Data Type to Feature Class, the Direction to Output and the Filter to Feature Type Polygon.
In the Execution tab, click the folder icon in the top-right corner and ad your saved Python script.
In the Validation tab update the updateMessage function as per below to restrict the input to two feature classes only. Click OK and take the tool for a spin.
def updateMessages(self):
# Customize messages for the parameters.
# This gets called after standard validation.
in_count = len(self.params[0].valueAsText.split(";"))
if in_count > 2:
self.params[0].setErrorMessage("Can only accept two input polygon feature classes")
else:
self.params[0].clearMessage()
return
You can download the tool and other custom tools over on this page. This tool is in the Custom Tools on a Basic License with ArcPy section.
All the code in one place
You can find the entire code workflow below with links to important components in the documentation that were used.
import arcpy
################################################################################
## Esri Documentation
## https://pro.arcgis.com/en/pro-app/latest/tool-reference/analysis/pairwise-intersect.htm
## https://pro.arcgis.com/en/pro-app/latest/arcpy/functions/getparameterastext.htm
## https://pro.arcgis.com/en/pro-app/latest/arcpy/functions/listfields.htm
## https://pro.arcgis.com/en/pro-app/latest/tool-reference/data-management/add-field.htm
## https://pro.arcgis.com/en/pro-app/latest/arcpy/data-access/searchcursor-class.htm
## https://pro.arcgis.com/en/pro-app/latest/arcpy/data-access/updatecursor-class.htm
## https://pro.arcgis.com/en/pro-app/latest/tool-reference/conversion/export-features.htm
## https://pro.arcgis.com/en/pro-app/latest/tool-reference/data-management/delete.htm
## https://pro.arcgis.com/en/pro-app/latest/help/analysis/geoprocessing/basics/the-in-memory-workspace.htm
##
## ArcGIS Pro Version: 3.1.0
##
################################################################################
################################################################################
## USER INPUTS
## only two input features allowed
in_features = arcpy.GetParameterAsText(0)
## output feature class
out_feature_class = arcpy.GetParameterAsText(1)
################################################################################
## REQUIRED OBJECTS
## we only want the FIDs in the intersect attribute table returned
join_attributes = "ONLY_FID"
## output type is the same as input - Polygon, we will limit this is the
## tool GUI
output_type="INPUT"
## the field name with the FIDs of the underlay polygons
## when you run the intersct tool this is the name of the field created with
## the original FIDs of the 1st feature class entered into the in_features
## eg FID_Name_Of_Feature_Class
fid_fld_name_1 = "FID_{0}".format(arcpy.Describe(in_features.split(";")[0].split("\\")[-1]).baseName)
## the field name with the FIDs of the underlay polygons
## when you run the intersct tool this is the name of the field created with
## the original FIDs of the 2nd feature class entered into the in_features
## eg FID_Name_Of_Feature_Class
fid_fld_name_2 = "FID_{0}".format(arcpy.Describe(in_features.split(";")[1].split("\\")[-1]).baseName)
## the name of the 1st table input
in_tbl = in_features.split(";")[0]
## the name of the 2nd table input
join_tbl = in_features.split(";")[1]
## the OID field name for the join_tbl
join_fld = [fld.name for fld in arcpy.ListFields(join_tbl) if fld.type == "OID"][0]
## these lists will be used in the search and update cursors
search_flds = [join_fld, "SHAPE@AREA"]
update_flds = [fid_fld_name_1, fid_fld_name_2, "overlap_percent"]
################################################################################
## PAIRWISE INTERSECT TO GET OVERLAPPING POLYGONS
## use the pairwise intersect tool, this will return a polygon feature class
## that contains the interected portions only.
memory_fc = arcpy.analysis.PairwiseIntersect(in_features, "memory\\pwise_fc", join_attributes, output_type=output_type)
################################################################################
## CALCULATE THE PERCENTAGE OF OVERLAP
## add a field to contain our percentage of overlap
arcpy.management.AddField(memory_fc, "overlap_percent", "DOUBLE")
## key (fid_fld_name_1, fid_fld_name_2), value Shape_Area
fid_dict = {(row[0],row[1]):row[2] for row in arcpy.da.SearchCursor(memory_fc, [fid_fld_name_1, fid_fld_name_2, "SHAPE@AREA"])}
## SQL expression for where clause when getting necessary features from the
## first of the in_features
sql_exp = "{0} IN ({1})".format(join_fld, ",".join(str(x[1]) for x in fid_dict.keys()))
## iterate through each feature from the sql_exp for the 2nd in_features
with arcpy.da.SearchCursor(join_tbl, search_flds, sql_exp) as cursor:
## for each row (polygon) in the feature class
for row in cursor:
## iterate over the memory_fc records where the fid_fld_name_2 is equal
## to the FID in the current search cursor.
with arcpy.da.UpdateCursor(memory_fc, update_flds, "{0} = {1}".format(fid_fld_name_2, row[0])) as u_cursor:
## for each record
for u_row in u_cursor:
## set the percentage_overlap to be the overlap area / the original
## area for the overlapped polygoon from the 2nd fc from in_features
u_row[2] = fid_dict[(u_row[0], u_row[1])]/row[1]
u_cursor.updateRow(u_row)
################################################################################
## CALCULATE THE PERCENTAGE OF OVERLAP
## write output to disk
arcpy.conversion.ExportFeatures(memory_fc, out_feature_class)
################################################################################
## CLEAN-UP MEMORY WORKSPACE
## clean-up memory workspace by deleteing the memory_fc
arcpy.management.Delete(memory_fc)
################################################################################