project = QgsProject.instance()
#Change to match your layer names below
grid = project.mapLayersByName('Hex_grid')[0]
points = project.mapLayersByName('Random_points')[0]
point_feats = [f for f in points.getFeatures()]
grid_feats = [f for f in grid.getFeatures()]
def find_nearest_empty_hexagon(feat):
index = QgsSpatialIndex(grid.getFeatures())
n = 0
found = False
while not found:
n+=1
nn_id = index.nearestNeighbor(feat.geometry(), n)[n-1]
geom = grid.getFeature(nn_id).geometry()
if not geom.intersects(QgsGeometry().collectGeometry([f.geometry() for f in points.getFeatures()])):
found = True
return nn_id
points_moved = []
for hex_ft in grid_feats:
points_within = [f for f in point_feats if f.geometry().within(hex_ft.geometry())]
if len(points_within) > 1:
for p in points_within:
current_centroid = hex_ft.geometry().centroid()
min_dist = min([p.geometry().distance(current_centroid) for p in points_within])
if p.geometry().distance(current_centroid) == min_dist:
continue
nearest_empty_id = find_nearest_empty_hexagon(p)
nearest_empty_feat = [f for f in grid_feats if f.id() == nearest_empty_id][0]
points.dataProvider().changeGeometryValues({p.id(): nearest_empty_feat.geometry().centroid()})
points_moved.append(p.id())
points_outside_grid = []
remaining_points = [p for p in point_feats if p.id() not in points_moved]
for p in remaining_points:
enclosing_hex = [f for f in grid_feats if f.geometry().contains(p.geometry())]
if not enclosing_hex:
points_outside_grid.append(p)
elif len(enclosing_hex)>0:
enclosing_hex_centroid = enclosing_hex[0].geometry().centroid()
points.dataProvider().changeGeometryValues({p.id(): enclosing_hex_centroid})
if points_outside_grid:
for p in points_outside_grid:
nearest_empty_id = find_nearest_empty_hexagon(p)
nearest_empty_feat = [f for f in grid_feats if f.id() == nearest_empty_id][0]
points.dataProvider().changeGeometryValues({p.id(): nearest_empty_feat.geometry().centroid()})
points.triggerRepaint()