6

I have two shapefiles: polygon(buffer) and point.

How can I select only one point in each buffer in QGIS?

If I use the "Select by Location", I will get all points inside the polygons, however I need only one? It doesn't matter which point out of two or more in polygon is selected.

enter image description here

3
  • 1
    Have you tried select by expression with geometry intersection?
    – Erik
    May 23 at 11:37
  • No, haven't tried that, not really sure how to write the expression.
    – g123456k
    May 23 at 11:39
  • I would have spatial join the points with the polygons, delete duplicates of the result, and then select the point by location with the deleted duplicates points. select by expression should be more efficient but you need those magical spell codes.
    – sutan
    May 23 at 12:10

6 Answers 6

8

If you don't care which point is selected, you can use this small PyQGIS script. Paste into a blank editor in the Python Console, change the layer names in the first two lines to match your layer names and click run.

# Change layer names in the first two lines to match your layers...
point_lyr = QgsProject.instance().mapLayersByName('New scratch layer')[0]
poly_lyr = QgsProject.instance().mapLayersByName('Buffered')[0]

ids = []

for feat in poly_lyr.getFeatures():
    contained_point = [f for f in point_lyr.getFeatures() if f.geometry().within(feat.geometry())][0]
    ids.append(contained_point.id())

point_lyr.selectByIds(ids)

Result:

enter image description here

1
  • This works so perfect! Thank you!
    – g123456k
    May 23 at 15:28
6

With the "Select by expression" tool you can use:

array_first(
    array_agg(
        $id,
        group_by:=overlay_intersects('polygons', $id), -- replace 'polygons'
        filter:=array_length(overlay_intersects('polygons', $id)) > 0 -- replace 'polygons'
        )
    ) = $id

on your point layer. Replace the name of the 'polygons' layer with the actual name and run it.

QGIS 3.16+ is required.

5

One way to do it would be:

  • First do a "Join Attributes by location" (Vector -> Data Management Tools) with your points as the Base Layer, your polygons as the Join Layer, "within" as the Geometric predicate and for the Join type : "Take attributes of the first matching feature only (one-to-one)". Remember you need to have a Spatial index for both your layers before doing this (Vector -> Data Management Tools -> Create Spatial Index)
  • Then, now that all your points within the same polygon have the same attribute, use the "Delete Duplicates by attribute" from the Processing toolbox in order to keep only one point inside each polygon.

Hope that helps.

4

To add some performance improvements to @Ben W's answer by using a spatial index and a break command:

point_lyr = QgsProject.instance().mapLayersByName('points')[0]
poly_lyr = QgsProject.instance().mapLayersByName('polygons')[0]

ids = []
point_lyr.removeSelection() #clear selection

point_lyr_idx = QgsSpatialIndex(point_lyr.getFeatures()) # build a spatial index on the points

for polygon in poly_lyr.getFeatures():
    for pointid in point_lyr_idx.intersects(polygon.geometry().boundingBox()): # iterate only over points inside bbox of polygon
        point = point_lyr.getFeature(pointid)
        if point.geometry().within(polygon.geometry()): # check if point is actually inside polygon
            ids.append(point.id())
            break # stop iteration after first match and go to next polygon

point_lyr.selectByIds(ids)
3

To add even more performance improvements to @MrXsquared's answer by using QgsGeometryEngine and skipping feature fetching:

point_lyr = QgsProject.instance().mapLayersByName('points')[0]
poly_lyr = QgsProject.instance().mapLayersByName('polygons')[0]

ids = []
point_lyr.removeSelection() #clear selection

# build a spatial index on the points (with flag to be able to retrieve geometries)
point_lyr_idx = QgsSpatialIndex(point_lyr.getFeatures(), flags=QgsSpatialIndex.FlagStoreFeatureGeometries) 

for polygon in poly_lyr.getFeatures():
    poly_geometry_engine = QgsGeometry.createGeometryEngine(polygon.geometry().constGet())
    poly_geometry_engine.prepareGeometry()

    for pointid in point_lyr_idx.intersects(polygon.geometry().boundingBox()): 
        # iterate only over points inside bbox of polygon
        point_geometry = point_lyr_idx.geometry(pointid)

        if poly_geometry_engine.contains(point_geometry.constGet()): # check if point is actually inside polygon
            ids.append(pointid)
            break # stop iteration after first match and go to next polygon

point_lyr.selectByIds(ids)

QgsGeometryEngine can be really powerful in some cases so it is useful to know about it (in some cases I have gotten a x100 performance improvement using it).

With these improvements you can get a ~20-30% improvement compared to @MrXsquared's answer.

4
  • Neat thing! Would be interested in reading up about it more, do you have some references?
    – MrXsquared
    May 25 at 10:58
  • The detailed description of the class in the api documentation is the best/only reference i have found. I found it accidentally while reading the qgsgeometry documentation there doesn't seem to be a lot of documented use of this class which is a shame in my opinion considering the huge benefits it can provide for really intensive spatial operations May 25 at 11:03
  • Especially because apparently this class has existed since qgis version 2.10 May 25 at 11:07
  • even in this article nyalldawson.net/2016/10/speeding-up-your-pyqgis-scripts written by one of the contributors of QGIS on the topic of optimizing pyqgis scripts it isn't mentioned at all May 25 at 11:09
1

You don't need to write a script, there is a built-in Random selection within subsets tool. Before start you need to join by location ID of the polygons to your point data and specify that field in the ID field parameter.

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.