qgis / QGIS

QGIS is a free, open source, cross platform (lin/win/mac) geographical information system (GIS)
https://qgis.org
GNU General Public License v2.0
10.62k stars 3.01k forks source link

Masking labels doesn't work with rule based labeling #46402

Open Forna73 opened 2 years ago

Forna73 commented 2 years ago

What is the bug or the crash?

Simple... I've any masking for labels even enabling the mask in label and selecting the masking options in layer properties

Steps to reproduce the issue

  1. create a line layer
  2. create a label and enable the masking option
  3. in the layer properties select both check box to enable the masking

Versions

Versione di QGIS 3.16.6-Hannover Revisione codice QGIS bfd36fddc9 Compilato con Qt 5.11.2 Esecuzione con Qt 5.11.2 Compilato con GDAL/OGR 3.1.4 Esecuzione con GDAL/OGR 3.1.4 Compilato con GEOS 3.8.1-CAPI-1.13.3 Esecuzione con GEOS 3.8.1-CAPI-1.13.3 Compilato su SQLite 3.29.0 In esecuzione su SQLite 3.29.0 Versione client PostgreSQL 11.5 Versione SpatiaLite 4.3.0 Versione QWT 6.1.3 Versione QScintilla2 2.10.8 Compilato con PROJ 6.3.2 Esecuzione con PROJ Rel. 6.3.2, May 1st, 2020 Versione SO Windows 10 (10.0) Attiva python plugin AzimuthDistanceCalculator; batch_hillshader-master; clipper; coordinates_converter; DigitizingTools; latlontools; map-coords-plugin; points2one; profiletool; Qgis2threejs; zoom_level; db_manager; MetaSearch; processing

Supported QGIS version

New profile

Additional context

No response

gioman commented 2 years ago

@Forna73 have you

"Select this mask shape as a mask source in the overlapping layer properties labelmask Mask tab (see Masks Properties)."

as explained here?

https://docs.qgis.org/3.22/en/docs/user_manual/style_library/label_settings.html?highlight=masking#mask-tab

gioman commented 2 years ago

When you do that:

image

Forna73 commented 2 years ago

I selcted them. I have only 1 layer, so it's the layer label that should mask its line, not an element of another layer.

Forna73 commented 2 years ago

I've just noticed that the check box doesn't remain selected. If I exit the layer properties panel and then I reopen it, the check boxes aren't selected. I tried "apply" maintaining the panel opened but nothing happens.

Forna73 commented 2 years ago

as you can see: 1 2

gioman commented 2 years ago

as you can see:

@Forna73 attach a sample project+data, the issue may be data/expression dependent.

Forna73 commented 2 years ago

it's done all from scratch. you can simply create a line layer and a label.....

gioman commented 2 years ago

it's done all from scratch. you can simply create a line layer and a label.....

@Forna73 in your screenshot you are using rule based labeling with an expression. With the most simple case ("you can simply create a line layer and a label") it works.

Forna73 commented 2 years ago

Ok.... You are right... simplest way works, I continued applying my rule; but this mean I can't use rule based labelling... is that expected to be right?

gioman commented 2 years ago

is that expected to be right?

@Forna73 please attach a minimal sample project with data that shows the behavior you describe.

Forna73 commented 2 years ago

Hi! In the meanwhile I was saving the file to attach I did some more tests and I discovered that only with "Rule based label" the mask doesn't work. With "single label", even with expression, it works. So the problem is apparently only with "rule based label".

gioman commented 2 years ago

So the problem is apparently only with "rule based label".

@Forna73 still works fine here. You really should attach a sample project with data that shows the issue.

gioman commented 2 years ago

@Forna73 nevermind, as soon as two mask sources are enabled masking stops to work. Thanks for the report.

gioman commented 2 years ago

The problem seems to be that when there are more than 1 masking sources then the selection for the sources and "masked symbol layer" do not stick in the layer properties, it seems that on ok/apply the selections are removed.

Forna73 commented 2 years ago

I do confirm!

hawstom commented 1 year ago

Any progress on this bug? I am experiencing it with v 3.28.8-Firenze. I am new to GIS and QGIS, but I can attach my minimal qgz file and layer file if you tell me what is the expected layer file format.

jfbourdon commented 8 months ago

The issue can be replicated with the snippet below. Up to QGIS 3.28.15, the masks works with a rule based labeling but it disappears if I open the layer properties window and click "OK". It doesn't matter if I change a setting or not, simply clicking "OK" does the job. However, clicking "Cancel" does not cause the mask to disappear. It must be noted that trying to set the same label parameters manually via the GUI does not allow to successfully apply the mask.

Starting at QGIS 3.30.0, my snippet breaks because of a change to the QgsSymbolLayerReference class. I haven't been able to figure out how to reference the symbol id in the new QgsSymbolLayerReference class...

# Make two simple lines
elev_fieldname = "ELEV"
layer = QgsVectorLayer(f"linestring?crs=epsg:32198&field={elev_fieldname}:integer", "contours", "memory")
for wkt, elev in [('LineString (0 400000, 100 400000)', 5), ('LineString (0 400010, 100 400010)', 10)]:
    geom = QgsGeometry.fromWkt(wkt)
    fet = QgsFeature(layer.fields())
    fet.setGeometry(geom)
    fet[elev_fieldname] = elev
    layer.dataProvider().addFeature(fet)

QgsProject.instance().addMapLayer(layer)

# Labeling
# Only the label of the upper line, with an elevation of 10, should be displayed
label_settings = QgsPalLayerSettings()
label_settings.placement = QgsPalLayerSettings.Line
label_settings.placementFlags = QgsPalLayerSettings.OnLine
label_settings.fieldName = elev_fieldname

mask = QgsTextMaskSettings()
mask.setEnabled(True)
mask.setSize(2)
mask.setMaskedSymbolLayers( [QgsSymbolLayerReference(layer.id(), QgsSymbolLayerId("", 0))] )  # Breaks starting at QGIS 3.30

text_format = label_settings.format()
text_format.setMask(mask)
label_settings.setFormat(text_format)

root = QgsRuleBasedLabeling.Rule(QgsPalLayerSettings())
rule = QgsRuleBasedLabeling.Rule(label_settings)
rule.setDescription("Only 10s")
rule.setFilterExpression(f"\"{elev_fieldname}\" % 10 = 0")
rule.setActive(True)
root.appendChild(rule)
rules_settings = QgsRuleBasedLabeling(root)
layer.setLabelsEnabled(True)
layer.setLabeling(rules_settings)

layer.labeling().setSettings(label_settings)
layer.triggerRepaint()
effjot commented 2 months ago

The problem still exists in the settings dialog in version 3.34.6 and 3.34.10. Also, it still is possible to turn on masking in the layer style dock.

jfbourdon commented 1 month ago

I'm trying to understand what exactly QgsSymbolLayerReference expects as a second argument now (since 3.30) in place of a QgsSymbolLayerId. The documentation says to use a QUuid string, but I can't find how to construct that string. I thought that there was a way to extract a uuid from a QgsSymbolLayerId object but that doesn't seem possible (and the QgsSymbolLayerId class is deprecated since 3.30). I'm at a loss...

@troopa81 Could you help on this matter as you seems to have edited the related lines of code related to these classes? Thanks.

troopa81 commented 1 month ago

Just discovering this issue

The problem seems to be that when there are more than 1 masking sources then the selection for the sources and "masked symbol layer" do not stick in the layer properties, it seems that on ok/apply the selections are removed.

You mean like this (2 mask sources, 1 masked symbol layer) ?

smaskedit

And it does work (in master but pretty sure it works with recent one)

smask

It would be very better if someone could share a project so I can reproduce the issue

troopa81 commented 1 month ago

I'm trying to understand what exactly QgsSymbolLayerReference expects as a second argument now (since 3.30) in place of a QgsSymbolLayerId

The unique symbol layer id. Like for instance

>>> iface.activeLayer().renderer()
<qgis._core.QgsSingleSymbolRenderer object at 0x7f7b5034c3a0>
>>> iface.activeLayer().renderer().symbol().symbolLayers()
[<qgis._core.QgsSimpleLineSymbolLayer object at 0x7f7b5044a290>, <qgis._core.QgsSimpleLineSymbolLayer object at 0x7f7b50331120>]
>>> iface.activeLayer().renderer().symbol().symbolLayers()[0].id()
'{6220c894-fcba-4f9f-86cf-d8884f4c49a5}'

The last one is the expected id. Before my modifications, it was the order of the symbol layer regarding its parent symbol and it was a mess (like for instance when you were changing the order). So now we have a generated unique id.

jfbourdon commented 1 month ago

Thanks!

My snippet below now works for >= 3.30. It displays two parallel lines with a label only on the upper one with a mask applied. The mask work, but like I said in my first comment, it stops working if the layer properties window is opened and then closed by clicking "OK", even without changing any setting.

Before opening the layer properties window image

After opening and closing the layer properties window image

# Make two simple lines
elev_fieldname = "ELEV"
layer = QgsVectorLayer(f"linestring?crs=epsg:32198&field={elev_fieldname}:integer", "contours", "memory")
for wkt, elev in [('LineString (0 400000, 100 400000)', 5), ('LineString (0 400010, 100 400010)', 10)]:
    geom = QgsGeometry.fromWkt(wkt)
    fet = QgsFeature(layer.fields())
    fet.setGeometry(geom)
    fet[elev_fieldname] = elev
    layer.dataProvider().addFeature(fet)

QgsProject.instance().addMapLayer(layer)

# Labeling
# Only the label of the upper line, with an elevation of 10, should be displayed
label_settings = QgsPalLayerSettings()
label_settings.placement = QgsPalLayerSettings.Line
label_settings.placementFlags = QgsPalLayerSettings.OnLine
label_settings.fieldName = elev_fieldname

mask = QgsTextMaskSettings()
mask.setEnabled(True)
mask.setSize(2)
mask.setMaskedSymbolLayers([
    QgsSymbolLayerReference(
        layer.id(),
        layer.renderer().symbol().symbolLayers()[0].id()
    )
])

text_format = label_settings.format()
text_format.setMask(mask)
label_settings.setFormat(text_format)

root = QgsRuleBasedLabeling.Rule(QgsPalLayerSettings())
rule = QgsRuleBasedLabeling.Rule(label_settings)
rule.setDescription("Only 10s")
rule.setFilterExpression(f"\"{elev_fieldname}\" % 10 = 0")
rule.setActive(True)
root.appendChild(rule)
rules_settings = QgsRuleBasedLabeling(root)
layer.setLabelsEnabled(True)
layer.setLabeling(rules_settings)

layer.labeling().setSettings(label_settings)
layer.triggerRepaint()
troopa81 commented 1 month ago

@jfbourdon OK, thanks! I I manage to reproduce the issue.

I'll fix the issue for next bugfix round (in january 2025) except if someone is willing to fund the fix