How can I assign a specific number of random points based on acreage?

4954
19
02-01-2016 09:33 AM
JamesLabate
New Contributor II

Hello,

Can someone tell me how to generate a specific number of random points based on the acreage of a polygon falling within a specified range?

Is there a way to make the Create Random Points tool generate a specific number of  points based on the comparison of an area field to an area range?

is there another tool or combination of whic I can use?

This is what I'm talking about:

So, I have 158 polygons in my timber stand layer.   I am trying to assign

random points to each individual polygon based on their acreage.  So the

breakdown is as follows:

Size of polygon in acres----number of random points required----number of

polygons of this size on the layer.

0-10acres---3points---4polygons

10-20acres---4---37

20-30acres---5---39

30-40acres---6---20

40-50acres---7---11

50-60acres---8---17

60-70acres---9---13

70-80acres---10---6

80-90acres---11---2

90-100acres---12---3

100-110acres---13---3

133acres---16---1

155acres---18---1

I would like to have the points evenly distributed if possible, if not, they

need to be a minimum of 100meters apart.

Thanks in Advance,

Jim Labate

Tags (1)
0 Kudos
19 Replies
XanderBakker
Esri Esteemed Contributor

I think the sample I posted here: Create Random Points: respect minimal distance ACROSS features  can be adjusted to include the number of points per polygon based on acreage.

JakeSkinner
Esri Esteemed Contributor

Hi James,

The below script should get you started.  It creates a feature layer from each feature, creates random points for each feature, then merges these points to a single feature class.

import arcpy
from arcpy import env
#Specify workspace
env.workspace = r"E:\temp\python\test.gdb"
env.overwriteOutput = 1

#Specify Feature Class
fc = "Airports"

minAcres = 0
maxAcres = 10
numPts = 3

#Iterate through feature class by Ascending order for the field Acres
with arcpy.da.SearchCursor(fc, ["OBJECTID", "Acres"], sql_clause=("ASC", "ORDER BY Acres")) as cursor:
    for row in cursor:
        arcpy.MakeFeatureLayer_management(fc, "fLayer", "OBJECTID = {0}".format(row[0]))
        if row[1] > minAcres and row[1] < maxAcres:
            arcpy.CreateRandomPoints_management(env.workspace, "pts{0}".format(row[0]), "fLayer", "", numPts, "100 Meter")
        elif row[1] == 133:
            numPts = 16
            arcpy.CreateRandomPoints_management(env.workspace, "pts{0}".format(row[0]), "fLayer", "", numPts, "100 Meter")
        elif row[1] == 133:
            numPts = 18
            arcpy.CreateRandomPoints_management(env.workspace, "pts{0}".format(row[0]), "fLayer", "", numPts, "100 Meter")
            
        minAcres += 10
        maxAcres += 10
        numPts += 1

del cursor

list = []

for fc in arcpy.ListFeatureClasses("Pts*"):
    list.append(fc)

#Merge all point feature classes
arcpy.Merge_management(list, "RandomPoints")

#Delete individual point feature classes
for fc in arcpy.ListFeatureClasses("Pts*"):
    arcpy.Delete_management(fc)
JamesLabate
New Contributor II

Jake (and all other respondents in this thread) -

Thanks very much for your input. My apologies for disappearing - I was in the middle of an extended project when I posted the question and have had no opportunity to pick this back up until today.

That said - your script is very impressive - direct and efficient. I must be leaving a slight edit out because the code runs to the end successfully, but so far the best output I've gotten is just a single point - the first point generated from the first polygon in the fc.

You can see the MakeFeatureLayer tool chugging away on every polygon in the input fc - but no temp fLayer (output) is generated - there should be one for every polygon correct? - that in turn is merged into the final "RandomPoints" fc.

A RandomPoints fc is created and the script does not error out - but as earlier stated, the out put is either empty or just contains the first point generated only. So it's very close to working.

There was an empty output warning early on in the results - when the CreateRandomPoints tool first kicked off

RandomPointsError2.JPG

Here is the script as I ran it

import arcpy 

... from arcpy import env 

... #Specify workspace 

... env.workspace = r"C:\GIS\BrettWalker\PointsByAcreage.gdb" 

... env.overwriteOutput = 1 

...      

... #Specify Feature Class 

... fc = r"C:\GIS\BrettWalker\PointsByAcreage.gdb\TimberInventoryStands" 

...      

... minAcres = 0 

... maxAcres = 10 

... numPts = 3 

...      

... #Iterate through feature class by Ascending order for the field Acres 

... with arcpy.da.SearchCursor(fc, ["OBJECTID", "Acres"], sql_clause=("ASC", "ORDER BY Acres")) as cursor: 

...     for row in cursor: 

...         arcpy.MakeFeatureLayer_management(fc, "fLayer", "OBJECTID = {0}".format(row[0])) 

...         if row[1] > minAcres and row[1] < maxAcres: 

...            arcpy.CreateRandomPoints_management(env.workspace, "pts{0}".format(row[0]), "fLayer", "", numPts, "100 Meter") 

...         elif row[1] == 133: 

...             numPts = 16 

...             arcpy.CreateRandomPoints_management(env.workspace, "pts{0}".format(row[0]), "fLayer", "", numPts, "100 Meter") 

...         elif row[1] == 133: 

...             numPts = 18 

...             arcpy.CreateRandomPoints_management(env.workspace, "pts{0}".format(row[0]), "fLayer", "", numPts, "100 Meter") 

...              

...         minAcres += 10 

...         maxAcres += 10 

...         numPts += 1 

...  

... del cursor 

...  

... list = [] 

...  

... for fc in arcpy.ListFeatureClasses("Pts*"): 

...     list.append(fc) 

...  

... #Merge all point feature classes 

... arcpy.Merge_management(list, "RandomPoints") 

...   

... #Delete individual point feature classes 

... for fc in arcpy.ListFeatureClasses("Pts*"): 

...     arcpy.Delete_management(fc) 

Does Darren Wiens suggestion about getting the need to be incorporated?

Thanks again for all your help.

Jim Labate

0 Kudos
XanderBakker
Esri Esteemed Contributor

So you are trying to generate 3 points with a minimum distance of 100 meters between them in an area between 0 and 10 acres? I don't think that is going to work. Not sure if that is the reason for the script to fail, but you might want to reduce the minimum space between the points when the areas are small.

0 Kudos
XanderBakker
Esri Esteemed Contributor

OK, taking a closer look, the main problem is that you test the field Acres and it will only do something if the area is between 0 and 10 acres or the acres = 133. That is the reason it is not working.

0 Kudos
JamesLabate
New Contributor II

Hi Xander,

Thanks for response. The code does test for all the other ranges e.g. 10 - 20; 20- 30 etc. via the 3 lines using the shorthand operator "+=" - Is it that the SearchCursor is not looping through those lines?

Also, when the script has generated a single point output file - for the smallest polygon (4.59 acres) it did not generate 3 points as the variable "numPts" should have instructed it to do.(plenty of room for a min distance of 100 meters)

Below is the status of how the script is operating:

  1. Script runs through without erroring out
  2. SearchCursor – appears to iterate through the rows of the “Acres”  field being tested to see if that record’s value falls in between minAcres & maxAcres range as the original range is defined i.e. 0 – 10 acres.
  3. Then two elif statements check for the two specific non-range values – 133 & 155.
  4. If none of the above values is found I Hope the next 3 lines of code with the shorthand operator “+=” is implemented and the SearchCursor runs through the next 10 acre range 10-20 acres.
    1. minAcres += 10
    2. maxAcres += 10
    3. numPts += 1

So it appears that all the required steps are accounted for in the code in order for it to assess a value in one field against a range and create the assigned number of points for that range via the CreateRandomPoints tool.

  1. MakeFeatureLayer does run for every polygon.

So is the problem:

  • That the shorthand operator ranges are not successfully being cycled through?
  • That a parameter for SearchCursor is slightly off? -
  • That a parameter for MakeFeatureLayer is slightly off?
  • That a parameter for CreateRandomPoints is slightly off?

Again,

it appears to be close to accomplishing the task. Do you see where it's going wrong? Any ideas Jake Skinner - are you still out there?

Thanks to all for assistance,

Jim

0 Kudos
XanderBakker
Esri Esteemed Contributor

Hi James Labate ,

The code does test for all the other ranges e.g. 10 - 20; 20- 30 etc. via the 3 lines using the shorthand operator "+=" - Is it that the SearchCursor is not looping through those lines?

Sure, the SearchCursor will loop through these lines, but the chances that the current area will be in that range are very small. Just have a look at this example:

 

LoopAreaminAcres maxAcres
14,59010
24,891020
310,192030
460,743040
570,694050

The first one is, the next ones aren't... You will have to create a function that returns the desired number of points based on the area.

Also, when the script has generated a single point output file - for the smallest polygon (4.59 acres) it did not generate 3 points as the variable "numPts" should have instructed it to do.(plenty of room for a min distance of 100 meters)

It really depends where the first point is located and if the system can generate more points inside the polygon with the specified minimum distance. From the Help:

When unable to place anymore random points within a constraining area without breaking the minimum allowed distance specified, the number of random points in the constraining area will be reduced to the maximum possible under the minimum allowed distance.

3. Then two elif statements check for the two specific non-range values – 133 & 155.

You are testing twice for the value 133 in the code you pasted in this thread.

0 Kudos
JamesLabate
New Contributor II

Xander,

1)     Doesn't the SearchCursor keep looping through the 3 lines of shorthand operator

minAcres += 10

maxAcres += 10

numPts += 1

until the acreage value for a specified polygon falls within on of the ten acre ranges?

For example, OBJECTID # 33 in my feature class has an acreage value of 106 acres.

Shouldn't the SearchCursor keep looping through the 3 lines of code above until the tenth time through where the range the acreage value being tested against has reached 100 - 110 acres? Then, having found the proper range match which through the shorthand operator should now assign 13 points - can continue on to MakeFeatureLayer and then CreateRandomPoints.

Is the issue here that the 3 lines of shorthand operator Do Not have a cumulative effect? In other words, every time it loops through is it just testing for the 10 - 20 acre range. Even if that was the case, there are 37 polygons that fall in the 10 - 20 acre range so shouldn't those 37 at lease - along with the polys that fall within the 0 - 10 range have points generated?

2)  If I can manually place 3 points at a minimum distance of 100 meters in a 4.59 acre polygon - why can't the CreateRandomPoints tool do it? I don't follow that. To test, I placed a point in the NW section of the 4.59 acre poly and then measure 100 meters SE and then 100 meters SW - 3 points at a minimum distance of 100 meters fit in that polygon - the tool doesn't seem to find the proper starting place for the first point's placement.

3)  The second elif has subsequently been edited to check for 155acres.

I'll also add this bit of the RESULT output.

MakeFeatureLayer does execute for every polygon in the fc but nothing seems to be "visible" to it - see below one example - 157 others similar

Executing: MakeFeatureLayer C:\GIS\BrettWalker\PointsByAcreage.gdb\TimberInventoryStands fLayer "OBJECTID = 84" # "OBJECTID OBJECTID VISIBLE NONE;Shape Shape VISIBLE NONE;AREA_SIZE AREA_SIZE VISIBLE NONE;INV_PA_AC_ INV_PA_AC_ VISIBLE NONE;STD_AC STD_AC VISIBLE NONE;FEAT_ID_ FEAT_ID_ VISIBLE NONE;standID standID VISIBLE NONE;treatment_ treatment_ VISIBLE NONE;remarks remarks VISIBLE NONE;siteindex siteindex VISIBLE NONE;sispecies sispecies VISIBLE NONE;total_basa total_basa VISIBLE NONE;AGS_ba AGS_ba VISIBLE NONE;medial_dia medial_dia VISIBLE NONE;eff_age eff_age VISIBLE NONE;gross_bdft gross_bdft VISIBLE NONE;net_bdft_v net_bdft_v VISIBLE NONE;value value VISIBLE NONE;Shape_Length Shape_Length VISIBLE NONE;Shape_Area Shape_Area VISIBLE NONE;Acres Acres VISIBLE NONE"

Start Time: Fri Feb 26 16:19:16 2016

Succeeded at Fri Feb 26 16:19:16 2016 (Elapsed Time: 0.00 seconds)

Is the tool not temporarily creating the polygon?

Thanks for all the help Xander,

Jim

0 Kudos
XanderBakker
Esri Esteemed Contributor

Let's explain something with some code examples. What you are doing is this:

# generate a list of areas (acres) similar to your data
from random import uniform
lst_areas = []
for i in range(0, 100):
    lst_areas.append(uniform(4.5, 110))
lst_areas += [133, 155]

# now continue with the logic of your script
minAcres = 0
maxAcres = 10
numPts = 3

for area in sorted(lst_areas):
    if area > minAcres and area < maxAcres:
        print area, numPts
    elif area == 133: # if it isn't exactly 133, it will not do this!
        numPts = 16
        print area, numPts
    elif area == 155: # you check for 133 twice!
        numPts = 18
        print area, numPts

    minAcres += 10
    maxAcres += 10
    numPts += 1

This will yield only three results (area<space>numPts):

4.5589899377 3
133 16
155 18

In your case you test twice for 133, so you may have only two results and if the area is not an integer you will not find 133 nor 155.

Probably you meant to do something along the lines of this (a loop within a loop):

from random import uniform
lst_areas = []
for i in range(0, 100):
    lst_areas.append(uniform(4.5, 110))
lst_areas += [133, 155]

for area in sorted(lst_areas):
    minAcres = 0
    maxAcres = 10
    numPts = 3
    cont = True

    while cont:
        if area > minAcres and area < maxAcres:
            print area, numPts
            cont = False
        elif area == 133: # if it isn't exactly 133, it will not do this!
            numPts = 16
            print area, numPts
            cont = False
        elif area == 155: # you check for 133 twice!
            numPts = 18
            print area, numPts
            cont = False

        minAcres += 10
        maxAcres += 10
        numPts += 1

This will yield a numPts for each individual area:

5.90022859369 3
6.06444530915 3
6.34175139149 3
...
107.897255347 13
109.498385359 13
133 16
155 18

The point is, there is no need to make thing this complex, since you could use something like:

from random import uniform
lst_areas = []
for i in range(0, 100):
    lst_areas.append(uniform(4.5, 110))
lst_areas += [133, 155]

for area in sorted(lst_areas):
    print area, int(area // 10) + 3

... providing the same result.

0 Kudos