Asked on Reddit:
I need to create a box around a point feature that is 66 ft wide and 50 ft tall. Is there a way to create a buffer that is customized between height and width? Or maybe another geoprocessing tool to help create this??
There might be other options out there, maybe even something better, but it is always fun to use ArcPy to overcome a challenge and provide a reusable solution. If there are alternatives out there please let me know in the comments. For now, let's walk through the creating the script that we will use in an ArcGIS Pro Toolbox.
We require several user input with the first two required - these are the input_point_features to create the rectangular buffers from and the polygon output_feature_class to create. The use_fields parameter is a boolean when set to True /checked on will enable the user to use field attributes for the width (width_field) and height (height_field) for each point to polygon, otherwise the user will set the buffer_width and buffer_height that will be universally used. The user also has on option to transfer none, all, or a subset of attributes from the point to the output polygon. If transfer _attributes is set to True, then attributes can be maintained with the fields parameter, if not, then only an ORIG_FID field will be created and populated with the OID from the original point.
import arcpy
## the point features to create the rectangle buffers from
input_point_features = arcpy.GetParameterAsText(0)
## the output polygon feature class
output_feature_class = arcpy.GetParameterAsText(1)
## boolean, if true use field attributes
use_fields = arcpy.GetParameter(2)
## the width of the buffer
buffer_width = arcpy.GetParameter(3)
## the height of the buffer
buffer_height = arcpy.GetParameter(4)
## the field name that contains the width values
width_field = arcpy.GetParameterAsText(5)
## the field name that contains the height values
heigth_field = arcpy.GetParameterAsText(6)
## Boolean whether to transfer attributes to the rectangle
transfer_attributes = arcpy.GetParameter(7)
## the fields to transfer
fields = arcpy.GetParameterAsText(8)
Not too many required. We need the Spatial Reference from the input points feature class and we'll make the output_feature_class the same. The OID field name from the input_point_feature class is required to transfer to the output polygon. If the user is using field attributes to determine the width and height per polygon then we create a dictionary where the key is the OID and the value is a tuple that stores the width and height associated with the OID. The in_field_list and out_field list allow us to keep track of what we are taking from the original dataset and adding to the output in terms of fields.
## spatial Reference for output same as input points
srs = arcpy.da.Describe(input_point_features)["spatialReference"]
## the oid name from the input points feature class
oid_fld = [fld.name for fld in arcpy.ListFields(dataset=input_point_features) if fld.type=="OID"][0]
## if the user is using field attributes for width and height we need this
## dictionary of oid : (width, height)
if width_field and heigth_field:
wh_dict = {row[0] : (row[1], row[2]) for row in arcpy.da.SearchCursor(input_point_features, [oid_fld, width_field, heigth_field])}
## the fields to transfer to the output from the input
in_field_list = [oid_fld] + fields.split(";") + ["SHAPE@XY"]
## the fields for the insert cursor
out_field_list = ["ORIG_FID"] + fields.split(";") + ["SHAPE@"]
If the user is transferring attributes to the output then we use the input_point_features layer/feature class as a template when creating the polygon feature class and then delete any non-selected fields. Otherwise no fields are created. If the latter is the case we need a little cleanup as there will be a n empty string in the list that will cause an error that you will see in the video.
## if the user is transferring fields
if fields:
temp_ply_fc = arcpy.management.CreateFeatureclass(
out_path = "memory",
out_name = "temp_ply_fc",
geometry_type = "POLYGON",
template = input_point_features,
spatial_reference = srs
)
arcpy.management.DeleteField(
in_table = temp_ply_fc,
drop_field = fields.split(";"),
method = "KEEP_FIELDS"
)
## otherwise
else:
temp_ply_fc = arcpy.management.CreateFeatureclass(
out_path = "memory",
out_name = "temp_ply_fc",
geometry_type = "POLYGON",
spatial_reference = srs
)
in_field_list.remove("")
out_field_list.remove("")
The ORIG_FID will match the OID from the point that the rectangle was generated from.
arcpy.management.AddField(
in_table=temp_ply_fc,
field_name="ORIG_FID",
field_type="LONG",
field_is_nullable="NULLABLE"
)
We iterate over each point. If the user has entered the numerical values for the width and height, we divide each by two, this will help us create the rectangles. If the user is using field attributes, we grab the width and height from the dictionary ({oid : (width, height)} and also divide each by two.
We then construct the rectangle (as an ArcPy Array of Point objects) based on xmin ymin, xmin ymax, xmax ymax, xmax ymin, and close the polygon.
We create a Polygon object from the Array and the construct the list for the InsertCursor and add as a feature to the memory polygon feature class.
## use a SearchCursor to iterate over the point features
with arcpy.da.SearchCursor(input_point_features, in_field_list) as s_cursor:
## use an InsertCursor to insert records to the memory feature class
with arcpy.da.InsertCursor(temp_ply_fc, out_field_list) as i_cursor:
## for each row (point) in the SearchCursor
for row in s_cursor:
## get the X and Y coord values
x,y = row[-1]
## half dimensions (easier for calculations)
if buffer_width and buffer_height:
half_width = buffer_width / 2.0
half_height = buffer_height / 2.0
else:
half_width = wh_dict[row[0]][0] / 2.0
half_height = wh_dict[row[0]][1] / 2.0
## contstruct the Array of Points that represent the rectangle
rectangle = arcpy.Array([
arcpy.Point(x - half_width, y - half_height), # xmin ymin
arcpy.Point(x - half_width, y + half_height), # xmin ymax
arcpy.Point(x + half_width, y + half_height), # xmax ymax
arcpy.Point(x + half_width, y - half_height), # xmax ymin
arcpy.Point(x - half_width, y - half_height) # closed polygon
])
## create the Polygon geometry object
polygon = arcpy.Polygon(
inputs = rectangle,
spatial_reference = srs
)
insert_feature = list(row[0:-1]) + [polygon]
## insert the row into the polygon feature class
i_cursor.insertRow(insert_feature)
We use the ExportFeatures Geoprocessing tool to save the polygon feature class from memory to disk and then cleanup the memory workspace.
arcpy.conversion.ExportFeatures(
in_features=temp_ply_fc,
out_features=output_feature_class
)
arcpy.management.Delete(
in_data = temp_ply_fc
)
See the video at 07:16 to see the properties, setup and validation of the custom tool in ArcGIS Pro.
import arcpy
################################################################################
## USER INPUTS #################################################################
## the point features to create the rectangle buffers from
input_point_features = arcpy.GetParameterAsText(0)
## the output polygon feature class
output_feature_class = arcpy.GetParameterAsText(1)
## boolean, if true use field attributes
use_fields = arcpy.GetParameter(2)
## the width of the buffer
buffer_width = arcpy.GetParameter(3)
## the height of the buffer
buffer_height = arcpy.GetParameter(4)
## the field name that contains the width values
width_field = arcpy.GetParameterAsText(5)
## the field name that contains the height values
heigth_field = arcpy.GetParameterAsText(6)
## Boolean whether to transfer attributes to the rectangle
transfer_attributes = arcpy.GetParameter(7)
## the fields to transfer
fields = arcpy.GetParameterAsText(8)
################################################################################
## REQUIRED OBJECTS ############################################################
## spatial Reference for output same as input points
srs = arcpy.da.Describe(input_point_features)["spatialReference"]
## the oid name from the input points feature class
oid_fld = [fld.name for fld in arcpy.ListFields(dataset=input_point_features) if fld.type=="OID"][0]
## if the user is using field attributes for width and height we need this
## dictionary of oid : (width, height)
if width_field and heigth_field:
wh_dict = {row[0] : (row[1], row[2]) for row in arcpy.da.SearchCursor(input_point_features, [oid_fld, width_field, heigth_field])}
## the fields to transfer to the output from the input
in_field_list = [oid_fld] + fields.split(";") + ["SHAPE@XY"]
## the fields for the insert cursor
out_field_list = ["ORIG_FID"] + fields.split(";") + ["SHAPE@"]
################################################################################
## CREATE MEMORY FEATURE CLASS #################################################
## if the user is transferring fields
if fields:
temp_ply_fc = arcpy.management.CreateFeatureclass(
out_path = "memory",
out_name = "temp_ply_fc",
geometry_type = "POLYGON",
template = input_point_features,
spatial_reference = srs
)
arcpy.management.DeleteField(
in_table = temp_ply_fc,
drop_field = fields.split(";"),
method = "KEEP_FIELDS"
)
## otherwise
else:
temp_ply_fc = arcpy.management.CreateFeatureclass(
out_path = "memory",
out_name = "temp_ply_fc",
geometry_type = "POLYGON",
spatial_reference = srs
)
in_field_list.remove("")
out_field_list.remove("")
################################################################################
## ADD THE ORIG_FID FIELD ######################################################
arcpy.management.AddField(
in_table=temp_ply_fc,
field_name="ORIG_FID",
field_type="LONG",
field_is_nullable="NULLABLE"
)
################################################################################
## CREATE POLYGONS #############################################################
## use a SearchCursor to iterate over the point features
with arcpy.da.SearchCursor(input_point_features, in_field_list) as s_cursor:
## use an InsertCursor to insert records to the memory feature class
with arcpy.da.InsertCursor(temp_ply_fc, out_field_list) as i_cursor:
## for each row (point) in the SearchCursor
for row in s_cursor:
## get the X and Y coord values
x,y = row[-1]
## half dimensions (easier for calculations)
if buffer_width and buffer_height:
half_width = buffer_width / 2.0
half_height = buffer_height / 2.0
else:
half_width = wh_dict[row[0]][0] / 2.0
half_height = wh_dict[row[0]][1] / 2.0
## contstruct the Array of Points that represent the rectangle
rectangle = arcpy.Array([
arcpy.Point(x - half_width, y - half_height), # xmin ymin
arcpy.Point(x - half_width, y + half_height), # xmin ymax
arcpy.Point(x + half_width, y + half_height), # xmax ymax
arcpy.Point(x + half_width, y - half_height), # xmax ymin
arcpy.Point(x - half_width, y - half_height) # closed polygon
])
## create the Polygon geometry object
polygon = arcpy.Polygon(
inputs = rectangle,
spatial_reference = srs
)
insert_feature = list(row[0:-1]) + [polygon]
## insert the row into the polygon feature class
i_cursor.insertRow(insert_feature)
################################################################################
## SAVE TO DISK ################################################################
arcpy.conversion.ExportFeatures(
in_features=temp_ply_fc,
out_features=output_feature_class
)
################################################################################
## CLEANUP MEMORY WORKSPACE ####################################################
arcpy.management.Delete(
in_data = temp_ply_fc
)
################################################################################
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.