QGIS Labeling – How to Label Only One Duplicate Attribute Without Editing Original Data

duplicationexpressionlabelingpointqgis

I have several points (e.g. 4 bus stop positions) in one area. They all belong to the same station, so they all have the same "name" attribute. To avoid a mess of labels showing all the same name I only want to label one feature.

I don't want to create new (virtual)layers or columns in my file to keep it simple and universal to use; Just use them as they are. So an expression should do it.

Data is taken from OSM (Geofabrik).

enter image description here

Best Answer

You can achieve something like in the picture just below by using some expressions without editing any of your data. Just use them as they are.

enter image description here

Follow the steps below and adjust them to your personal setup if you need to.

Adjust osm_id (must be a unique identifier like fid or similar) or name (the label you wish to display) if needed.

1. Filter within the "label with" field

Fill the label with field with the following code:

Case When
array_find(array_agg("osm_id",group_by:="name"),attribute($currentfeature,'osm_id')) = 0 
Then
"name"
else
''
end

For this to work it is necessary that your data has an (additional to name field) unique field, like an ID.

Explanation: array_agg() groups all the features in this layer by its name and fills an array with their ID. array_find() now looks for this ID of the current feature (like a loop for each feature) by using attribute($currentfeature, 'ID'). The feature only gets a label if it is the first one within this array =0.

Keep in mind this can lead to non-labeld stations when for example two bus stations have the same name but are 1000km apart.

2. Optimize label position

If you want to label it within the centroid of these grouped points you can use data defined positioning and enter the following code:

Case
When
area(bounds(transform(convex_hull(collect($geometry,group_by:="name")),layer_property(@layer_name, 'crs'), 'EPSG:3395'))) < 10000
Then
x(centroid(convex_hull(collect($geometry,group_by:="name"))))
Else
x($geometry)
End

Respectively replace x with y.... Explanation: OSM-Data is not consistent. It is possible that two bus stops, spread over 1000km apart do have the same name. To prevent labels from showing up in the middle of nowhere we will only merge stations within a convex hull area smaller than 10000m². Choose a fitting CRS for your area and replace EPSG:3395 with your choice.

enter image description here

3. Create different styles for different classes

To get different label styles for different classes, for example show a train station with a bus stop (both having the same name) larger than a bus stop only you can do the following: Use data defined overwrite for your style (in my example the font-size) and enter the following code:

coalesce(
Case When
array_contains(array_agg("name", group_by:="name", filter:="fclass" LIKE '%rail%'), attribute($currentfeature,'name'))
= true 
Then 33 Else NULL End,
Case When
array_contains(array_agg("name", group_by:="name", filter:="fclass" LIKE '%ferry%'), attribute($currentfeature,'name'))
= true 
Then 88 Else NULL End,
Case When
array_contains(array_agg("name", group_by:="name", filter:="fclass" LIKE '%tram%'), attribute($currentfeature,'name'))
= true 
Then 23 Else NULL End,
Case When
array_contains(array_agg("name", group_by:="name", filter:="fclass" LIKE '%bus%'), attribute($currentfeature,'name'))
= true 
Then 9 Else NULL End,
NULL
)

Keep in mind the order of this list; coalesce() will only return the first value which is not null.

Explanation: array_agg() generates an array with a filter on the class. So this array will only contain station names of e.g. bus stops. If the current feature name is within that filtered array it returns true. Since we already filtered features showing up above (label with field) this will have an effect to the only one remaining.

4. Adding Callout Lines

You can now also add callout lines to connect your centered label with the associated stops. Add a geometry generator-expression to your symbology and set it up as lines. Use this expression:

make_line(make_point($x,$y),make_point(x(centroid(convex_hull(collect($geometry,group_by:="name")))),y(centroid(convex_hull(collect($geometry,group_by:="name"))))))

5. Adding Center Symbol

In addition to the callout lines you can also add a center symbology. Add a geometry generator to your expression and set it up as point. Use this expression:

centroid(convex_hull(collect($geometry,group_by:="name")))

6. Your symbology setup now looks something like this:

enter image description here

7. Hints:

Be aware that all this will slow down rendering of your map. You might want only use parts of this answer or only use it at certain scales. Also, if editing your layer or data is an option, you might want to take a look at alternatives below.

8. Alternatives:

If you are ok with creating new layers or editing data check out this: Show only one label for multiple points with same value in QGIS

Related Question