This project began with a deliberately artificial but technically challenging task: replace Austin with the fictional name Austannum throughout a course-provided geodatabase, then apply a set of hypothetical speed-limit rules based on proximity to schools. The exercise wasn’t a policy proposal for Austin. It was a way to test whether one reproducible Python script could inspect an existing geodatabase, modify its schema and attributes, perform spatial analysis, and generate a structured report.
Seemingly simple edits become more complicated when they must be applied consistently across an entire geodatabase. A place name may appear in dataset names, tables, and feature-class attributes. Its replacement may exceed an existing field length. Spatially selected records must be separated from the remaining records so that no feature is updated twice.
Python and ArcPy brought those tasks together in a single workflow that:
- Created a working copy of the source geodatabase
- Inventoried its feature datasets, feature classes, tables, and fields
- Updated dataset names and text attributes
- Expanded text fields where the replacement required more space
- Applied speed-limit rules using spatial and conditional logic
- Exported a deduplicated CSV report

Updating an entire geodatabase
The script first copied the source geodatabase, preserving the original data before making any changes. It then used the ArcPy Walk data-access function to inventory the copied geodatabase from the top down.
inventory = []
for dirpath, dirnames, filenames in arcpy.da.Walk(
output_gdb,
topdown=True
):
for dirname in dirnames:
path = os.path.join(dirpath, dirname)
inventory.append(
(path, dirname, "FeatureDataset")
)
for filename in filenames:
path = os.path.join(dirpath, filename)
desc = arcpy.Describe(path)
inventory.append(
(path, filename, desc.dataType)
)
This approach allowed the script to discover the geodatabase structure rather than depend on a hard-coded list of datasets. It then identified the text fields in each table and feature class and searched them for common capitalization variants of Austin.
The longer replacement name introduced an additional complication: some text fields weren’t wide enough to store Austannum. Before updating those attributes, the script used a SearchCursor to calculate the maximum required length and expanded the field only when necessary.
length_needed = length_og
with arcpy.da.SearchCursor(
fc_path,
[field_name]
) as cursor:
for row in cursor:
value = row[0]
if value is None:
continue
if target in value.lower():
text_new = (
value.replace("Austin", "Austannum")
.replace("AUSTIN", "AUSTANNUM")
.replace("austin", "austannum")
)
if len(text_new) > length_needed:
length_needed = len(text_new)
if length_needed > length_og:
arcpy.management.AlterField(
in_table=fc_path,
field=field_name,
field_length=length_needed
)
After checking the schema, an UpdateCursor applied the replacements to the matching attribute values. Dataset names containing Austin were also renamed before the script rebuilt the inventory to verify the resulting structure.
Applying spatial rules to street data
The second part of the workflow applied three hypothetical speed-limit rules:
- Streets within 250 meters of a school received a 10 mph reduction.
- Remaining streets with speed limits of 40 mph or below received a 5 mph reduction.
- Remaining streets with speed limits above 40 mph received a 10 mph reduction.
The script created temporary feature layers and used SelectLayerByLocation to identify street segments within the specified distance of a school.
arcpy.management.SelectLayerByLocation(
in_layer=streets_lyr,
overlap_type="WITHIN_A_DISTANCE",
select_features=schools_lyr,
search_distance="250 Meters"
)
An UpdateCursor reduced the speed limits on the selected segments. Records with null street names, null speed limits, or values below 10 mph were skipped rather than forcing an update onto incomplete or questionable data.
The same cursor also stored the original and updated speeds in a dictionary. Using the street name as the dictionary key prevented repeated road segments from producing duplicate rows in the final report.
with arcpy.da.UpdateCursor(
streets_lyr,
["FULL_STREE", "SPEED_LIMI"]
) as cursor:
for row in cursor:
street_name = row[0]
speed_og = row[1]
if (
street_name is None
or speed_og is None
or speed_og < 10
):
continue
speed_updated = speed_og - 10
if street_name not in d_streets_near_schools:
d_streets_near_schools[street_name] = (
speed_og,
speed_updated
)
row[1] = speed_updated
cursor.updateRow(row)
To process the rest of the street network, the script repeated the spatial query with its relationship inverted. This separated streets outside the 250-meter distance from those already updated and prevented school-adjacent segments from receiving a second reduction.
arcpy.management.SelectLayerByLocation(
in_layer=streets_lyr,
overlap_type="WITHIN_A_DISTANCE",
select_features=schools_lyr,
search_distance="250 Meters",
invert_spatial_relationship="INVERT"
)

Creating a deduplicated summary
The final step exported the dictionary of school-adjacent streets to a CSV file. Street names were sorted alphabetically, and each row recorded the original and updated speed limit.
with open(
output_csv,
mode="w+",
newline=""
) as csv_file:
columns = [
"STREET_NAME",
"OLD_SPEED_LIMIT",
"NEW_SPEED_LIMIT"
]
writer = csv.DictWriter(
csv_file,
fieldnames=columns
)
writer.writeheader()
for street_name, speeds in sorted(
d_streets_near_schools.items()
):
writer.writerow({
"STREET_NAME": street_name,
"OLD_SPEED_LIMIT": speeds[0],
"NEW_SPEED_LIMIT": speeds[1]
})
Because the same street name could occur on multiple segments, the first qualifying occurrence supplied the values written to the report. This satisfied the requirement for unique street names while making the handling of duplicates explicit.
| Street name | Original speed limit | Updated speed limit |
|---|---|---|
| Anderson Ln | 40 | 30 |
| Burnet Rd | 45 | 35 |
| Lamar Blvd | 35 | 25 |
The resulting CSV demonstrates a reporting pattern that could be adapted for quality assurance, transportation analysis, or other workflows that require a concise record of automated changes.
Why the automation matters
The script in this workflow had to discover the structure of an existing geodatabase, protect against field-length constraints, account for null and implausible values, keep spatial rules mutually exclusive, and reduce repeated street segments to a clean summary.
The most useful part of the project was seeing how spatial analysis could operate inside a broader data-management pipeline. Instead of producing only a map or a one-time edit, the script created a repeatable chain from the original geodatabase to updated spatial data and a documented CSV output.
Although the Austin scenario was hypothetical, the underlying pattern is broadly applicable.
Citations
Esri. (n.d.-a). Alter Field (Data Management Tools) [ArcGIS Pro documentation]. https://pro.arcgis.com/en/pro-app/latest/tool-reference/data-management/alter-field-properties.htm
Esri. (n.d.-b). Select Layer By Location (Data Management Tools) [ArcGIS Pro documentation]. https://pro.arcgis.com/en/pro-app/latest/tool-reference/data-management/select-layer-by-location.htm
Esri. (n.d.-c). UpdateCursor [ArcGIS Pro documentation]. https://pro.arcgis.com/en/pro-app/latest/arcpy/data-access/updatecursor-class.htm
Esri. (n.d.-d). Walk [ArcGIS Pro documentation]. https://pro.arcgis.com/en/pro-app/latest/arcpy/data-access/walk.htm
Python Software Foundation. (n.d.). csv — CSV file reading and writing [Python documentation]. https://docs.python.org/3/library/csv.html
Categories: Spatial data management, Spatial analysis, Scripting and development