I have survey data which is collected on a 1ha grid square, within each grid will be 4 survey points.
What I am looking to do is select 3 points that fall within a larger grid square, 10ha.
With how the data has been collected there are roughly 36 points per 10ha grid square.
The points selected can be random.
I thought about creating random points and some sort of buffer but that is just a no go and also thought about select by attribute but the data doesn't lend itself to getting specifically 3 survey points selected using any one attribute.
I've dropped a screenshot of how the survey appears with a 10ha polygon grid for reference.
Any suggestions are welcome.
Solved! Go to Solution.
Not sure if I understand your exact needs, but you can try something like below. I think a more efficient method would be doing a spatial join in memory and then working with a pandas Data Frame to select three points from each subgroup, but this may get you started.
Code below will randomly select exactly three points within each "box".
import sys
import numpy as np
# a list of point IDs to eventually export
point_oids_to_export = []
# Iterate over your sampling boxes
with arcpy.da.SearchCursor('SamplingPointBox', "OID@") as scurs:
for oid, in scurs:
# Select the current box. Select all the points with that box.
box_selection = arcpy.management.SelectLayerByAttribute('SamplingPointBox', 'NEW_SELECTION', f"OID = {oid}")
point_selection = arcpy.management.SelectLayerByLocation('SamplingPoint', 'INTERSECT', 'SamplingPointBox')
# Get a list of the OIDs of the points within the box.
oidValueList = [r[0] for r in arcpy.da.SearchCursor(point_selection, ["OID@"])]
# Select three of those OIDs, not selecting the same one twice.
rng = np.random.default_rng()
chosen_oids = rng.choice(oidValueList, 3, False, None, 0, False)
# Add those three new points to the running list.
point_oids_to_export.extend(chosen_oids)
# Select all the collected points based on the long list of OIDs
query = f"OBJECTID IN {tuple(point_oids_to_export)}"
arcpy.management.SelectLayerByAttribute('SamplingPoint', 'NEW_SELECTION', query)
## export your selection from here, or add "arcpy.conversion.ExportFeatures()"
Not sure if I understand your exact needs, but you can try something like below. I think a more efficient method would be doing a spatial join in memory and then working with a pandas Data Frame to select three points from each subgroup, but this may get you started.
Code below will randomly select exactly three points within each "box".
import sys
import numpy as np
# a list of point IDs to eventually export
point_oids_to_export = []
# Iterate over your sampling boxes
with arcpy.da.SearchCursor('SamplingPointBox', "OID@") as scurs:
for oid, in scurs:
# Select the current box. Select all the points with that box.
box_selection = arcpy.management.SelectLayerByAttribute('SamplingPointBox', 'NEW_SELECTION', f"OID = {oid}")
point_selection = arcpy.management.SelectLayerByLocation('SamplingPoint', 'INTERSECT', 'SamplingPointBox')
# Get a list of the OIDs of the points within the box.
oidValueList = [r[0] for r in arcpy.da.SearchCursor(point_selection, ["OID@"])]
# Select three of those OIDs, not selecting the same one twice.
rng = np.random.default_rng()
chosen_oids = rng.choice(oidValueList, 3, False, None, 0, False)
# Add those three new points to the running list.
point_oids_to_export.extend(chosen_oids)
# Select all the collected points based on the long list of OIDs
query = f"OBJECTID IN {tuple(point_oids_to_export)}"
arcpy.management.SelectLayerByAttribute('SamplingPoint', 'NEW_SELECTION', query)
## export your selection from here, or add "arcpy.conversion.ExportFeatures()"
For posterity, this seems like a much more efficient option (working with Pandas Spatial Data Frames, instead of Cursors and SelectionByLocation/Attribute):
def new_method(pnt: Union[str, os.PathLike], # full path to points
box: Union[str, os.PathLike], # full path to boxes
box_id: str, # field name containing Box ID
sample_count: int, # how many points per box?
output: Union[str, os.PathLike] # full path to output FC
) -> None: # function has no return
"""SpatJoin points with boxes. Convert to SDF. Groups points by joined BOX ID.
Select 3 points per group. Exports sampled Spatial Data Frame back to FC."""
pnt_box_join = arcpy.analysis.SpatialJoin(pnt, box, r"memory\pnt_box_join").getOutput(0)
# Convert FC to SDF. Drop unused columns. GroupBy BOX_ID column.
# Without replacing, sample X# rows from each group. Export SDF back to FC.
sdf = pd.DataFrame.spatial.from_featureclass(pnt_box_join)
sdf = sdf.drop(columns=["Join_Count", "TARGET_FID", "Shape_Length", "Shape_Area"])
sampled_sdf = sdf.groupby(box_id, group_keys=False).sample(n=sample_count)
sampled_sdf.spatial.to_featureclass(location=output, sanitize_columns=False)
print("SAMPLED SDF -> FC EXPORTED")
Morning @VinceE ,
Thanks very much for replying and sorry for the slow response.
I think you have understood my question perfectly. I have created some test data to see how the above works, and it works well. The only thing that came up was that if there were less than 3 points in a box then it would stop at that point. But that makes sense since its looking to select 3 points so this would be impossible if there aren't at least 3 to begin with.
This is great, a real help and gives me something to learn from. Thanks again.