[GIS] If two symbols in same QGIS layer are too close, show only one

categorized-rendererqgisqgis-custom-functionsvgsymbology

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?

Best Answer

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