8

I have a shapefile layer with different underwater, over water and water surface stones. These are often too close to each other to be shown nicely on the print composer generated map. I use svg symbols for the stones, and they are styled (svg for the stones are chosen) as categorized by a class id in the shapefile table.

How do I get QGIS to show only stones that are, for instance, more than 2mm or xx map units apart?

4
  • Please, have a look at my answer. I didn't test it widely, but it seems to work (I hope it doesn't conflict with the SVG symbols). Let me know if there is any problem!
    – mgri
    Commented Mar 26, 2017 at 21:54
  • How will you decide which one to show? If there are two: one on the left, one on the right, which one should be displayed? The larger one?
    – Fezter
    Commented Mar 27, 2017 at 6:19
  • @Fezter I don't know if your comment is for me but, in my approach, I don't make choices. If there are two stones, one on the left, one on the right, they will be displayed if their buffers don't intersect the other corresponding stone; otherwise, the both won't be displayed (since the condition should be verified or not for both stones at the same time).
    – mgri
    Commented Mar 27, 2017 at 6:48
  • @mgri oh I see.
    – Fezter
    Commented Mar 27, 2017 at 6:55

2 Answers 2

14

EDIT I edited the question because the OP specified that he is working with point shapefiles (the approach will be similar to the original one, though).


I propose an approach which only recurs to a geometry generator and a custom function.

Context

Let's assume to start from this point vector layer representing the stones:

enter image description here

The layer stores the class ids in the "CLASS_ID" field.

Solution

Go to Layer Properties | Style and then:

  1. Choose the Categorized renderer;
  2. Select the "CLASS_ID" field (or whatever you want for your situation), without the further clicking on the Classify button;
  3. Click on the Change button for the Symbol option.

From the Symbol selector dialog, choose a Geometry generator as symbol layer type and Point / MultiPoint as geometry type. Then click on the Function Editor tab:

enter image description here

Then, click on New file and type show as the name of the new function:

enter image description here

You will see that a new function has been created and it is listed on the left side of the dialog. Now, click on the name of the function and replace the default @qgsfunction with the following code (don't delete the libraries imported by default):

@qgsfunction(args='auto', group='Custom')
def set_dist(distance, layer_name, geom, curr_feat, feature, parent):
    layer  = QgsMapLayerRegistry.instance().mapLayersByName(layer_name)[0]
    buffered_geom = geom.buffer(distance, -1)
    req = QgsFeatureRequest().setFilterRect(buffered_geom.boundingBox())
    show = 1
    for feat in layer.getFeatures(req):
        inGeom = feat.geometry()
        if feat.id() != curr_feat.id() and buffered_geom.intersects(inGeom):
            return False
            break
    return True

Once you have done this, click on the Load button and you will be able to see the function from the Custom Menu of the Expression dialog.

Now, type this expression (see the image below as reference):

CASE
WHEN 
show(10,  @layer_name, $geometry, $currentfeature)
THEN 
$geometry
END

enter image description here

You have just run a function which is saying, in an imaginary way:

"For the current layer (@layer_name), display the geometry ($geometry) from the current feature ($currentfeature) only when it is more than 10 meters apart from any other feature in the same layer."

The only thing you need to change is the research value for the displaying, which in your case should be 0.002 if you are using a Projected CRS. Therefore, leave the other function parameters as provided.

If you want to set SVG symbols for the stones, go to Simple fill and select the proper option:

enter image description here

Finally, go back to the Style main dialog and click on the Classify button (at last!) for setting the changes.

You will see that some stones are not rendered (in the following image I also reported a buffer layer of 10 meters for the sake of clearness):

enter image description here

15
  • Thank you for once again very clear instructions! I think that this would work very well for me, except one thing: I was not clear enough about this but my objects (stones) are points, not polygons. It is possible to insert the code & expresstion that you give for a point shapefile, but categorizing the points with SVG images, does not work. QGIS automatically tries to make a line as a sub-style for a svg image, and the svg image is not displayed. On your result image (with stones of which some are not rendered) I am a bit unclear about why the stones in the middle are not shown? Commented Mar 27, 2017 at 15:24
  • @BenjaminDonner please, see my edited answer (I adapted it for a point shapefile).
    – mgri
    Commented Mar 28, 2017 at 15:26
  • For extra points drop the spatial index and use QgsFeatureRequest().setFilterRect(tmp_geom.buffer(distance,-1).boundingBox()) instead. The index only would speed up things if reused (caching it in the QgsExpressionContext would be possible but more complicated). Commented Mar 31, 2017 at 15:18
  • @MatthiasKuhn Thank for the hint! I will try to adapt your hint to my code and edit the answer if I will be able to implement it. =)
    – mgri
    Commented Mar 31, 2017 at 15:29
  • 4
    Excellent and very detailed answer, what GIS.SE is trying to achieve!
    – Mapperz
    Commented Mar 31, 2017 at 16:08
2

The simple solution is, update to QGIS 2.99/3.0 and use a "Point cluster" renderer.

See this video

https://www.youtube.com/watch?v=gNL-ZSlc4KM

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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