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".
VinceE_0-1728507127223.png
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".
VinceE_0-1728507127223.png
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")
VinceE_0-1729265179912.png
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.