Bird's eye view of a mining exploration area with drill pads

How to Create an ArcGIS Pro Python Script to Generate Drillhole Traces from CSV

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.

Bird's eye view of a mining exploration area with drill pads
Photo by Curioso Photography on Unsplash

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):

Surface Collar (X, Y, Z) Toe (end of hole) Dip Vertical reference Depth (along azimuth/dip)

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

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