If you work in mineral exploration or geotechnical mapping, you already know how tedious it is to manually digitize drillhole traces in ArcGIS Pro one by one. The good news is that ArcGIS Pro’s built-in arcpy module lets you automate the entire process: read collar coordinates and survey data (azimuth, dip, and depth) straight from a CSV file, then generate 3D polyline features representing each drillhole trace in a single script run.
In this tutorial, we’ll build a simple Python script that reads a CSV file containing drillhole collar location, azimuth, dip, and depth, then calculates the end point of each hole using basic trigonometry and writes the resulting 3D polylines into a feature class.

Expected CSV Structure
Your CSV file should have at minimum the following columns:
| Column | Description | Example |
|---|---|---|
| HoleID | Unique identifier for the drillhole | DH-001 |
| X, Y, Z | Collar coordinates (easting, northing, elevation) | 456123.5, 7890456.2, 512.3 |
| Azimuth | Hole direction in degrees (0–360, from north) | 135 |
| Dip | Hole inclination in degrees (negative for downward holes) | -60 |
| Depth | Total hole length | 250 |
If you’re new to reading tabular data into Python, our guide to reading CSV files with pandas is a good primer before working with csv.DictReader here.
Trace Geometry Diagram
Before diving into the code, here’s a diagram of how the trace endpoint is derived from the collar point, azimuth, and dip (this is a schematic to explain the geometry, not a screenshot of the ArcGIS Pro interface):
The Python Script
import arcpy
import csv
import math
import os
# ---- Configuration ----
csv_path = r"C:\Data\drillholes.csv"
output_gdb = r"C:\Data\DrillholeTraces.gdb"
output_fc_name = "DrillholeTraces"
output_fc = os.path.join(output_gdb, output_fc_name)
# Set this to match your project's coordinate system
sr = arcpy.SpatialReference(32750) # example: WGS 1984 UTM Zone 50S
# ---- Create output feature class ----
if not arcpy.Exists(output_gdb):
arcpy.management.CreateFileGDB(os.path.dirname(output_gdb), os.path.basename(output_gdb))
if arcpy.Exists(output_fc):
arcpy.management.Delete(output_fc)
arcpy.management.CreateFeatureclass(
output_gdb, output_fc_name, "POLYLINE",
spatial_reference=sr, has_z="ENABLED"
)
arcpy.management.AddField(output_fc, "HoleID", "TEXT", field_length=50)
# ---- Read CSV and build traces ----
with arcpy.da.InsertCursor(output_fc, ["SHAPE@", "HoleID"]) as cursor:
with open(csv_path, "r", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
hole_id = row["HoleID"]
x, y, z = float(row["X"]), float(row["Y"]), float(row["Z"])
azimuth = math.radians(float(row["Azimuth"]))
dip = math.radians(float(row["Dip"]))
depth = float(row["Depth"])
# Convert azimuth/dip/depth into a 3D displacement vector
dx = depth * math.sin(azimuth) * math.cos(dip)
dy = depth * math.cos(azimuth) * math.cos(dip)
dz = depth * math.sin(dip) # dip is negative for downward holes
end_x, end_y, end_z = x + dx, y + dy, z + dz
array = arcpy.Array([
arcpy.Point(x, y, z),
arcpy.Point(end_x, end_y, end_z)
])
polyline = arcpy.Polyline(array, sr, True)
cursor.insertRow([polyline, hole_id])
print(f"Drillhole traces written to {output_fc}")
How It Works
The script converts each hole’s azimuth and dip into a 3D unit vector using trigonometry, scales it by the hole’s total depth, and adds it to the collar coordinate to find the toe (end point) of the hole. Each hole becomes a two-vertex 3D polyline — enough to represent a straight trace. If you have downhole survey stations instead of a single azimuth/dip/depth per hole, you can extend the script to loop through multiple survey rows per HoleID and build a multi-vertex polyline for a more accurate, curved trace.
Running the Script
From the Python Window
Save the script as a .py file and run it from the ArcGIS Pro Python window for a quick one-off run. This is the fastest way to test it against a small sample CSV before scaling up.
As a Reusable Script Tool
For repeated use, set it up as a Script Tool in a custom toolbox so it can be reused with different input parameters (CSV path, output geodatabase, coordinate system) without editing the code each time — the same pattern used in our regular grid points arcpy tutorial. Once it finishes, add the output feature class to your ArcGIS Pro scene to visualize the traces in 3D.
This approach scales well — whether you have 10 holes or 10,000, the script processes the entire CSV in seconds, saving hours of manual digitizing.
Related ArcGIS Pro Tutorials
- How to Create Regular Grid Points on ArcGIS Pro — another arcpy geometry-generation script, useful if you also need sample or grid points around your drillholes.
- How to Export Layer to GPX in ArcGIS Pro — handy once your drillhole traces are created and you need to bring collar locations into a GPS device.
FAQ
Can this script handle deviated (curved) drillholes?
Not as written — this version draws a straight trace from a single azimuth, dip, and depth per hole. For deviated holes, extend the script to loop through multiple downhole survey stations per HoleID and build a multi-vertex polyline.
Do I need an ArcGIS Pro license to run this script?
Yes — arcpy ships with ArcGIS Pro and requires a valid license to import and run, whether from the Python window, a notebook, or a standalone script.
What coordinate system should I use?
Match the arcpy.SpatialReference() code to your project’s actual coordinate system (a projected CRS, not geographic) so depth and displacement calculations are in real-world linear units like meters.
Further Reading
- Esri: arcpy Polyline class reference
- Esri: arcpy.da.InsertCursor class reference
- Esri: Create Feature Class (Data Management) tool reference
- Esri: Setting up a Python editor for ArcGIS Pro