1"""
2***************************************************************************
3    test_qgssymbollayer_createsld.py
4    ---------------------
5    Date                 : July 2016
6    Copyright            : (C) 2016 by Andrea Aime
7    Email                : andrea dot aime at geosolutions dot it
8***************************************************************************
9*                                                                         *
10*   This program is free software; you can redistribute it and/or modify  *less
11*   it under the terms of the GNU General Public License as published by  *
12*   the Free Software Foundation; either version 2 of the License, or     *
13*   (at your option) any later version.                                   *
14*                                                                         *
15***************************************************************************
16"""
17
18__author__ = 'Andrea Aime'
19__date__ = 'July 2016'
20__copyright__ = '(C) 2012, Andrea Aime'
21
22import qgis  # NOQA
23
24from qgis.PyQt.QtCore import Qt, QDir, QFile, QIODevice, QPointF, QSizeF
25from qgis.PyQt.QtXml import QDomDocument
26from qgis.PyQt.QtGui import QColor, QFont
27
28from qgis.core import (
29    QgsSimpleMarkerSymbolLayer, QgsSimpleMarkerSymbolLayerBase, QgsUnitTypes, QgsSvgMarkerSymbolLayer,
30    QgsFontMarkerSymbolLayer, QgsEllipseSymbolLayer, QgsSimpleLineSymbolLayer,
31    QgsMarkerLineSymbolLayer, QgsMarkerSymbol, QgsSimpleFillSymbolLayer, QgsSVGFillSymbolLayer,
32    QgsLinePatternFillSymbolLayer, QgsPointPatternFillSymbolLayer, QgsVectorLayer, QgsVectorLayerSimpleLabeling,
33    QgsTextBufferSettings, QgsPalLayerSettings, QgsTextBackgroundSettings, QgsRuleBasedLabeling)
34from qgis.testing import start_app, unittest
35from utilities import unitTestDataPath
36
37# Convenience instances in case you may need them
38# not used in this test
39start_app()
40
41
42class TestQgsSymbolLayerCreateSld(unittest.TestCase):
43    """
44     This class tests the creation of SLD from QGis layers
45     """
46
47    def testSimpleMarkerRotation(self):
48        symbol = QgsSimpleMarkerSymbolLayer(
49            QgsSimpleMarkerSymbolLayerBase.Star, color=QColor(255, 0, 0), strokeColor=QColor(0, 255, 0), size=10)
50        symbol.setAngle(50)
51        dom, root = self.symbolToSld(symbol)
52        # print( "Simple marker rotation: " + root.ownerDocument().toString())
53
54        self.assertStaticRotation(root, '50')
55
56    def testSimpleMarkerUnitDefault(self):
57        symbol = QgsSimpleMarkerSymbolLayer(
58            QgsSimpleMarkerSymbolLayerBase.Star, color=QColor(255, 0, 0), strokeColor=QColor(0, 255, 0), size=10)
59        symbol.setStrokeWidth(3)
60        symbol.setOffset(QPointF(5, 10))
61        dom, root = self.symbolToSld(symbol)
62        # print("Simple marker unit mm: " + root.ownerDocument().toString())
63
64        # Check the size has been rescaled to pixels
65        self.assertStaticSize(root, '36')
66
67        # Check the same happened to the stroke width
68        self.assertStrokeWidth(root, 2, 11)
69        self.assertStaticDisplacement(root, 18, 36)
70
71    def testSimpleMarkerUnitPixels(self):
72        symbol = QgsSimpleMarkerSymbolLayer(
73            QgsSimpleMarkerSymbolLayerBase.Star, color=QColor(255, 0, 0), strokeColor=QColor(0, 255, 0), size=10)
74        symbol.setStrokeWidth(3)
75        symbol.setOffset(QPointF(5, 10))
76        symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
77        dom, root = self.symbolToSld(symbol)
78        # print("Marker unit mm: " + root.ownerDocument().toString())
79
80        # Check the size has not been rescaled
81        self.assertStaticSize(root, '10')
82
83        # Check the same happened to the stroke width
84        self.assertStrokeWidth(root, 2, 3)
85        self.assertStaticDisplacement(root, 5, 10)
86
87    def testSvgMarkerUnitDefault(self):
88        symbol = QgsSvgMarkerSymbolLayer('symbols/star.svg', 10, 90)
89        symbol.setFillColor(QColor("blue"))
90        symbol.setStrokeWidth(1)
91        symbol.setStrokeColor(QColor('red'))
92        symbol.setPath('symbols/star.svg')
93        symbol.setOffset(QPointF(5, 10))
94
95        dom, root = self.symbolToSld(symbol)
96        # print("Svg marker mm: " + dom.toString())
97
98        self.assertExternalGraphic(root, 0,
99                                   'symbols/star.svg?fill=%230000ff&fill-opacity=1&outline=%23ff0000&outline-opacity=1&outline-width=4',
100                                   'image/svg+xml')
101        self.assertExternalGraphic(root, 1,
102                                   'symbols/star.svg', 'image/svg+xml')
103        self.assertWellKnownMark(root, 0, 'square', '#0000ff', '#ff0000', 4)
104
105        # Check the size has been rescaled
106        self.assertStaticSize(root, '36')
107
108        # Check rotation for good measure
109        self.assertStaticRotation(root, '90')
110        self.assertStaticDisplacement(root, 18, 36)
111
112    def testSvgMarkerUnitPixels(self):
113        symbol = QgsSvgMarkerSymbolLayer('symbols/star.svg', 10, 0)
114        symbol.setFillColor(QColor("blue"))
115        symbol.setStrokeWidth(1)
116        symbol.setStrokeColor(QColor('red'))
117        symbol.setPath('symbols/star.svg')
118        symbol.setOffset(QPointF(5, 10))
119        symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
120        dom, root = self.symbolToSld(symbol)
121        # print("Svg marker unit px: " + dom.toString())
122
123        self.assertExternalGraphic(root, 0,
124                                   'symbols/star.svg?fill=%230000ff&fill-opacity=1&outline=%23ff0000&outline-opacity=1&outline-width=1',
125                                   'image/svg+xml')
126        self.assertExternalGraphic(root, 1,
127                                   'symbols/star.svg', 'image/svg+xml')
128        self.assertWellKnownMark(root, 0, 'square', '#0000ff', '#ff0000', 1)
129
130        # Check the size has not been rescaled
131        self.assertStaticSize(root, '10')
132        self.assertStaticDisplacement(root, 5, 10)
133
134    def testFontMarkerUnitDefault(self):
135        symbol = QgsFontMarkerSymbolLayer('sans', ',', 10, QColor('black'), 45)
136        symbol.setOffset(QPointF(5, 10))
137        dom, root = self.symbolToSld(symbol)
138        # print("Font marker unit mm: " + dom.toString())
139
140        # Check the size has been rescaled
141        self.assertStaticSize(root, '36')
142        self.assertStaticRotation(root, '45')
143        self.assertStaticDisplacement(root, 18, 36)
144
145    def testFontMarkerUnitPixel(self):
146        symbol = QgsFontMarkerSymbolLayer('sans', ',', 10, QColor('black'), 45)
147        symbol.setOffset(QPointF(5, 10))
148        symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
149        dom, root = self.symbolToSld(symbol)
150        # print ("Font marker unit mm: " + dom.toString())
151
152        # Check the size has been rescaled
153        self.assertStaticSize(root, '10')
154        self.assertStaticRotation(root, '45')
155        self.assertStaticDisplacement(root, 5, 10)
156
157    def createEllipseSymbolLayer(self):
158        # No way to build it programmatically...
159        mTestName = 'QgsEllipseSymbolLayer'
160        mFilePath = QDir.toNativeSeparators(
161            '%s/symbol_layer/%s.sld' % (unitTestDataPath(), mTestName))
162
163        mDoc = QDomDocument(mTestName)
164        mFile = QFile(mFilePath)
165        mFile.open(QIODevice.ReadOnly)
166        mDoc.setContent(mFile, True)
167        mFile.close()
168        mSymbolLayer = QgsEllipseSymbolLayer.createFromSld(
169            mDoc.elementsByTagName('PointSymbolizer').item(0).toElement())
170        return mSymbolLayer
171
172    def testEllipseMarkerUnitDefault(self):
173        symbol = self.createEllipseSymbolLayer()
174        symbol.setOffset(QPointF(5, 10))
175        symbol.setOutputUnit(QgsUnitTypes.RenderMillimeters)
176        dom, root = self.symbolToSld(symbol)
177        # print ("Ellipse marker unit mm: " + dom.toString())
178
179        # Check the size has been rescaled
180        self.assertStaticSize(root, '25')
181        # Check also the stroke width
182        self.assertStrokeWidth(root, 2, 4)
183        self.assertStaticDisplacement(root, 18, 36)
184
185    def testEllipseMarkerUnitPixel(self):
186        symbol = self.createEllipseSymbolLayer()
187        symbol.setOffset(QPointF(5, 10))
188        symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
189        dom, root = self.symbolToSld(symbol)
190        # print ("Ellipse marker unit mm: " + dom.toString())
191
192        # Check the size has been rescaled
193        self.assertStaticSize(root, '7')
194        # Check also the stroke width
195        self.assertStrokeWidth(root, 2, 1)
196        self.assertStaticDisplacement(root, 5, 10)
197
198    def testSimpleLineHairline(self):
199        symbol = QgsSimpleLineSymbolLayer(QColor("black"), 0)
200        dom, root = self.symbolToSld(symbol)
201
202        # print ("Simple line px: \n" + dom.toString())
203
204        # Hairline is turned into 0.5px
205        self.assertStrokeWidth(root, 1, 0.5)
206
207    def testSimpleLineUnitDefault(self):
208        symbol = QgsSimpleLineSymbolLayer(QColor("black"), 1)
209        symbol.setCustomDashVector([10, 10])
210        symbol.setUseCustomDashPattern(True)
211        symbol.setOffset(5)
212        dom, root = self.symbolToSld(symbol)
213
214        # print ("Simple line px: \n" + dom.toString())
215
216        self.assertStrokeWidth(root, 1, 4)
217        self.assertDashPattern(root, 4, '36 36')
218        self.assertStaticPerpendicularOffset(root, '18')
219
220    def testSimpleLineUnitPixel(self):
221        symbol = QgsSimpleLineSymbolLayer(QColor("black"), 1)
222        symbol.setCustomDashVector([10, 10])
223        symbol.setUseCustomDashPattern(True)
224        symbol.setOffset(5)
225        symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
226        dom, root = self.symbolToSld(symbol)
227
228        # print ("Simple line px: \n" + dom.toString())
229
230        self.assertStrokeWidth(root, 1, 1)
231        self.assertDashPattern(root, 4, '10 10')
232        self.assertStaticPerpendicularOffset(root, '5')
233
234    def testMarkLineUnitDefault(self):
235        symbol = QgsMarkerLineSymbolLayer()
236        symbol.setSubSymbol(
237            QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3'}))
238        symbol.setInterval(5)
239        symbol.setOffset(5)
240        dom, root = self.symbolToSld(symbol)
241
242        # print ("Mark line mm: \n" + dom.toString())
243
244        # size of the mark
245        self.assertStaticSize(root, '11')
246        # gap and offset
247        self.assertStaticGap(root, '18')
248        self.assertStaticPerpendicularOffset(root, '18')
249
250    def testMarkLineUnitPixels(self):
251        symbol = QgsMarkerLineSymbolLayer()
252        symbol.setSubSymbol(
253            QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3'}))
254        symbol.setInterval(5)
255        symbol.setOffset(5)
256        symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
257        dom, root = self.symbolToSld(symbol)
258
259        # print ("Mark line px: \n" + dom.toString())
260
261        # size of the mark
262        self.assertStaticSize(root, '3')
263        # gap and offset
264        self.assertStaticGap(root, '5')
265        self.assertStaticPerpendicularOffset(root, '5')
266
267    def testSimpleFillDefault(self):
268        symbol = QgsSimpleFillSymbolLayer(
269            QColor('red'), Qt.SolidPattern, QColor('green'), Qt.SolidLine, 5)
270        symbol.setOffset(QPointF(5, 10))
271
272        dom, root = self.symbolToSld(symbol)
273
274        # print ("Simple fill mm: \n" + dom.toString())
275
276        self.assertStrokeWidth(root, 2, 18)
277        self.assertStaticDisplacement(root, 18, 36)
278
279    def testSimpleFillPixels(self):
280        symbol = QgsSimpleFillSymbolLayer(
281            QColor('red'), Qt.SolidPattern, QColor('green'), Qt.SolidLine, 5)
282        symbol.setOffset(QPointF(5, 10))
283        symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
284
285        dom, root = self.symbolToSld(symbol)
286        # print ( "Simple fill px: \n" + dom.toString())
287
288        self.assertStrokeWidth(root, 2, 5)
289        self.assertStaticDisplacement(root, 5, 10)
290
291    def testSvgFillDefault(self):
292        symbol = QgsSVGFillSymbolLayer('test/star.svg', 10, 45)
293        symbol.setSvgFillColor(QColor('blue'))
294        symbol.setSvgStrokeWidth(3)
295        symbol.setSvgStrokeColor(QColor('yellow'))
296        symbol.subSymbol().setWidth(10)
297
298        dom, root = self.symbolToSld(symbol)
299        # print ("Svg fill mm: \n" + dom.toString())
300
301        self.assertExternalGraphic(root, 0,
302                                   'test/star.svg?fill=%230000ff&fill-opacity=1&outline=%23ffff00&outline-opacity=1&outline-width=11',
303                                   'image/svg+xml')
304        self.assertExternalGraphic(root, 1,
305                                   'test/star.svg', 'image/svg+xml')
306        self.assertWellKnownMark(root, 0, 'square', '#0000ff', '#ffff00', 11)
307
308        self.assertStaticRotation(root, '45')
309        self.assertStaticSize(root, '36')
310        # width of the polygon stroke
311        lineSymbolizer = root.elementsByTagName('se:LineSymbolizer').item(0).toElement()
312        self.assertStrokeWidth(lineSymbolizer, 1, 36)
313
314    def testSvgFillPixel(self):
315        symbol = QgsSVGFillSymbolLayer('test/star.svg', 10, 45)
316        symbol.setSvgFillColor(QColor('blue'))
317        symbol.setSvgStrokeWidth(3)
318        symbol.setSvgStrokeColor(QColor('black'))
319        symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
320        symbol.subSymbol().setWidth(10)
321
322        dom, root = self.symbolToSld(symbol)
323        # print ("Svg fill px: \n" + dom.toString())
324
325        self.assertExternalGraphic(root, 0,
326                                   'test/star.svg?fill=%230000ff&fill-opacity=1&outline=%23000000&outline-opacity=1&outline-width=3',
327                                   'image/svg+xml')
328        self.assertExternalGraphic(root, 1,
329                                   'test/star.svg', 'image/svg+xml')
330        self.assertWellKnownMark(root, 0, 'square', '#0000ff', '#000000', 3)
331
332        self.assertStaticRotation(root, '45')
333        self.assertStaticSize(root, '10')
334        # width of the polygon stroke
335        lineSymbolizer = root.elementsByTagName('se:LineSymbolizer').item(0).toElement()
336        self.assertStrokeWidth(lineSymbolizer, 1, 10)
337
338    def testLineFillDefault(self):
339        symbol = QgsLinePatternFillSymbolLayer()
340        symbol.setLineAngle(45)
341        symbol.setLineWidth(1)
342        symbol.setOffset(5)
343
344        dom, root = self.symbolToSld(symbol)
345        # print ("Line fill mm: \n" + dom.toString())
346
347        self.assertStaticRotation(root, '45')
348        self.assertStrokeWidth(root, 1, 4)
349        self.assertStaticSize(root, '18')
350        self.assertStaticDisplacement(root, 15, 9)
351
352    def testLineFillPixels(self):
353        symbol = QgsLinePatternFillSymbolLayer()
354        symbol.setLineAngle(45)
355        symbol.setLineWidth(1)
356        symbol.setOffset(5)
357        symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
358
359        dom, root = self.symbolToSld(symbol)
360        # print ("Line fill px: \n" + dom.toString())
361
362        self.assertStaticRotation(root, '45')
363        self.assertStrokeWidth(root, 1, 1)
364        self.assertStaticSize(root, '5')
365        self.assertStaticDisplacement(root, 4.25, 2.63)
366
367    def testPointFillDefault(self):
368        symbol = QgsPointPatternFillSymbolLayer()
369        dom, root = self.symbolToSld(symbol)
370        # print ("Point fill mm: \n" + dom.toString())
371
372        self.assertStaticSize(root, '7')
373
374    def testPointFillpixels(self):
375        symbol = QgsPointPatternFillSymbolLayer()
376        symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
377        dom, root = self.symbolToSld(symbol)
378        # print ("Point fill px: \n" + dom.toString())
379
380        self.assertStaticSize(root, '2')
381
382    def testSingleSymbolNoScaleDependencies(self):
383        layer = QgsVectorLayer("Point", "addfeat", "memory")
384        mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "singleSymbol"))
385        layer.loadNamedStyle(mFilePath)
386
387        dom, root = self.layerToSld(layer)
388        # print("No dep on single symbol:" + dom.toString())
389
390        self.assertScaleDenominator(root, None, None)
391
392    def testSingleSymbolScaleDependencies(self):
393        layer = QgsVectorLayer("Point", "addfeat", "memory")
394        mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "singleSymbol"))
395        layer.loadNamedStyle(mFilePath)
396        layer.setMaximumScale(1000)
397        layer.setMinimumScale(500000)
398        layer.setScaleBasedVisibility(True)
399
400        dom, root = self.layerToSld(layer)
401        # print("Scale dep on single symbol:" + dom.toString())
402
403        self.assertScaleDenominator(root, '1000', '500000')
404
405    def testCategorizedNoScaleDependencies(self):
406        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
407        mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "categorized"))
408        layer.loadNamedStyle(mFilePath)
409
410        dom, root = self.layerToSld(layer)
411        # print("Categorized no scale deps:" + dom.toString())
412
413        ruleCount = root.elementsByTagName('se:Rule').size()
414        for i in range(0, ruleCount):
415            self.assertScaleDenominator(root, None, None, i)
416
417    def testCategorizedWithScaleDependencies(self):
418        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
419        mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "categorized"))
420        layer.loadNamedStyle(mFilePath)
421        layer.setMaximumScale(1000)
422        layer.setMinimumScale(500000)
423        layer.setScaleBasedVisibility(True)
424
425        dom, root = self.layerToSld(layer)
426        # print("Categorized with scale deps:" + dom.toString())
427
428        ruleCount = root.elementsByTagName('se:Rule').size()
429        for i in range(0, ruleCount):
430            self.assertScaleDenominator(root, '1000', '500000', i)
431
432    def testGraduatedNoScaleDependencies(self):
433        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
434
435        mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "graduated"))
436        status = layer.loadNamedStyle(mFilePath)  # NOQA
437
438        dom, root = self.layerToSld(layer)
439        # print("Graduated no scale deps:" + dom.toString())
440
441        ruleCount = root.elementsByTagName('se:Rule').size()
442        for i in range(0, ruleCount):
443            self.assertScaleDenominator(root, None, None, i)
444
445    #    def testRuleBasedNoRootScaleDependencies(self):
446    #        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
447    #
448    #        mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "ruleBased"))
449    #        status = layer.loadNamedStyle(mFilePath)  # NOQA
450    #
451    #        dom, root = self.layerToSld(layer)
452    #        print(("Rule based, no root scale deps:" + dom.toString()))
453    #
454    #        ruleCount = root.elementsByTagName('se:Rule').size()  # NOQA
455    #        self.assertScaleDenominator(root, '1000', '40000000', 0)
456    #        self.assertScaleDenominator(root, None, None, 1)
457
458    def testRuleBasedNoRootScaleDependencies(self):
459        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
460
461        mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "ruleBased"))
462        status = layer.loadNamedStyle(mFilePath)  # NOQA
463        layer.setMaximumScale(5000)
464        layer.setMinimumScale(50000000)
465        layer.setScaleBasedVisibility(True)
466
467        dom, root = self.layerToSld(layer)
468        # print("Rule based, with root scale deps:" + dom.toString())
469
470        ruleCount = root.elementsByTagName('se:Rule').size()  # NOQA
471        self.assertScaleDenominator(root, '5000', '40000000', 0)
472        self.assertScaleDenominator(root, '5000', '50000000', 1)
473
474    def testCategorizedFunctionConflict(self):
475        layer = QgsVectorLayer("Point", "addfeat", "memory")
476
477        mFilePath = QDir.toNativeSeparators(
478            '%s/symbol_layer/%s.qml' % (unitTestDataPath(), "categorizedFunctionConflict"))
479        status = layer.loadNamedStyle(mFilePath)  # NOQA
480
481        dom, root = self.layerToSld(layer)
482        # print("Rule based, with root scale deps:" + dom.toString())
483
484        ruleCount = root.elementsByTagName('se:Rule').size()  # NOQA
485        self.assertEqual(7, ruleCount)
486        self.assertRuleRangeFilter(root, 0, 'Area', '0', True, '500', True)
487        self.assertRuleRangeFilter(root, 1, 'Area', '500', False, '1000', True)
488        self.assertRuleRangeFilter(root, 2, 'Area', '1000', False, '5000', True)
489        self.assertRuleRangeFilter(root, 3, 'Area', '5000', False, '10000', True)
490        self.assertRuleRangeFilter(root, 4, 'Area', '10000', False, '50000', True)
491        self.assertRuleRangeFilter(root, 5, 'Area', '50000', False, '100000', True)
492        self.assertRuleRangeFilter(root, 6, 'Area', '100000', False, '200000', True)
493
494    def assertRuleRangeFilter(self, root, index, attributeName, min, includeMin, max, includeMax):
495        rule = root.elementsByTagName('se:Rule').item(index).toElement()
496        filter = rule.elementsByTagName("Filter").item(0).firstChild()
497        self.assertEqual("ogc:And", filter.nodeName())
498
499        gt = filter.firstChild()
500        expectedGtName = "ogc:PropertyIsGreaterThanOrEqualTo" if includeMin else "ogc:PropertyIsGreaterThan"
501        self.assertEqual(expectedGtName, gt.nodeName())
502        gtProperty = gt.firstChild()
503        self.assertEqual("ogc:PropertyName", gtProperty.nodeName())
504        self.assertEqual(attributeName, gtProperty.toElement().text())
505        gtValue = gt.childNodes().item(1)
506        self.assertEqual(min, gtValue.toElement().text())
507
508        lt = filter.childNodes().item(1)
509        expectedLtName = "ogc:PropertyIsLessThanOrEqualTo" if includeMax else "ogc:PropertyIsLessThan"
510        self.assertEqual(expectedLtName, lt.nodeName())
511        ltProperty = lt.firstChild()
512        self.assertEqual("ogc:PropertyName", ltProperty.nodeName())
513        self.assertEqual(attributeName, ltProperty.toElement().text())
514        ltValue = lt.childNodes().item(1)
515        self.assertEqual(max, ltValue.toElement().text())
516
517    def testSimpleLabeling(self):
518        layer = QgsVectorLayer("Point", "addfeat", "memory")
519        self.loadStyleWithCustomProperties(layer, "simpleLabel")
520        # Pick a local default font
521        fontFamily = QFont().family()
522        settings = layer.labeling().settings()
523        format = settings.format()
524        font = format.font()
525        font.setFamily(fontFamily)
526        font.setBold(False)
527        font.setItalic(False)
528        format.setFont(font)
529        settings.setFormat(format)
530        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
531
532        dom, root = self.layerToSld(layer)
533        # print("Simple label text symbolizer" + dom.toString())
534
535        ts = self.getTextSymbolizer(root, 1, 0)
536        self.assertPropertyName(ts, 'se:Label', 'NAME')
537        font = self.assertElement(ts, 'se:Font', 0)
538        self.assertEqual(fontFamily, self.assertSvgParameter(font, 'font-family').text())
539        self.assertEqual('11', self.assertSvgParameter(font, 'font-size').text())
540
541        fill = self.assertElement(ts, 'se:Fill', 0)
542        self.assertEqual('#000000', self.assertSvgParameter(fill, "fill").text())
543        self.assertIsNone(self.assertSvgParameter(fill, "fill-opacity", True))
544
545    def testLabelingUomMillimeter(self):
546        layer = QgsVectorLayer("Point", "addfeat", "memory")
547        self.loadStyleWithCustomProperties(layer, "simpleLabel")
548        self.updateLayerLabelingUnit(layer, QgsUnitTypes.RenderMillimeters)
549
550        dom, root = self.layerToSld(layer)
551        # print("Label sized in mm " + dom.toString())
552
553        ts = self.getTextSymbolizer(root, 1, 0)
554        font = self.assertElement(ts, 'se:Font', 0)
555        self.assertEqual('32', self.assertSvgParameter(font, 'font-size').text())
556
557    def testLabelingUomPixels(self):
558        layer = QgsVectorLayer("Point", "addfeat", "memory")
559        self.loadStyleWithCustomProperties(layer, "simpleLabel")
560        self.updateLayerLabelingUnit(layer, QgsUnitTypes.RenderPixels)
561
562        dom, root = self.layerToSld(layer)
563        # print("Label sized in pixels " + dom.toString())
564
565        ts = self.getTextSymbolizer(root, 1, 0)
566        font = self.assertElement(ts, 'se:Font', 0)
567        self.assertEqual('9', self.assertSvgParameter(font, 'font-size').text())
568
569    def testLabelingUomInches(self):
570        layer = QgsVectorLayer("Point", "addfeat", "memory")
571        self.loadStyleWithCustomProperties(layer, "simpleLabel")
572        self.updateLayerLabelingUnit(layer, QgsUnitTypes.RenderInches)
573
574        dom, root = self.layerToSld(layer)
575        # print("Label sized in inches " + dom.toString())
576
577        ts = self.getTextSymbolizer(root, 1, 0)
578        font = self.assertElement(ts, 'se:Font', 0)
579        self.assertEqual('816', self.assertSvgParameter(font, 'font-size').text())
580
581    def testTextStyle(self):
582        layer = QgsVectorLayer("Point", "addfeat", "memory")
583        self.loadStyleWithCustomProperties(layer, "simpleLabel")
584
585        # testing regular
586        self.updateLayerLabelingFontStyle(layer, False, False)
587        dom, root = self.layerToSld(layer)
588        # print("Simple label italic text" + dom.toString())
589        ts = self.getTextSymbolizer(root, 1, 0)
590        font = self.assertElement(ts, 'se:Font', 0)
591        self.assertIsNone(self.assertSvgParameter(font, 'font-weight', True))
592        self.assertIsNone(self.assertSvgParameter(font, 'font-style', True))
593
594        # testing bold
595        self.updateLayerLabelingFontStyle(layer, True, False)
596        dom, root = self.layerToSld(layer)
597        # print("Simple label bold text" + dom.toString())
598        ts = self.getTextSymbolizer(root, 1, 0)
599        font = self.assertElement(ts, 'se:Font', 0)
600        self.assertEqual('bold', self.assertSvgParameter(font, 'font-weight').text())
601        self.assertIsNone(self.assertSvgParameter(font, 'font-style', True))
602
603        # testing italic
604        self.updateLayerLabelingFontStyle(layer, False, True)
605        dom, root = self.layerToSld(layer)
606        # print("Simple label italic text" + dom.toString())
607        ts = self.getTextSymbolizer(root, 1, 0)
608        font = self.assertElement(ts, 'se:Font', 0)
609        self.assertEqual('italic', self.assertSvgParameter(font, 'font-style').text())
610        self.assertIsNone(self.assertSvgParameter(font, 'font-weight', True))
611
612        # testing bold italic
613        self.updateLayerLabelingFontStyle(layer, True, True)
614        dom, root = self.layerToSld(layer)
615        # print("Simple label bold and italic text" + dom.toString())
616        ts = self.getTextSymbolizer(root, 1, 0)
617        font = self.assertElement(ts, 'se:Font', 0)
618        self.assertEqual('italic', self.assertSvgParameter(font, 'font-style').text())
619        self.assertEqual('bold', self.assertSvgParameter(font, 'font-weight').text())
620
621        # testing underline and strikethrough vendor options
622        self.updateLayerLabelingFontStyle(layer, False, False, True, True)
623        dom, root = self.layerToSld(layer)
624        # print("Simple label underline and strikethrough text" + dom.toString())
625        ts = self.getTextSymbolizer(root, 1, 0)
626        font = self.assertElement(ts, 'se:Font', 0)
627        self.assertEqual('true', self.assertVendorOption(ts, 'underlineText').text())
628        self.assertEqual('true', self.assertVendorOption(ts, 'strikethroughText').text())
629
630    def testTextMixedCase(self):
631        self.assertCapitalizationFunction(QFont.MixedCase, None)
632
633    def testTextUppercase(self):
634        self.assertCapitalizationFunction(QFont.AllUppercase, "strToUpperCase")
635
636    def testTextLowercase(self):
637        self.assertCapitalizationFunction(QFont.AllLowercase, "strToLowerCase")
638
639    def testTextCapitalcase(self):
640        self.assertCapitalizationFunction(QFont.Capitalize, "strCapitalize")
641
642    def assertCapitalizationFunction(self, capitalization, expectedFunction):
643        layer = QgsVectorLayer("Point", "addfeat", "memory")
644        self.loadStyleWithCustomProperties(layer, "simpleLabel")
645
646        settings = layer.labeling().settings()
647        format = settings.format()
648        font = format.font()
649        font.setCapitalization(capitalization)
650        format.setFont(font)
651        settings.setFormat(format)
652        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
653
654        dom, root = self.layerToSld(layer)
655        # print("Simple text with capitalization " + str(QFont.AllUppercase) + ": " + dom.toString())
656        ts = self.getTextSymbolizer(root, 1, 0)
657        label = self.assertElement(ts, "se:Label", 0)
658        if expectedFunction is None:
659            property = self.assertElement(label, "ogc:PropertyName", 0)
660            self.assertEqual("NAME", property.text())
661        else:
662            function = self.assertElement(label, "ogc:Function", 0)
663            self.assertEqual(expectedFunction, function.attribute("name"))
664            property = self.assertElement(function, "ogc:PropertyName", 0)
665            self.assertEqual("NAME", property.text())
666
667    def testLabelingTransparency(self):
668        layer = QgsVectorLayer("Point", "addfeat", "memory")
669        self.loadStyleWithCustomProperties(layer, "simpleLabel")
670        settings = layer.labeling().settings()
671        format = settings.format()
672        format.setOpacity(0.5)
673        settings.setFormat(format)
674        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
675
676        dom, root = self.layerToSld(layer)
677        # print("Label with transparency  " + dom.toString())
678
679        ts = self.getTextSymbolizer(root, 1, 0)
680        fill = self.assertElement(ts, 'se:Fill', 0)
681        self.assertEqual('#000000', self.assertSvgParameter(fill, "fill").text())
682        self.assertEqual('0.5', self.assertSvgParameter(fill, "fill-opacity").text())
683
684    def testLabelingBuffer(self):
685        layer = QgsVectorLayer("Point", "addfeat", "memory")
686        self.loadStyleWithCustomProperties(layer, "simpleLabel")
687        buffer = QgsTextBufferSettings()
688        buffer.setEnabled(True)
689        buffer.setSize(10)
690        buffer.setSizeUnit(QgsUnitTypes.RenderPixels)
691        buffer.setColor(QColor("Black"))
692        self.setLabelBufferSettings(layer, buffer)
693
694        dom, root = self.layerToSld(layer)
695        # print("Label with buffer 10 px  " + dom.toString())
696
697        ts = self.getTextSymbolizer(root, 1, 0)
698        halo = self.assertElement(ts, 'se:Halo', 0)
699        # not full width, just radius here
700        self.assertEqual('5', self.assertElement(ts, 'se:Radius', 0).text())
701        haloFill = self.assertElement(halo, 'se:Fill', 0)
702        self.assertEqual('#000000', self.assertSvgParameter(haloFill, "fill").text())
703
704    def testLabelingBufferPointTranslucent(self):
705        layer = QgsVectorLayer("Point", "addfeat", "memory")
706        self.loadStyleWithCustomProperties(layer, "simpleLabel")
707        buffer = QgsTextBufferSettings()
708        buffer.setEnabled(True)
709        buffer.setSize(10)
710        buffer.setSizeUnit(QgsUnitTypes.RenderPoints)
711        buffer.setColor(QColor("Red"))
712        buffer.setOpacity(0.5)
713        self.setLabelBufferSettings(layer, buffer)
714
715        dom, root = self.layerToSld(layer)
716        # print("Label with buffer 10 points, red 50% transparent  " + dom.toString())
717
718        ts = self.getTextSymbolizer(root, 1, 0)
719        halo = self.assertElement(ts, 'se:Halo', 0)
720        # not full width, just radius here
721        self.assertEqual('6.5', self.assertElement(ts, 'se:Radius', 0).text())
722        haloFill = self.assertElement(halo, 'se:Fill', 0)
723        self.assertEqual('#ff0000', self.assertSvgParameter(haloFill, "fill").text())
724        self.assertEqual('0.5', self.assertSvgParameter(haloFill, "fill-opacity").text())
725
726    def testLabelingLowPriority(self):
727        self.assertLabelingPriority(0, 0, '0')
728
729    def testLabelingDefaultPriority(self):
730        self.assertLabelingPriority(0, 5, None)
731
732    def testLabelingHighPriority(self):
733        self.assertLabelingPriority(0, 10, '1000')
734
735    def testLabelingZIndexLowPriority(self):
736        self.assertLabelingPriority(1, 0, '1001')
737
738    def testLabelingZIndexDefaultPriority(self):
739        self.assertLabelingPriority(1, 5, "1500")
740
741    def testLabelingZIndexHighPriority(self):
742        self.assertLabelingPriority(1, 10, '2000')
743
744    def assertLabelingPriority(self, zIndex, priority, expectedSldPriority):
745        layer = QgsVectorLayer("Point", "addfeat", "memory")
746        self.loadStyleWithCustomProperties(layer, "simpleLabel")
747        settings = layer.labeling().settings()
748        settings.zIndex = zIndex
749        settings.priority = priority
750        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
751
752        dom, root = self.layerToSld(layer)
753        # print("Label with zIndex at " + str(zIndex) + " and priority at " + str(priority) + ": " + dom.toString())
754
755        ts = self.getTextSymbolizer(root, 1, 0)
756        priorityElement = self.assertElement(ts, "se:Priority", 0, True)
757        if expectedSldPriority is None:
758            self.assertIsNone(priorityElement)
759        else:
760            self.assertEqual(expectedSldPriority, priorityElement.text())
761
762    def testLabelingPlacementOverPointOffsetRotation(self):
763        layer = QgsVectorLayer("Point", "addfeat", "memory")
764        self.loadStyleWithCustomProperties(layer, "simpleLabel")
765        settings = layer.labeling().settings()
766        settings.placement = QgsPalLayerSettings.OverPoint
767        settings.xOffset = 5
768        settings.yOffset = 10
769        settings.offsetUnits = QgsUnitTypes.RenderMillimeters
770        settings.quadOffset = QgsPalLayerSettings.QuadrantOver
771        settings.angleOffset = 30
772        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
773
774        dom, root = self.layerToSld(layer)
775        # print("Label with 'over point' placement  " + dom.toString())
776
777        ts = self.getTextSymbolizer(root, 1, 0)
778        pointPlacement = self.assertPointPlacement(ts)
779        self.assertStaticDisplacement(pointPlacement, 18, 36)
780        self.assertStaticAnchorPoint(pointPlacement, 0.5, 0.5)
781
782    def testPointPlacementAboveLeft(self):
783        self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantAboveLeft, "AboveLeft", 1, 0)
784
785    def testPointPlacementAbove(self):
786        self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantAbove, "Above", 0.5, 0)
787
788    def testPointPlacementAboveRight(self):
789        self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantAboveRight, "AboveRight", 0, 0)
790
791    def testPointPlacementLeft(self):
792        self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantLeft, "Left", 1, 0.5)
793
794    def testPointPlacementRight(self):
795        self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantRight, "Right", 0, 0.5)
796
797    def testPointPlacementBelowLeft(self):
798        self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantBelowLeft, "BelowLeft", 1, 1)
799
800    def testPointPlacementBelow(self):
801        self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantBelow, "Below", 0.5, 1)
802
803    def testPointPlacementAboveRight(self):
804        self.assertLabelQuadrant(QgsPalLayerSettings.QuadrantBelowRight, "BelowRight", 0, 1)
805
806    def testPointPlacementCartoraphic(self):
807        self.assertPointPlacementDistance(QgsPalLayerSettings.OrderedPositionsAroundPoint)
808
809    def testPointPlacementCartoraphic(self):
810        self.assertPointPlacementDistance(QgsPalLayerSettings.AroundPoint)
811
812    def testLineParallelPlacement(self):
813        layer = QgsVectorLayer("LineString", "addfeat", "memory")
814        self.loadStyleWithCustomProperties(layer, "lineLabel")
815
816        dom, root = self.layerToSld(layer)
817        # print("Label with parallel line placement  " + dom.toString())
818        linePlacement = self.assertLinePlacement(root)
819        generalize = self.assertElement(linePlacement, 'se:GeneralizeLine', 0)
820        self.assertEqual("true", generalize.text())
821
822    def testLineParallelPlacementOffsetRepeat(self):
823        layer = QgsVectorLayer("LineString", "addfeat", "memory")
824        self.loadStyleWithCustomProperties(layer, "lineLabel")
825        self.updateLinePlacementProperties(layer, QgsPalLayerSettings.Line, 2, 50)
826
827        dom, root = self.layerToSld(layer)
828        # print("Label with parallel line placement, perp. offset and repeat  " + dom.toString())
829        ts = self.getTextSymbolizer(root, 1, 0)
830        linePlacement = self.assertLinePlacement(ts)
831        generalize = self.assertElement(linePlacement, 'se:GeneralizeLine', 0)
832        self.assertEqual("true", generalize.text())
833        offset = self.assertElement(linePlacement, 'se:PerpendicularOffset', 0)
834        self.assertEqual("7", offset.text())
835        repeat = self.assertElement(linePlacement, 'se:Repeat', 0)
836        self.assertEqual("true", repeat.text())
837        gap = self.assertElement(linePlacement, 'se:Gap', 0)
838        self.assertEqual("179", gap.text())
839        self.assertEqual("179", self.assertVendorOption(ts, "repeat").text())
840
841    def testLineCurvePlacementOffsetRepeat(self):
842        layer = QgsVectorLayer("LineString", "addfeat", "memory")
843        self.loadStyleWithCustomProperties(layer, "lineLabel")
844        self.updateLinePlacementProperties(layer, QgsPalLayerSettings.Curved, 2, 50, 30, 40)
845
846        dom, root = self.layerToSld(layer)
847        # print("Label with curved line placement  " + dom.toString())
848
849        ts = self.getTextSymbolizer(root, 1, 0)
850        linePlacement = self.assertLinePlacement(ts)
851        generalize = self.assertElement(linePlacement, 'se:GeneralizeLine', 0)
852        self.assertEqual("true", generalize.text())
853        offset = self.assertElement(linePlacement, 'se:PerpendicularOffset', 0)
854        self.assertEqual("7", offset.text())
855        repeat = self.assertElement(linePlacement, 'se:Repeat', 0)
856        self.assertEqual("true", repeat.text())
857        gap = self.assertElement(linePlacement, 'se:Gap', 0)
858        self.assertEqual("179", gap.text())
859        self.assertEqual("179", self.assertVendorOption(ts, "repeat").text())
860        self.assertEqual("true", self.assertVendorOption(ts, "followLine").text())
861        self.assertEqual("30", self.assertVendorOption(ts, "maxAngleDelta").text())
862
863    def testLineCurveMergeLines(self):
864        layer = QgsVectorLayer("LineString", "addfeat", "memory")
865        self.loadStyleWithCustomProperties(layer, "lineLabel")
866        settings = layer.labeling().settings()
867        settings.placement = QgsPalLayerSettings.Curved
868        settings.mergeLines = True
869        settings.labelPerPart = True
870        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
871
872        dom, root = self.layerToSld(layer)
873        # print("Label with curved line and line grouping  " + dom.toString())
874
875        ts = self.getTextSymbolizer(root, 1, 0)
876        self.assertEqual("yes", self.assertVendorOption(ts, "group").text())
877        self.assertEqual("true", self.assertVendorOption(ts, "labelAllGroup").text())
878
879    def testLabelingPolygonFree(self):
880        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
881        self.loadStyleWithCustomProperties(layer, "polygonLabel")
882        settings = layer.labeling().settings()
883        settings.placement = QgsPalLayerSettings.Free
884        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
885
886        dom, root = self.layerToSld(layer)
887        # print("Polygon label with 'Free' placement  " + dom.toString())
888
889        ts = self.getTextSymbolizer(root, 1, 0)
890        pointPlacement = self.assertPointPlacement(ts)
891        self.assertIsNone(self.assertElement(ts, "se:Displacement", 0, True))
892        self.assertStaticAnchorPoint(pointPlacement, 0.5, 0.5)
893
894    def testLabelingPolygonPerimeterCurved(self):
895        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
896        self.loadStyleWithCustomProperties(layer, "polygonLabel")
897        self.updateLinePlacementProperties(layer, QgsPalLayerSettings.PerimeterCurved, 2, 50, 30, -40)
898
899        dom, root = self.layerToSld(layer)
900        # print("Polygon Label with curved perimeter line placement  " + dom.toString())
901
902        ts = self.getTextSymbolizer(root, 1, 0)
903        linePlacement = self.assertLinePlacement(ts)
904        generalize = self.assertElement(linePlacement, 'se:GeneralizeLine', 0)
905        self.assertEqual("true", generalize.text())
906        offset = self.assertElement(linePlacement, 'se:PerpendicularOffset', 0)
907        self.assertEqual("7", offset.text())
908        repeat = self.assertElement(linePlacement, 'se:Repeat', 0)
909        self.assertEqual("true", repeat.text())
910        gap = self.assertElement(linePlacement, 'se:Gap', 0)
911        self.assertEqual("179", gap.text())
912        self.assertEqual("179", self.assertVendorOption(ts, "repeat").text())
913        self.assertEqual("true", self.assertVendorOption(ts, "followLine").text())
914        self.assertEqual("30", self.assertVendorOption(ts, "maxAngleDelta").text())
915
916    def testLabelScaleDependencies(self):
917        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
918        self.loadStyleWithCustomProperties(layer, "polygonLabel")
919        settings = layer.labeling().settings()
920        settings.scaleVisibility = True
921        # Careful: min scale -> large scale denomin
922        settings.minimumScale = 10000000
923        settings.maximumScale = 1000000
924        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
925
926        dom, root = self.layerToSld(layer)
927        # print("Labeling with scale dependencies  " + dom.toString())
928        self.assertScaleDenominator(root, "1000000", "10000000", 1)
929
930    def testLabelShowAll(self):
931        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
932        self.loadStyleWithCustomProperties(layer, "polygonLabel")
933        settings = layer.labeling().settings()
934        settings.displayAll = True
935        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
936
937        dom, root = self.layerToSld(layer)
938        # print("Labeling, showing all labels  " + dom.toString())
939
940        ts = self.getTextSymbolizer(root, 1, 0)
941        self.assertVendorOption(ts, "conflictResolution", "false")
942
943    def testLabelUpsideDown(self):
944        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
945        self.loadStyleWithCustomProperties(layer, "polygonLabel")
946        settings = layer.labeling().settings()
947        settings.upsidedownLabels = QgsPalLayerSettings.ShowAll
948        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
949
950        dom, root = self.layerToSld(layer)
951        # print("Labeling, showing upside down labels on lines  " + dom.toString())
952
953        ts = self.getTextSymbolizer(root, 1, 0)
954        self.assertVendorOption(ts, "forceLeftToRight", "false")
955
956    def testLabelBackgroundSquareResize(self):
957        self.assertLabelBackground(QgsTextBackgroundSettings.ShapeSquare, 'square',
958                                   QgsTextBackgroundSettings.SizeBuffer, 'proportional')
959
960    def testLabelBackgroundRectangleResize(self):
961        self.assertLabelBackground(QgsTextBackgroundSettings.ShapeRectangle, 'square',
962                                   QgsTextBackgroundSettings.SizeBuffer, 'stretch')
963
964    def testLabelBackgroundCircleResize(self):
965        self.assertLabelBackground(QgsTextBackgroundSettings.ShapeCircle, 'circle',
966                                   QgsTextBackgroundSettings.SizeBuffer, 'proportional')
967
968    def testLabelBackgroundEllipseResize(self):
969        self.assertLabelBackground(QgsTextBackgroundSettings.ShapeEllipse, 'circle',
970                                   QgsTextBackgroundSettings.SizeBuffer, 'stretch')
971
972    def testLabelBackgroundSquareAbsolute(self):
973        self.assertLabelBackground(QgsTextBackgroundSettings.ShapeSquare, 'square',
974                                   QgsTextBackgroundSettings.SizeFixed, None)
975
976    def testLabelBackgroundRectangleAbsolute(self):
977        self.assertLabelBackground(QgsTextBackgroundSettings.ShapeRectangle, 'square',
978                                   QgsTextBackgroundSettings.SizeFixed, None)
979
980    def testLabelBackgroundCircleAbsolute(self):
981        self.assertLabelBackground(QgsTextBackgroundSettings.ShapeCircle, 'circle',
982                                   QgsTextBackgroundSettings.SizeFixed, None)
983
984    def testLabelBackgroundEllipseAbsolute(self):
985        self.assertLabelBackground(QgsTextBackgroundSettings.ShapeEllipse, 'circle',
986                                   QgsTextBackgroundSettings.SizeFixed, None)
987
988    def assertLabelBackground(self, backgroundType, expectedMarkName, sizeType, expectedResize):
989        layer = QgsVectorLayer("Polygon", "addfeat", "memory")
990        self.loadStyleWithCustomProperties(layer, "polygonLabel")
991        settings = layer.labeling().settings()
992        background = QgsTextBackgroundSettings()
993        background.setEnabled(True)
994        background.setType(backgroundType)
995        background.setFillColor(QColor('yellow'))
996        background.setStrokeColor(QColor('black'))
997        background.setStrokeWidth(2)
998        background.setSize(QSizeF(10, 10))
999        background.setSizeType(sizeType)
1000        format = settings.format()
1001        format.setBackground(background)
1002        settings.setFormat(format)
1003        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
1004
1005        dom, root = self.layerToSld(layer)
1006        # print("Labeling, with background type " + str(backgroundType) + " and size type " + str(sizeType) + ": " + dom.toString())
1007
1008        ts = self.getTextSymbolizer(root, 1, 0)
1009        graphic = self.assertElement(ts, "se:Graphic", 0)
1010        self.assertEqual("36", self.assertElement(graphic, 'se:Size', 0).text())
1011        self.assertWellKnownMark(graphic, 0, expectedMarkName, '#ffff00', '#000000', 7)
1012        if expectedResize is None:
1013            self.assertIsNone(expectedResize, self.assertVendorOption(ts, 'graphic-resize', True))
1014        else:
1015            self.assertEqual(expectedResize, self.assertVendorOption(ts, 'graphic-resize').text())
1016        if sizeType == 0:
1017            # check extra padding for proportional ellipse
1018            if backgroundType == QgsTextBackgroundSettings.ShapeEllipse:
1019                self.assertEqual("42.5 49", self.assertVendorOption(ts, 'graphic-margin').text())
1020            else:
1021                self.assertEqual("36 36", self.assertVendorOption(ts, 'graphic-margin').text())
1022        else:
1023            self.assertIsNone(self.assertVendorOption(ts, 'graphic-margin', True))
1024
1025    def testRuleBasedLabels(self):
1026        layer = QgsVectorLayer("Point", "addfeat", "memory")
1027        self.loadStyleWithCustomProperties(layer, "ruleLabel")
1028
1029        dom, root = self.layerToSld(layer)
1030        # print("Rule based labeling: " + dom.toString())
1031
1032        # three rules, one with the point symbol, one with the first rule based label,
1033        # one with the second rule based label
1034        rule1 = self.getRule(root, 0)
1035        self.assertElement(rule1, 'se:PointSymbolizer', 0)
1036
1037        rule2 = self.getRule(root, 1)
1038        self.assertScaleDenominator(root, '100000', '10000000', 1)
1039        tsRule2 = self.assertElement(rule2, 'se:TextSymbolizer', 0)
1040        gt = rule2.elementsByTagName("Filter").item(0).firstChild()
1041        self.assertEqual("ogc:PropertyIsGreaterThan", gt.nodeName())
1042        gtProperty = gt.toElement().firstChild()
1043        self.assertEqual("ogc:PropertyName", gtProperty.nodeName())
1044        self.assertEqual("POP_MAX", gtProperty.toElement().text())
1045        gtValue = gt.childNodes().item(1)
1046        self.assertEqual("1000000", gtValue.toElement().text())
1047
1048        rule3 = self.getRule(root, 2)
1049        tsRule3 = self.assertElement(rule3, 'se:TextSymbolizer', 0)
1050        lt = rule3.elementsByTagName("Filter").item(0).firstChild()
1051        self.assertEqual("ogc:PropertyIsLessThan", lt.nodeName())
1052        ltProperty = lt.toElement().firstChild()
1053        self.assertEqual("ogc:PropertyName", ltProperty.nodeName())
1054        self.assertEqual("POP_MAX", ltProperty.toElement().text())
1055        ltValue = gt.childNodes().item(1)
1056        self.assertEqual("1000000", gtValue.toElement().text())
1057
1058        # check that adding a rule without settings does not segfault
1059        xml1 = dom.toString()
1060        layer.labeling().rootRule().appendChild(QgsRuleBasedLabeling.Rule(None))
1061        dom, root = self.layerToSld(layer)
1062        xml2 = dom.toString()
1063        self.assertEqual(xml1, xml2)
1064
1065    def updateLinePlacementProperties(self, layer, linePlacement, distance, repeat, maxAngleInternal=25,
1066                                      maxAngleExternal=-25):
1067        settings = layer.labeling().settings()
1068        settings.placement = linePlacement
1069        settings.dist = distance
1070        settings.repeatDistance = repeat
1071        settings.maxCurvedCharAngleIn = maxAngleInternal
1072        settings.maxCurvedCharAngleOut = maxAngleExternal
1073        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
1074
1075    def assertPointPlacementDistance(self, placement):
1076        layer = QgsVectorLayer("Point", "addfeat", "memory")
1077        self.loadStyleWithCustomProperties(layer, "simpleLabel")
1078
1079        settings = layer.labeling().settings()
1080        settings.placement = placement
1081        settings.xOffset = 0
1082        settings.yOffset = 0
1083        settings.dist = 2
1084        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
1085
1086        dom, root = self.layerToSld(layer)
1087        # print("Label with around point placement  " + dom.toString())
1088        ts = self.getTextSymbolizer(root, 1, 0)
1089        pointPlacement = self.assertPointPlacement(ts)
1090        self.assertStaticAnchorPoint(pointPlacement, 0, 0.5)
1091        self.assertStaticDisplacement(pointPlacement, 4.95, 4.95)
1092
1093    def assertLabelQuadrant(self, quadrant, label, ax, ay):
1094        layer = QgsVectorLayer("Point", "addfeat", "memory")
1095        self.loadStyleWithCustomProperties(layer, "simpleLabel")
1096
1097        settings = layer.labeling().settings()
1098        settings.placement = QgsPalLayerSettings.OverPoint
1099        settings.xOffset = 0
1100        settings.yOffset = 0
1101        settings.quadOffset = quadrant
1102        settings.angleOffset = 0
1103        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
1104
1105        dom, root = self.layerToSld(layer)
1106        # print("Label with " + label  + " placement  " + dom.toString())
1107        self.assertStaticAnchorPoint(root, ax, ay)
1108
1109    def setLabelBufferSettings(self, layer, buffer):
1110        settings = layer.labeling().settings()
1111        format = settings.format()
1112        format.setBuffer(buffer)
1113        settings.setFormat(format)
1114        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
1115
1116    def updateLayerLabelingFontStyle(self, layer, bold, italic, underline=False, strikeout=False):
1117        settings = layer.labeling().settings()
1118        format = settings.format()
1119        font = format.font()
1120        font.setBold(bold)
1121        font.setItalic(italic)
1122        font.setUnderline(underline)
1123        font.setStrikeOut(strikeout)
1124        format.setFont(font)
1125        settings.setFormat(format)
1126        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
1127
1128    def updateLayerLabelingUnit(self, layer, unit):
1129        settings = layer.labeling().settings()
1130        format = settings.format()
1131        format.setSizeUnit(unit)
1132        settings.setFormat(format)
1133        layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
1134
1135    def loadStyleWithCustomProperties(self, layer, qmlFileName):
1136        # load the style, only vector symbology
1137        path = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), qmlFileName))
1138
1139        # labeling is in custom properties, they need to be loaded separately
1140        status = layer.loadNamedStyle(path)
1141        doc = QDomDocument()
1142        file = QFile(path)
1143        file.open(QIODevice.ReadOnly)
1144        doc.setContent(file, True)
1145        file.close()
1146        flag = layer.readCustomProperties(doc.documentElement())
1147
1148    def assertPointPlacement(self, textSymbolizer):
1149        labelPlacement = self.assertElement(textSymbolizer, 'se:LabelPlacement', 0)
1150        self.assertIsNone(self.assertElement(labelPlacement, 'se:LinePlacement', 0, True))
1151        pointPlacement = self.assertElement(labelPlacement, 'se:PointPlacement', 0)
1152        return pointPlacement
1153
1154    def assertLinePlacement(self, textSymbolizer):
1155        labelPlacement = self.assertElement(textSymbolizer, 'se:LabelPlacement', 0)
1156        self.assertIsNone(self.assertElement(labelPlacement, 'se:PointPlacement', 0, True))
1157        linePlacement = self.assertElement(labelPlacement, 'se:LinePlacement', 0)
1158        return linePlacement
1159
1160    def assertElement(self, container, elementName, index, allowMissing=False):
1161        list = container.elementsByTagName(elementName)
1162        if list.size() <= index:
1163            if allowMissing:
1164                return None
1165            else:
1166                self.fail('Expected to find at least ' + str(
1167                    index + 1) + ' ' + elementName + ' in ' + container.nodeName() + ' but found ' + str(list.size()))
1168
1169        node = list.item(index)
1170        self.assertTrue(node.isElement(), 'Found node but it''s not an element')
1171        return node.toElement()
1172
1173    def getRule(self, root, ruleIndex):
1174        rule = self.assertElement(root, 'se:Rule', ruleIndex)
1175        return rule
1176
1177    def getTextSymbolizer(self, root, ruleIndex, textSymbolizerIndex):
1178        rule = self.assertElement(root, 'se:Rule', ruleIndex)
1179        textSymbolizer = self.assertElement(rule, 'se:TextSymbolizer', textSymbolizerIndex)
1180        return textSymbolizer
1181
1182    def assertPropertyName(self, root, containerProperty, expectedAttributeName):
1183        container = root.elementsByTagName(containerProperty).item(0).toElement()
1184        property = container.elementsByTagName("ogc:PropertyName").item(0).toElement()
1185        self.assertEqual(expectedAttributeName, property.text())
1186
1187    def assertSvgParameter(self, container, expectedName, allowMissing=False):
1188        list = container.elementsByTagName("se:SvgParameter")
1189        for i in range(0, list.size()):
1190            item = list.item(i)
1191            if item.isElement and item.isElement() and item.toElement().attribute('name') == expectedName:
1192                return item.toElement()
1193        if allowMissing:
1194            return None
1195        else:
1196            self.fail('Could not find a se:SvgParameter named ' + expectedName + ' in ' + container.nodeName())
1197
1198    def assertVendorOption(self, container, expectedName, allowMissing=False):
1199        list = container.elementsByTagName("se:VendorOption")
1200        for i in range(0, list.size()):
1201            item = list.item(i)
1202            if item.isElement and item.isElement() and item.toElement().attribute('name') == expectedName:
1203                return item.toElement()
1204        if allowMissing:
1205            return None
1206        else:
1207            self.fail('Could not find a se:VendorOption named ' + expectedName + ' in ' + container.nodeName())
1208
1209    def testRuleBaseEmptyFilter(self):
1210        layer = QgsVectorLayer("Point", "addfeat", "memory")
1211
1212        mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "categorizedEmptyValue"))
1213        status = layer.loadNamedStyle(mFilePath)  # NOQA
1214
1215        dom, root = self.layerToSld(layer)
1216        # print("Rule based, with last rule checking against empty value:" + dom.toString())
1217
1218        # get the third rule
1219        rule = root.elementsByTagName('se:Rule').item(2).toElement()
1220        filter = rule.elementsByTagName('Filter').item(0).toElement()
1221        filter = filter.firstChild().toElement()
1222        self.assertEqual("ogc:Or", filter.nodeName())
1223        self.assertEqual(1, filter.elementsByTagName('ogc:PropertyIsEqualTo').size())
1224        self.assertEqual(1, filter.elementsByTagName('ogc:PropertyIsNull').size())
1225
1226    def assertScaleDenominator(self, root, expectedMinScale, expectedMaxScale, index=0):
1227        rule = root.elementsByTagName('se:Rule').item(index).toElement()
1228
1229        if expectedMinScale:
1230            minScale = rule.elementsByTagName('se:MinScaleDenominator').item(0)
1231            self.assertEqual(expectedMinScale, minScale.firstChild().nodeValue())
1232        else:
1233            self.assertEqual(0, root.elementsByTagName('se:MinScaleDenominator').size())
1234
1235        if expectedMaxScale:
1236            maxScale = rule.elementsByTagName('se:MaxScaleDenominator').item(0)
1237            self.assertEqual(expectedMaxScale, maxScale.firstChild().nodeValue())
1238        else:
1239            self.assertEqual(0, root.elementsByTagName('se:MaxScaleDenominator').size())
1240
1241    def assertDashPattern(self, root, svgParameterIdx, expectedPattern):
1242        strokeWidth = root.elementsByTagName(
1243            'se:SvgParameter').item(svgParameterIdx)
1244        svgParameterName = strokeWidth.attributes().namedItem('name')
1245        self.assertEqual("stroke-dasharray", svgParameterName.nodeValue())
1246        self.assertEqual(
1247            expectedPattern, strokeWidth.firstChild().nodeValue())
1248
1249    def assertStaticGap(self, root, expectedValue):
1250        # Check the rotation element is a literal, not a
1251        rotation = root.elementsByTagName('se:Gap').item(0)
1252        literal = rotation.firstChild()
1253        self.assertEqual("ogc:Literal", literal.nodeName())
1254        self.assertEqual(expectedValue, literal.firstChild().nodeValue())
1255
1256    def assertStaticSize(self, root, expectedValue):
1257        size = root.elementsByTagName('se:Size').item(0)
1258        self.assertEqual(expectedValue, size.firstChild().nodeValue())
1259
1260    def assertExternalGraphic(self, root, index, expectedLink, expectedFormat):
1261        graphic = root.elementsByTagName('se:ExternalGraphic').item(index)
1262        onlineResource = graphic.firstChildElement('se:OnlineResource')
1263        self.assertEqual(expectedLink, onlineResource.attribute('xlink:href'))
1264        format = graphic.firstChildElement('se:Format')
1265        self.assertEqual(expectedFormat, format.firstChild().nodeValue())
1266
1267    def assertStaticPerpendicularOffset(self, root, expectedValue):
1268        offset = root.elementsByTagName('se:PerpendicularOffset').item(0)
1269        self.assertEqual(expectedValue, offset.firstChild().nodeValue())
1270
1271    def assertWellKnownMark(self, root, index, expectedName, expectedFill, expectedStroke, expectedStrokeWidth):
1272        mark = root.elementsByTagName('se:Mark').item(index)
1273        wkn = mark.firstChildElement('se:WellKnownName')
1274        self.assertEqual(expectedName, wkn.text())
1275
1276        fill = mark.firstChildElement('se:Fill')
1277        if expectedFill is None:
1278            self.assertTrue(fill.isNull())
1279        else:
1280            parameter = fill.firstChildElement('se:SvgParameter')
1281            self.assertEqual('fill', parameter.attribute('name'))
1282            self.assertEqual(expectedFill, parameter.text())
1283
1284        stroke = mark.firstChildElement('se:Stroke')
1285        if expectedStroke is None:
1286            self.assertTrue(stroke.isNull())
1287        else:
1288            parameter = stroke.firstChildElement('se:SvgParameter')
1289            self.assertEqual('stroke', parameter.attribute('name'))
1290            self.assertEqual(expectedStroke, parameter.text())
1291            parameter = parameter.nextSiblingElement('se:SvgParameter')
1292            self.assertEqual('stroke-width', parameter.attribute('name'))
1293            self.assertEqual(str(expectedStrokeWidth), parameter.text())
1294
1295    def assertStaticRotation(self, root, expectedValue, index=0):
1296        # Check the rotation element is a literal, not a
1297        rotation = root.elementsByTagName('se:Rotation').item(index)
1298        literal = rotation.firstChild()
1299        self.assertEqual("ogc:Literal", literal.nodeName())
1300        self.assertEqual(expectedValue, literal.firstChild().nodeValue())
1301
1302    def assertStaticDisplacement(self, root, expectedAnchorX, expectedAnchorY):
1303        displacement = root.elementsByTagName('se:Displacement').item(0)
1304        self.assertIsNotNone(displacement)
1305        dx = displacement.firstChild()
1306        self.assertIsNotNone(dx)
1307        self.assertEqual("se:DisplacementX", dx.nodeName())
1308        self.assertSldNumber(expectedAnchorX, dx.firstChild().nodeValue())
1309        dy = displacement.lastChild()
1310        self.assertIsNotNone(dy)
1311        self.assertEqual("se:DisplacementY", dy.nodeName())
1312        self.assertSldNumber(expectedAnchorY, dy.firstChild().nodeValue())
1313
1314    def assertStaticAnchorPoint(self, root, expectedDispX, expectedDispY):
1315        anchor = root.elementsByTagName('se:AnchorPoint').item(0)
1316        self.assertIsNotNone(anchor)
1317        ax = anchor.firstChild()
1318        self.assertIsNotNone(ax)
1319        self.assertEqual("se:AnchorPointX", ax.nodeName())
1320        self.assertSldNumber(expectedDispX, ax.firstChild().nodeValue())
1321        ay = anchor.lastChild()
1322        self.assertIsNotNone(ay)
1323        self.assertEqual("se:AnchorPointY", ay.nodeName())
1324        self.assertSldNumber(expectedDispY, ay.firstChild().nodeValue())
1325
1326    def assertSldNumber(self, expected, stringValue):
1327        value = float(stringValue)
1328        self.assertFloatEquals(expected, value, 0.01)
1329
1330    def assertFloatEquals(self, expected, actual, tol):
1331        self.assertLess(abs(expected - actual), tol, 'Expected %d but was %d' % (expected, actual))
1332
1333    def assertStrokeWidth(self, root, svgParameterIdx, expectedWidth):
1334        strokeWidth = root.elementsByTagName(
1335            'se:SvgParameter').item(svgParameterIdx)
1336        svgParameterName = strokeWidth.attributes().namedItem('name')
1337        self.assertEqual("stroke-width", svgParameterName.nodeValue())
1338        self.assertSldNumber(
1339            expectedWidth, strokeWidth.firstChild().nodeValue())
1340
1341    def symbolToSld(self, symbolLayer):
1342        dom = QDomDocument()
1343        root = dom.createElement("FakeRoot")
1344        dom.appendChild(root)
1345        symbolLayer.toSld(dom, root, {})
1346        return dom, root
1347
1348    def layerToSld(self, mapLayer):
1349        dom = QDomDocument()
1350        root = dom.createElement("FakeRoot")
1351        dom.appendChild(root)
1352        error = None
1353        mapLayer.writeSld(root, dom, error, {})
1354        return dom, root
1355
1356
1357if __name__ == '__main__':
1358    unittest.main()
1359