Create Random Points (Data Management) on a Basic License with ArcPy

The Create Random Points (Data Management) tool is available with an Advanced license or on a Basic or Standard license if you have 3D Analyst or Spatial Analyst. In this post we will look at creating the tool for use with a Basic license. We’ll keep it simple for now and just generate the desired amount of points per feature. Check out the Esri documentation for the Create Random Points tool.

If you’re interested in learning ArcPy, check out this course.

The syntax for the Create Random Points (Data Management) tool is as follows…

arcpy.management.CreateRandomPoints(out_path, out_name, {constraining_feature_class}, {constraining_extent}, {number_of_points_or_field}, {minimum_allowed_distance}, {create_multipoint_output}, {multipoint_size})

We’re going to take a different direction from the Create Random Points syntax and use three required parameters; in_features, the features you wish to generate random points for, these can be Point, Polyline, or Polygon; out_feature_class, the output Point feature class to create containing the random points; and number_of_points, the number of points to generate for each feature from the in_features.

Let’s get started by importing the necessary modules…

import arcpy
import random

…and then the required user inputs as discussed above.

## the input features to generate random points for
in_features = arcpy.GetParameterAsText(0)
## the output point feature class
out_feature_class = arcpy.GetParameterAsText(1)
## the number of points to generate for each feature
number_of_points = arcpy.GetParameterAsText(2)

We require four objects to help us with our workflow; the shape type of the in_features, whether it is Point, Polyline, or Polygon; the Spatial Reference System code so we can add as the SRS to the out_feature_class; the OID field from the in_features so we can assign to the ORIG_FID in the out_feature_class; and then to make sure that we cast the number of points from the input to an integer.

## shape type of in features - Point, Polygon, Polyline
shape_type = arcpy.Describe(in_features).shapeType

## srs id of the in_features so we can assign the output the same
srs_id = arcpy.Describe(in_features).spatialReference.factoryCode

## the OID field name from the in_features
oid_fld = [fld.name for fld in arcpy.ListFields(in_features) if fld.type=="OID"][0]

## make sure the number of points is an integer
number_of_points = int(number_of_points)

The memory workspace is fantastic for holding intermediate data in the workflow so we will use it to store or random generated points. We create a feature class in the memory workspace and then add an ORIG_FID field.

## a memory workspace feature class to store the random points generated
temp_fc = arcpy.management.CreateFeatureclass("memory", "temp_fc", "POINT", spatial_reference=srs_id)
## add ORIG_FID field to teh feature class
arcpy.management.AddField(temp_fc, "ORIG_FID", "LONG")

Now for the magic to happen! We need to account for our three possible shape types; Point, Polyline, and Polygon.

If the in_features are Points, we need to know if the number_of _points entered is greater than or equal to the number of records in the in_features. If the number_of_points is indeed greater or equal to the record count, then all points are exported as ‘random points’. However, if the number_of_points is less than the record count of the in_features, then we generate a random selection of OID values and use an Insert Cursor with a where clause to add the points to our memory feature class.

If the in_features are Polylines, we combine the use of an Insert Cursor with our memory feature class with a Search Cursor for our in_features and use a random distance along each line to generate each random point.

Finally, if the in_features are Polygons, we combine the use of an Insert Cursor with our memory feature class with a Search Cursor for our in_features, we get the minimum bounding extent for each Polygon features and generate random X and Y coordinate pairs the fall within the bounding extent. We perform a test that the random point is contained by the polygon geometry and if not, go again until it is.

## if the in_features are Points
if shape_type == "Point":
    num_points = int(arcpy.management.GetCount(in_features)[0])

    ## if the number of random points required is more than the points in in_features
    if number_of_points >= num_points:
        ## no need for a where clause if the number of points from user input
        ## matches or exceeds the number of points in the in_features
        sql_exp = ""

    ## otherwise get a random selection
    else:
        ## get a list of all OIDs
        oid_list = [row[0] for row in arcpy.da.SearchCursor(in_features, oid_fld)]
        ## get a random list of OIDs
        random_list = random.sample(oid_list, number_of_points)
        ## create the where clause sql expression
        sql_exp = "{0} IN ({1})".format(oid_fld, ",".join(str(x) for x in random_list))

    ## start an insert cursor for the temp_fc
    with arcpy.da.InsertCursor(temp_fc, ["ORIG_FID", "SHAPE@"]) as insert_cursor:
        ## iterate through the records of the in_features
        with arcpy.da.SearchCursor(in_features, [oid_fld, "SHAPE@"], sql_exp) as search_cursor:
            ## add the points to the temp_fc
            for row in search_cursor:
                insert_cursor.insertRow((row[0], row[1]))

elif shape_type == "Polyline":
    ## start an insert cursor for the temp_fc
    with arcpy.da.InsertCursor(temp_fc, ["ORIG_FID", "SHAPE@"]) as insert_cursor:
        ## iterate through the records for the in_features
        with arcpy.da.SearchCursor(in_features, [oid_fld, "SHAPE@", "SHAPE@LENGTH"]) as search_cursor:
            ## for each linear record
            for row in search_cursor:
                ## generate a random point along the line and add to temp_fc
                for num in range(number_of_points):
                    distance = round(random.uniform(0.0, row[2]), 3)
                    pt = row[1].positionAlongLine(distance)
                    insert_cursor.insertRow((row[0], pt))

elif shape_type == "Polygon":
    ## start an insert cursor for the temp_fc
    with arcpy.da.InsertCursor(temp_fc, ["ORIG_FID", "SHAPE@"]) as insert_cursor:
        ## iterate through the records for the in_features
        with arcpy.da.SearchCursor(in_features, [oid_fld, "SHAPE@"]) as search_cursor:
            ## for each polygon record
            for row in search_cursor:
                ## to hold random points
                random_points = []
                ## get the extent of the feature
                extent = row[1].extent
                min_x, max_x = extent.XMin, extent.XMax
                min_y, max_y = extent.YMin, extent.YMax

                ## generate points
                for num in range(number_of_points):
                    while len(random_points) < number_of_points:
                        x = random.uniform(min_x, max_x)
                        y = random.uniform(min_y, max_y)
                        pt = arcpy.Point(x, y)
                        ## if the point is inside the polygon then add to the list
                        if row[1].contains(pt):
                            random_points.append(pt)

                ## insert points into the temp_fc
                for pt in random_points:
                    insert_cursor.insertRow((row[0], pt))

Export the random generated points in our memory feature class to disk.

## export random generated points to a feature class on disk
arcpy.conversion.ExportFeatures(temp_fc, out_feature_class)

And as always, clean-up the memory workspace.

## clean-up memory workspace
arcpy.management.Delete(temp_fc)

Save your script and open up ArcGIS Pro. 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 createRandomPointsLabel to Create Random Points (Basic), and the Description to Create Random Points with a Basic License.

In the Parameters tab set as per below. Set the Filter for Input Features to Feature Type; Point, Polyline, Polygon. Set the Direction for Output Feature Class to Output and a Filter for Feature Type of Point.

In the Execution tab, click the folder icon in the top-right corner and ad your saved Python script. Click OK and take the tool for a spin.

You can download the tool and other Advanced tools with a Basic license over on this page.

Here’s the Create Random Points script in its entirety.

import arcpy
import random

################################################################################
## Esri Documentation
##  https://pro.arcgis.com/en/pro-app/latest/arcpy/functions/getparameterastext.htm
##  https://pro.arcgis.com/en/pro-app/latest/arcpy/functions/describe.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/tool-reference/data-management/create-feature-class.htm
##  https://pro.arcgis.com/en/pro-app/latest/arcpy/classes/polyline.htm
##  https://pro.arcgis.com/en/pro-app/latest/arcpy/classes/polygon.htm
##  https://pro.arcgis.com/en/pro-app/latest/tool-reference/data-management/get-count.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
##
## ArcGIS Pro Version: 3.1.0
##
################################################################################

################################################################################
## USER INPUTS

## the input features to generate random points for
in_features = arcpy.GetParameterAsText(0)
## the output point feature class
out_feature_class = arcpy.GetParameterAsText(1)
## the number of points to generate for each feature
number_of_points = arcpy.GetParameterAsText(2)

################################################################################
## REQUIRED OBJECTS

## shape type of in features - Point, Polygon, Polyline
shape_type = arcpy.Describe(in_features).shapeType

## srs id of the in_features so we can assign the output the same
srs_id = arcpy.Describe(in_features).spatialReference.factoryCode

## the OID field name from the in_features
oid_fld = [fld.name for fld in arcpy.ListFields(in_features) if fld.type=="OID"][0]

## make sure the number of points is an integer
number_of_points = int(number_of_points)

################################################################################
## CREATE POINT FEATURE CLASS IN MEMORY WORKSPACE

## a memory workspace feature class to store the random points generated
temp_fc = arcpy.management.CreateFeatureclass("memory", "temp_fc", "POINT", spatial_reference=srs_id)
## add ORIG_FID field to teh feature class
arcpy.management.AddField(temp_fc, "ORIG_FID", "LONG")

################################################################################
## CREATE RANDOM POINTS

## if the in_features are Points
if shape_type == "Point":
    num_points = int(arcpy.management.GetCount(in_features)[0])

    ## if the number of random points required is more than the points in in_features
    if number_of_points >= num_points:
        ## no need for a where clause if the number of points from user input
        ## matches or exceeds the number of points in the in_features
        sql_exp = ""

    ## otherwise get a random selection
    else:
        ## get a list of all OIDs
        oid_list = [row[0] for row in arcpy.da.SearchCursor(in_features, oid_fld)]
        ## get a random list of OIDs
        random_list = random.sample(oid_list, number_of_points)
        ## create the where clause sql expression
        sql_exp = "{0} IN ({1})".format(oid_fld, ",".join(str(x) for x in random_list))

    ## start an insert cursor for the temp_fc
    with arcpy.da.InsertCursor(temp_fc, ["ORIG_FID", "SHAPE@"]) as insert_cursor:
        ## iterate through the records of the in_features
        with arcpy.da.SearchCursor(in_features, [oid_fld, "SHAPE@"], sql_exp) as search_cursor:
            ## add the points to the temp_fc
            for row in search_cursor:
                insert_cursor.insertRow((row[0], row[1]))

elif shape_type == "Polyline":
    ## start an insert cursor for the temp_fc
    with arcpy.da.InsertCursor(temp_fc, ["ORIG_FID", "SHAPE@"]) as insert_cursor:
        ## iterate through the records for the in_features
        with arcpy.da.SearchCursor(in_features, [oid_fld, "SHAPE@", "SHAPE@LENGTH"]) as search_cursor:
            ## for each linear record
            for row in search_cursor:
                ## generate a random point along the line and add to temp_fc
                for num in range(number_of_points):
                    distance = round(random.uniform(0.0, row[2]), 3)
                    pt = row[1].positionAlongLine(distance)
                    insert_cursor.insertRow((row[0], pt))

elif shape_type == "Polygon":
    ## start an insert cursor for the temp_fc
    with arcpy.da.InsertCursor(temp_fc, ["ORIG_FID", "SHAPE@"]) as insert_cursor:
        ## iterate through the records for the in_features
        with arcpy.da.SearchCursor(in_features, [oid_fld, "SHAPE@"]) as search_cursor:
            ## for each polygon record
            for row in search_cursor:
                ## to hold random points
                random_points = []
                ## get the extent of the feature
                extent = row[1].extent
                min_x, max_x = extent.XMin, extent.XMax
                min_y, max_y = extent.YMin, extent.YMax

                ## generate points
                for num in range(number_of_points):
                    while len(random_points) < number_of_points:
                        x = random.uniform(min_x, max_x)
                        y = random.uniform(min_y, max_y)
                        pt = arcpy.Point(x, y)
                        ## if the point is inside the polygon then add to the list
                        if row[1].contains(pt):
                            random_points.append(pt)

                ## insert points into the temp_fc
                for pt in random_points:
                    insert_cursor.insertRow((row[0], pt))

################################################################################
## SAVE MEMORY FEATURE CLASS TO DISK

## export random generated points to a feature class on disk
arcpy.conversion.ExportFeatures(temp_fc, out_feature_class)

################################################################################
## CLEAN-UP MEMORY WORKSPACE

## clean-up memory workspace
arcpy.management.Delete(temp_fc)

################################################################################

Leave a Comment

Your email address will not be published. Required fields are marked *