1# -*- coding: utf-8 -*-
2
3"""
4***************************************************************************
5    test_qgsmarkerlinesymbollayer.py
6    ---------------------
7    Date                 : November 2018
8    Copyright            : (C) 2018 by Nyall Dawson
9    Email                : nyall dot dawson at gmail dot com
10***************************************************************************
11*                                                                         *
12*   This program is free software; you can redistribute it and/or modify  *
13*   it under the terms of the GNU General Public License as published by  *
14*   the Free Software Foundation; either version 2 of the License, or     *
15*   (at your option) any later version.                                   *
16*                                                                         *
17***************************************************************************
18"""
19
20__author__ = 'Nyall Dawson'
21__date__ = 'November 2018'
22__copyright__ = '(C) 2018, Nyall Dawson'
23
24import qgis  # NOQA
25
26import os
27from utilities import unitTestDataPath
28
29from qgis.PyQt.QtCore import QDir, Qt, QSize
30from qgis.PyQt.QtGui import QImage, QColor, QPainter
31from qgis.PyQt.QtXml import QDomDocument
32
33from qgis.core import (QgsGeometry,
34                       QgsFillSymbol,
35                       QgsRenderContext,
36                       QgsFeature,
37                       QgsMapSettings,
38                       QgsRenderChecker,
39                       QgsVectorLayer,
40                       QgsReadWriteContext,
41                       QgsSymbolLayerUtils,
42                       QgsSimpleMarkerSymbolLayer,
43                       QgsLineSymbolLayer,
44                       QgsTemplatedLineSymbolLayerBase,
45                       QgsMarkerLineSymbolLayer,
46                       QgsMarkerSymbol,
47                       QgsGeometryGeneratorSymbolLayer,
48                       QgsSymbol,
49                       QgsFontMarkerSymbolLayer,
50                       QgsFontUtils,
51                       QgsLineSymbol,
52                       QgsSymbolLayer,
53                       QgsProperty,
54                       QgsRectangle,
55                       QgsUnitTypes,
56                       QgsMultiRenderChecker,
57                       QgsSingleSymbolRenderer
58                       )
59
60from qgis.testing import unittest, start_app
61
62start_app()
63TEST_DATA_DIR = unitTestDataPath()
64
65
66class TestQgsMarkerLineSymbolLayer(unittest.TestCase):
67
68    def setUp(self):
69        self.report = "<h1>Python QgsMarkerLineSymbolLayer Tests</h1>\n"
70
71    def tearDown(self):
72        report_file_path = "%s/qgistest.html" % QDir.tempPath()
73        with open(report_file_path, 'a') as report_file:
74            report_file.write(self.report)
75
76    def testWidth(self):
77        ms = QgsMapSettings()
78        extent = QgsRectangle(100, 200, 100, 200)
79        ms.setExtent(extent)
80        ms.setOutputSize(QSize(400, 400))
81        context = QgsRenderContext.fromMapSettings(ms)
82        context.setScaleFactor(96 / 25.4)  # 96 DPI
83        ms.setExtent(QgsRectangle(100, 150, 100, 150))
84        ms.setOutputDpi(ms.outputDpi() * 2)
85        context2 = QgsRenderContext.fromMapSettings(ms)
86        context2.setScaleFactor(300 / 25.4)
87
88        s = QgsFillSymbol()
89        s.deleteSymbolLayer(0)
90
91        marker_line = QgsMarkerLineSymbolLayer(True)
92        marker_line.setPlacement(QgsMarkerLineSymbolLayer.FirstVertex)
93        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 10)
94        marker.setColor(QColor(255, 0, 0))
95        marker.setStrokeStyle(Qt.NoPen)
96        marker_symbol = QgsMarkerSymbol()
97        marker_symbol.changeSymbolLayer(0, marker)
98        marker_line.setSubSymbol(marker_symbol)
99
100        self.assertEqual(marker_line.width(), 10)
101        self.assertAlmostEqual(marker_line.width(context), 37.795275590551185, 3)
102        self.assertAlmostEqual(marker_line.width(context2), 118.11023622047244, 3)
103
104        marker_line.subSymbol().setSizeUnit(QgsUnitTypes.RenderPixels)
105        self.assertAlmostEqual(marker_line.width(context), 10.0, 3)
106        self.assertAlmostEqual(marker_line.width(context2), 10.0, 3)
107
108    def testRingFilter(self):
109        # test filtering rings during rendering
110        s = QgsFillSymbol()
111        s.deleteSymbolLayer(0)
112
113        marker_line = QgsMarkerLineSymbolLayer(True)
114        marker_line.setPlacement(QgsMarkerLineSymbolLayer.FirstVertex)
115        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
116        marker.setColor(QColor(255, 0, 0))
117        marker.setStrokeStyle(Qt.NoPen)
118        marker_symbol = QgsMarkerSymbol()
119        marker_symbol.changeSymbolLayer(0, marker)
120        marker_line.setSubSymbol(marker_symbol)
121
122        s.appendSymbolLayer(marker_line.clone())
123        self.assertEqual(s.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.AllRings)
124        s.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.ExteriorRingOnly)
125        self.assertEqual(s.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly)
126
127        s2 = s.clone()
128        self.assertEqual(s2.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly)
129
130        doc = QDomDocument()
131        context = QgsReadWriteContext()
132        element = QgsSymbolLayerUtils.saveSymbol('test', s, doc, context)
133
134        s2 = QgsSymbolLayerUtils.loadSymbol(element, context)
135        self.assertEqual(s2.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.ExteriorRingOnly)
136
137        # rendering test
138        s3 = QgsFillSymbol()
139        s3.deleteSymbolLayer(0)
140        s3.appendSymbolLayer(
141            QgsMarkerLineSymbolLayer())
142        s3.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.ExteriorRingOnly)
143        s3.symbolLayer(0).setAverageAngleLength(0)
144
145        g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))')
146        rendered_image = self.renderGeometry(s3, g)
147        assert self.imageCheck('markerline_exterioronly', 'markerline_exterioronly', rendered_image)
148
149        s3.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.InteriorRingsOnly)
150        g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))')
151        rendered_image = self.renderGeometry(s3, g)
152        assert self.imageCheck('markerline_interioronly', 'markerline_interioronly', rendered_image)
153
154    def testRingNumberVariable(self):
155        # test test geometry_ring_num variable
156        s3 = QgsFillSymbol()
157        s3.deleteSymbolLayer(0)
158        s3.appendSymbolLayer(
159            QgsMarkerLineSymbolLayer())
160        s3.symbolLayer(0).subSymbol()[0].setDataDefinedProperty(QgsSymbolLayer.PropertyFillColor,
161                                                                QgsProperty.fromExpression('case when @geometry_ring_num=0 then \'green\' when @geometry_ring_num=1 then \'blue\' when @geometry_ring_num=2 then \'red\' end'))
162        s3.symbolLayer(0).setAverageAngleLength(0)
163
164        g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))')
165        rendered_image = self.renderGeometry(s3, g)
166        assert self.imageCheck('markerline_ring_num', 'markerline_ring_num', rendered_image)
167
168    def testPartNum(self):
169        # test geometry_part_num variable
170        s = QgsLineSymbol()
171        s.deleteSymbolLayer(0)
172
173        sym_layer = QgsGeometryGeneratorSymbolLayer.create({'geometryModifier': 'segments_to_lines($geometry)'})
174        sym_layer.setSymbolType(QgsSymbol.Line)
175        s.appendSymbolLayer(sym_layer)
176
177        marker_line = QgsMarkerLineSymbolLayer(False)
178        marker_line.setPlacement(QgsMarkerLineSymbolLayer.FirstVertex)
179        f = QgsFontUtils.getStandardTestFont('Bold', 24)
180        marker = QgsFontMarkerSymbolLayer(f.family(), 'x', 24, QColor(255, 255, 0))
181        marker.setDataDefinedProperty(QgsSymbolLayer.PropertyCharacter, QgsProperty.fromExpression('@geometry_part_num'))
182        marker_symbol = QgsMarkerSymbol()
183        marker_symbol.changeSymbolLayer(0, marker)
184        marker_line.setSubSymbol(marker_symbol)
185        marker_line.setAverageAngleLength(0)
186        line_symbol = QgsLineSymbol()
187        line_symbol.changeSymbolLayer(0, marker_line)
188        sym_layer.setSubSymbol(line_symbol)
189
190        # rendering test
191        g = QgsGeometry.fromWkt('LineString(0 0, 10 0, 10 10, 0 10)')
192        rendered_image = self.renderGeometry(s, g, buffer=4)
193        assert self.imageCheck('part_num_variable', 'part_num_variable', rendered_image)
194
195        marker.setDataDefinedProperty(QgsSymbolLayer.PropertyCharacter,
196                                      QgsProperty.fromExpression('@geometry_part_count'))
197
198        # rendering test
199        g = QgsGeometry.fromWkt('LineString(0 0, 10 0, 10 10, 0 10)')
200        rendered_image = self.renderGeometry(s, g, buffer=4)
201        assert self.imageCheck('part_count_variable', 'part_count_variable', rendered_image)
202
203    def testPartNumPolygon(self):
204        # test geometry_part_num variable
205        s = QgsFillSymbol()
206
207        marker_line = QgsMarkerLineSymbolLayer(False)
208        marker_line.setPlacement(QgsMarkerLineSymbolLayer.FirstVertex)
209        f = QgsFontUtils.getStandardTestFont('Bold', 24)
210        marker = QgsFontMarkerSymbolLayer(f.family(), 'x', 24, QColor(255, 255, 0))
211        marker.setDataDefinedProperty(QgsSymbolLayer.PropertyCharacter, QgsProperty.fromExpression('@geometry_part_num'))
212        marker_symbol = QgsMarkerSymbol()
213        marker_symbol.changeSymbolLayer(0, marker)
214        marker_line.setSubSymbol(marker_symbol)
215        marker_line.setAverageAngleLength(0)
216        s.changeSymbolLayer(0, marker_line)
217
218        # rendering test - a polygon with a smaller part first
219        g = QgsGeometry.fromWkt('MultiPolygon(((0 0, 2 0, 2 2, 0 0)),((10 0, 10 10, 0 10, 10 0)))')
220        rendered_image = self.renderGeometry(s, g, buffer=4)
221        assert self.imageCheck('poly_part_num_variable', 'poly_part_num_variable', rendered_image)
222
223    def testCompoundCurve(self):
224        # test rendering compound curve with markers at vertices and curve points
225        s = QgsLineSymbol()
226        s.deleteSymbolLayer(0)
227
228        marker_line = QgsMarkerLineSymbolLayer(True)
229        marker_line.setPlacement(QgsMarkerLineSymbolLayer.Vertex)
230        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
231        marker.setColor(QColor(255, 0, 0))
232        marker.setStrokeStyle(Qt.NoPen)
233        marker_symbol = QgsMarkerSymbol()
234        marker_symbol.changeSymbolLayer(0, marker)
235        marker_line.setSubSymbol(marker_symbol)
236
237        s.appendSymbolLayer(marker_line.clone())
238
239        marker_line2 = QgsMarkerLineSymbolLayer(True)
240        marker_line2.setPlacement(QgsMarkerLineSymbolLayer.CurvePoint)
241        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Square, 4)
242        marker.setColor(QColor(0, 255, 0))
243        marker.setStrokeStyle(Qt.NoPen)
244        marker_symbol = QgsMarkerSymbol()
245        marker_symbol.changeSymbolLayer(0, marker)
246        marker_line2.setSubSymbol(marker_symbol)
247
248        s.appendSymbolLayer(marker_line2.clone())
249
250        # rendering test
251        g = QgsGeometry.fromWkt('CompoundCurve (CircularString (2606642.3863534671254456 1228883.61571401031687856, 2606656.45901552261784673 1228882.30281259422190487, 2606652.60236761253327131 1228873.80998155777342618, 2606643.65822671446949244 1228875.45110832806676626, 2606642.3863534671254456 1228883.65674217976629734))')
252        self.assertFalse(g.isNull())
253        rendered_image = self.renderGeometry(s, g)
254        self.assertTrue(self.imageCheck('markerline_compoundcurve', 'markerline_compoundcurve', rendered_image))
255
256    def testMultiCurve(self):
257        # test rendering multi curve with markers at vertices and curve points
258        s = QgsLineSymbol()
259        s.deleteSymbolLayer(0)
260
261        marker_line = QgsMarkerLineSymbolLayer(True)
262        marker_line.setPlacement(QgsMarkerLineSymbolLayer.Vertex)
263        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
264        marker.setColor(QColor(255, 0, 0))
265        marker.setStrokeStyle(Qt.NoPen)
266        marker_symbol = QgsMarkerSymbol()
267        marker_symbol.changeSymbolLayer(0, marker)
268        marker_line.setSubSymbol(marker_symbol)
269
270        s.appendSymbolLayer(marker_line.clone())
271
272        marker_line2 = QgsMarkerLineSymbolLayer(True)
273        marker_line2.setPlacement(QgsMarkerLineSymbolLayer.CurvePoint)
274        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Square, 4)
275        marker.setColor(QColor(0, 255, 0))
276        marker.setStrokeStyle(Qt.NoPen)
277        marker_symbol = QgsMarkerSymbol()
278        marker_symbol.changeSymbolLayer(0, marker)
279        marker_line2.setSubSymbol(marker_symbol)
280
281        s.appendSymbolLayer(marker_line2.clone())
282
283        # rendering test
284        g = QgsGeometry.fromWkt('MultiCurve (CompoundCurve (CircularString (2606668.74491960229352117 1228910.0701227153185755, 2606667.84593895543366671 1228899.48981202743016183, 2606678.70285907341167331 1228879.78139015776105225, 2606701.64743852475658059 1228866.43043032777495682, 2606724.96578619908541441 1228864.70617623627185822)),LineString (2606694.16802780656144023 1228913.44624055083841085, 2606716.84054400492459536 1228890.51009044284000993, 2606752.43112175865098834 1228906.59175890940241516))')
285        self.assertFalse(g.isNull())
286        rendered_image = self.renderGeometry(s, g)
287        self.assertTrue(self.imageCheck('markerline_multicurve', 'markerline_multicurve', rendered_image))
288
289    def testCurvePolygon(self):
290        # test rendering curve polygon with markers at vertices and curve points
291        s = QgsFillSymbol()
292        s.deleteSymbolLayer(0)
293
294        marker_line = QgsMarkerLineSymbolLayer(True)
295        marker_line.setPlacement(QgsMarkerLineSymbolLayer.Vertex)
296        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
297        marker.setColor(QColor(255, 0, 0))
298        marker.setStrokeStyle(Qt.NoPen)
299        marker_symbol = QgsMarkerSymbol()
300        marker_symbol.changeSymbolLayer(0, marker)
301        marker_line.setSubSymbol(marker_symbol)
302
303        s.appendSymbolLayer(marker_line.clone())
304
305        marker_line2 = QgsMarkerLineSymbolLayer(True)
306        marker_line2.setPlacement(QgsMarkerLineSymbolLayer.CurvePoint)
307        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Square, 4)
308        marker.setColor(QColor(0, 255, 0))
309        marker.setStrokeStyle(Qt.NoPen)
310        marker_symbol = QgsMarkerSymbol()
311        marker_symbol.changeSymbolLayer(0, marker)
312        marker_line2.setSubSymbol(marker_symbol)
313
314        s.appendSymbolLayer(marker_line2.clone())
315
316        # rendering test
317        g = QgsGeometry.fromWkt('CurvePolygon (CompoundCurve (CircularString (2606711.1353147104382515 1228875.77055342611856759, 2606715.00784672703593969 1228870.79158369055949152, 2606721.16240653907880187 1228873.35022091586142778),(2606721.16240653907880187 1228873.35022091586142778, 2606711.1353147104382515 1228875.77055342611856759)))')
318        self.assertFalse(g.isNull())
319        rendered_image = self.renderGeometry(s, g)
320        self.assertTrue(self.imageCheck('markerline_curvepolygon', 'markerline_curvepolygon', rendered_image))
321
322    def testMultiSurve(self):
323        # test rendering multisurface with markers at vertices and curve points
324        s = QgsFillSymbol()
325        s.deleteSymbolLayer(0)
326
327        marker_line = QgsMarkerLineSymbolLayer(True)
328        marker_line.setPlacement(QgsMarkerLineSymbolLayer.Vertex)
329        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
330        marker.setColor(QColor(255, 0, 0))
331        marker.setStrokeStyle(Qt.NoPen)
332        marker_symbol = QgsMarkerSymbol()
333        marker_symbol.changeSymbolLayer(0, marker)
334        marker_line.setSubSymbol(marker_symbol)
335
336        s.appendSymbolLayer(marker_line.clone())
337
338        marker_line2 = QgsMarkerLineSymbolLayer(True)
339        marker_line2.setPlacement(QgsMarkerLineSymbolLayer.CurvePoint)
340        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Square, 4)
341        marker.setColor(QColor(0, 255, 0))
342        marker.setStrokeStyle(Qt.NoPen)
343        marker_symbol = QgsMarkerSymbol()
344        marker_symbol.changeSymbolLayer(0, marker)
345        marker_line2.setSubSymbol(marker_symbol)
346
347        s.appendSymbolLayer(marker_line2.clone())
348
349        # rendering test
350        g = QgsGeometry.fromWkt('MultiSurface (CurvePolygon (CompoundCurve (CircularString (2606664.83926784340292215 1228868.83649749564938247, 2606666.84044930292293429 1228872.22980518848635256, 2606668.05855975672602654 1228875.62311288132332265, 2606674.45363963954150677 1228870.05460794945247471, 2606680.58769585331901908 1228866.00874108518473804, 2606680.7182076876051724 1228865.05165429995395243, 2606679.97864062618464231 1228864.61661485210061073, 2606671.93041084241122007 1228867.87941071065142751, 2606664.83926784340292215 1228868.79299355088733137),(2606664.83926784340292215 1228868.79299355088733137, 2606664.83926784340292215 1228868.83649749564938247))),Polygon ((2606677.23432376980781555 1228875.74241803237237036, 2606674.27243852382525802 1228874.75512295053340495, 2606675.61874999897554517 1228871.97274590120650828, 2606678.84989754017442465 1228870.35717213083989918, 2606680.64497950719669461 1228873.31905737658962607, 2606677.23432376980781555 1228875.74241803237237036)))')
351        self.assertFalse(g.isNull())
352        rendered_image = self.renderGeometry(s, g)
353        self.assertTrue(self.imageCheck('markerline_multisurface', 'markerline_multisurface', rendered_image))
354
355    def testMultiSurfaceOnePart(self):
356        # test rendering multisurface with one part with markers at vertices and curve points
357        s = QgsFillSymbol()
358        s.deleteSymbolLayer(0)
359
360        marker_line = QgsMarkerLineSymbolLayer(True)
361        marker_line.setPlacement(QgsMarkerLineSymbolLayer.Vertex)
362        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
363        marker.setColor(QColor(255, 0, 0))
364        marker.setStrokeStyle(Qt.NoPen)
365        marker_symbol = QgsMarkerSymbol()
366        marker_symbol.changeSymbolLayer(0, marker)
367        marker_line.setSubSymbol(marker_symbol)
368
369        s.appendSymbolLayer(marker_line.clone())
370
371        marker_line2 = QgsMarkerLineSymbolLayer(True)
372        marker_line2.setPlacement(QgsMarkerLineSymbolLayer.CurvePoint)
373        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Square, 4)
374        marker.setColor(QColor(0, 255, 0))
375        marker.setStrokeStyle(Qt.NoPen)
376        marker_symbol = QgsMarkerSymbol()
377        marker_symbol.changeSymbolLayer(0, marker)
378        marker_line2.setSubSymbol(marker_symbol)
379
380        s.appendSymbolLayer(marker_line2.clone())
381
382        # rendering test
383        g = QgsGeometry.fromWkt('MultiSurface (CurvePolygon (CompoundCurve (CircularString (2606664.83926784340292215 1228868.83649749564938247, 2606666.84044930292293429 1228872.22980518848635256, 2606668.05855975672602654 1228875.62311288132332265, 2606674.45363963954150677 1228870.05460794945247471, 2606680.58769585331901908 1228866.00874108518473804, 2606680.7182076876051724 1228865.05165429995395243, 2606679.97864062618464231 1228864.61661485210061073, 2606671.93041084241122007 1228867.87941071065142751, 2606664.83926784340292215 1228868.79299355088733137),(2606664.83926784340292215 1228868.79299355088733137, 2606664.83926784340292215 1228868.83649749564938247))))')
384        self.assertFalse(g.isNull())
385        rendered_image = self.renderGeometry(s, g)
386        self.assertTrue(self.imageCheck('markerline_one_part_multisurface', 'markerline_one_part_multisurface', rendered_image))
387
388    def testMarkerAverageAngle(self):
389        s = QgsLineSymbol()
390        s.deleteSymbolLayer(0)
391
392        marker_line = QgsMarkerLineSymbolLayer(True)
393        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.Interval)
394        marker_line.setInterval(6)
395        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
396        marker.setColor(QColor(255, 0, 0))
397        marker.setStrokeStyle(Qt.NoPen)
398        marker_symbol = QgsMarkerSymbol()
399        marker_symbol.changeSymbolLayer(0, marker)
400        marker_line.setSubSymbol(marker_symbol)
401        marker_line.setAverageAngleLength(60)
402        line_symbol = QgsLineSymbol()
403        line_symbol.changeSymbolLayer(0, marker_line)
404
405        s.appendSymbolLayer(marker_line.clone())
406
407        g = QgsGeometry.fromWkt('LineString(0 0, 10 10, 10 0)')
408        rendered_image = self.renderGeometry(s, g)
409        assert self.imageCheck('markerline_average_angle', 'markerline_average_angle', rendered_image)
410
411    def testMarkerAverageAngleRing(self):
412        s = QgsLineSymbol()
413        s.deleteSymbolLayer(0)
414
415        marker_line = QgsMarkerLineSymbolLayer(True)
416        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.Interval)
417        marker_line.setInterval(6)
418        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
419        marker.setColor(QColor(255, 0, 0))
420        marker.setStrokeStyle(Qt.NoPen)
421        marker_symbol = QgsMarkerSymbol()
422        marker_symbol.changeSymbolLayer(0, marker)
423        marker_line.setSubSymbol(marker_symbol)
424        marker_line.setAverageAngleLength(60)
425        line_symbol = QgsLineSymbol()
426        line_symbol.changeSymbolLayer(0, marker_line)
427
428        s.appendSymbolLayer(marker_line.clone())
429
430        g = QgsGeometry.fromWkt('LineString(0 0, 0 10, 10 10, 10 0, 0 0)')
431        rendered_image = self.renderGeometry(s, g)
432        assert self.imageCheck('markerline_ring_average_angle', 'markerline_ring_average_angle', rendered_image)
433
434    def testMarkerAverageAngleCenter(self):
435        s = QgsLineSymbol()
436        s.deleteSymbolLayer(0)
437
438        marker_line = QgsMarkerLineSymbolLayer(True)
439        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.CentralPoint)
440        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
441        marker.setColor(QColor(255, 0, 0))
442        marker.setStrokeStyle(Qt.NoPen)
443        marker_symbol = QgsMarkerSymbol()
444        marker_symbol.changeSymbolLayer(0, marker)
445        marker_line.setSubSymbol(marker_symbol)
446        marker_line.setAverageAngleLength(60)
447        line_symbol = QgsLineSymbol()
448        line_symbol.changeSymbolLayer(0, marker_line)
449
450        s.appendSymbolLayer(marker_line.clone())
451
452        g = QgsGeometry.fromWkt('LineString(0 0, 10 10, 10 0)')
453        rendered_image = self.renderGeometry(s, g)
454        assert self.imageCheck('markerline_center_average_angle', 'markerline_center_average_angle', rendered_image)
455
456    def testRingNoDupe(self):
457        s = QgsLineSymbol()
458        s.deleteSymbolLayer(0)
459
460        marker_line = QgsMarkerLineSymbolLayer(True)
461        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.Interval)
462        marker_line.setInterval(10)
463        marker_line.setIntervalUnit(QgsUnitTypes.RenderMapUnits)
464        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Circle, 4)
465        marker.setColor(QColor(255, 0, 0, 100))
466        marker.setStrokeStyle(Qt.NoPen)
467        marker_symbol = QgsMarkerSymbol()
468        marker_symbol.changeSymbolLayer(0, marker)
469        marker_line.setSubSymbol(marker_symbol)
470        line_symbol = QgsLineSymbol()
471        line_symbol.changeSymbolLayer(0, marker_line)
472
473        s.appendSymbolLayer(marker_line.clone())
474
475        g = QgsGeometry.fromWkt('LineString(0 0, 0 10, 10 10, 10 0, 0 0)')
476        rendered_image = self.renderGeometry(s, g)
477        assert self.imageCheck('markerline_ring_no_dupes', 'markerline_ring_no_dupes', rendered_image)
478
479    def testSinglePoint(self):
480        s = QgsLineSymbol()
481        s.deleteSymbolLayer(0)
482
483        marker_line = QgsMarkerLineSymbolLayer(True)
484        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.Interval)
485        marker_line.setInterval(1000)
486        marker_line.setIntervalUnit(QgsUnitTypes.RenderMapUnits)
487        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Circle, 4)
488        marker.setColor(QColor(255, 0, 0, 100))
489        marker.setStrokeStyle(Qt.NoPen)
490        marker_symbol = QgsMarkerSymbol()
491        marker_symbol.changeSymbolLayer(0, marker)
492        marker_line.setSubSymbol(marker_symbol)
493        line_symbol = QgsLineSymbol()
494        line_symbol.changeSymbolLayer(0, marker_line)
495
496        s.appendSymbolLayer(marker_line.clone())
497
498        g = QgsGeometry.fromWkt('LineString(0 0, 0 10, 10 10)')
499        rendered_image = self.renderGeometry(s, g)
500        assert self.imageCheck('markerline_single', 'markerline_single', rendered_image)
501
502    def testNoPoint(self):
503        s = QgsLineSymbol()
504        s.deleteSymbolLayer(0)
505
506        marker_line = QgsMarkerLineSymbolLayer(True)
507        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.Interval)
508        marker_line.setOffsetAlongLine(1000)
509        marker_line.setIntervalUnit(QgsUnitTypes.RenderMapUnits)
510        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Circle, 4)
511        marker.setColor(QColor(255, 0, 0, 100))
512        marker.setStrokeStyle(Qt.NoPen)
513        marker_symbol = QgsMarkerSymbol()
514        marker_symbol.changeSymbolLayer(0, marker)
515        marker_line.setSubSymbol(marker_symbol)
516        line_symbol = QgsLineSymbol()
517        line_symbol.changeSymbolLayer(0, marker_line)
518
519        s.appendSymbolLayer(marker_line.clone())
520
521        g = QgsGeometry.fromWkt('LineString(0 0, 0 10, 10 10)')
522        rendered_image = self.renderGeometry(s, g)
523        assert self.imageCheck('markerline_none', 'markerline_none', rendered_image)
524
525    def testCenterSegment(self):
526        s = QgsLineSymbol()
527        s.deleteSymbolLayer(0)
528
529        marker_line = QgsMarkerLineSymbolLayer(True)
530        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.SegmentCenter)
531        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
532        marker.setColor(QColor(255, 0, 0))
533        marker.setStrokeStyle(Qt.NoPen)
534        marker_symbol = QgsMarkerSymbol()
535        marker_symbol.changeSymbolLayer(0, marker)
536        marker_line.setSubSymbol(marker_symbol)
537        line_symbol = QgsLineSymbol()
538        line_symbol.changeSymbolLayer(0, marker_line)
539
540        s.appendSymbolLayer(marker_line.clone())
541
542        g = QgsGeometry.fromWkt('LineString(0 0, 10 0, 0 10)')
543        rendered_image = self.renderGeometry(s, g)
544        assert self.imageCheck('markerline_segmentcenter', 'markerline_segmentcenter', rendered_image)
545
546    def testMarkerDataDefinedAngleLine(self):
547        """Test issue https://github.com/qgis/QGIS/issues/38716"""
548
549        s = QgsLineSymbol()
550        s.deleteSymbolLayer(0)
551
552        marker_line = QgsMarkerLineSymbolLayer(True)
553        marker_line.setRotateSymbols(True)
554        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.CentralPoint)
555        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Arrow, 10)
556        marker.setAngle(90)
557        marker.setColor(QColor(255, 0, 0))
558        marker.setStrokeStyle(Qt.NoPen)
559        marker_symbol = QgsMarkerSymbol()
560        marker_symbol.changeSymbolLayer(0, marker)
561        marker_line.setSubSymbol(marker_symbol)
562        line_symbol = QgsLineSymbol()
563        line_symbol.changeSymbolLayer(0, marker_line)
564
565        s.appendSymbolLayer(marker_line.clone())
566
567        g = QgsGeometry.fromWkt('LineString(0 0, 10 10, 20 20)')
568        rendered_image = self.renderGeometry(s, g)
569        assert self.imageCheck('markerline_center_angle_dd', 'markerline_center_angle_dd', rendered_image)
570
571        # Now with DD
572
573        s = QgsLineSymbol()
574        s.deleteSymbolLayer(0)
575
576        marker_line = QgsMarkerLineSymbolLayer(True)
577        marker_line.setRotateSymbols(True)
578        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.CentralPoint)
579        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Arrow, 10)
580        # Note: set this to a different value than the reference test (90)
581        marker.setAngle(30)
582        marker.setColor(QColor(255, 0, 0))
583        marker.setStrokeStyle(Qt.NoPen)
584        marker_symbol = QgsMarkerSymbol()
585        marker_symbol.changeSymbolLayer(0, marker)
586        # This is the same value of the reference test
587        marker_symbol.setDataDefinedAngle(QgsProperty.fromExpression('90'))
588        marker_line.setSubSymbol(marker_symbol)
589        line_symbol = QgsLineSymbol()
590        line_symbol.changeSymbolLayer(0, marker_line)
591
592        s.appendSymbolLayer(marker_line.clone())
593
594        g = QgsGeometry.fromWkt('LineString(0 0, 10 10, 20 20)')
595        rendered_image = self.renderGeometry(s, g)
596        assert self.imageCheck('markerline_center_angle_dd', 'markerline_center_angle_dd', rendered_image)
597
598    def testDataDefinedAnglePolygon(self):
599        # test rendering curve polygon with markers at vertices and curve points
600        s = QgsFillSymbol()
601
602        marker_line = QgsMarkerLineSymbolLayer(True)
603        marker_line.setPlacement(QgsMarkerLineSymbolLayer.SegmentCenter)
604        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
605        marker.setColor(QColor(255, 0, 0))
606        marker.setStrokeStyle(Qt.NoPen)
607        marker.setAngle(90)
608        marker_symbol = QgsMarkerSymbol()
609        marker_symbol.changeSymbolLayer(0, marker)
610        marker_line.setSubSymbol(marker_symbol)
611
612        s.appendSymbolLayer(marker_line.clone())
613
614        g = QgsGeometry.fromWkt('Polygon (LineString (0 5, 5 0, 10 5, 5 10, 0 5))')
615        self.assertFalse(g.isNull())
616
617        # rendering test with non data-defined angle
618        rendered_image = self.renderGeometry(s, g)
619        self.assertTrue(self.imageCheck('markerline_datadefinedanglepolygon', 'markerline_datadefinedanglepolygon', rendered_image))
620
621        s = QgsFillSymbol()
622
623        marker_line = QgsMarkerLineSymbolLayer(True)
624        marker_line.setPlacement(QgsMarkerLineSymbolLayer.SegmentCenter)
625        marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
626        marker.setColor(QColor(255, 0, 0))
627        marker.setStrokeStyle(Qt.NoPen)
628        marker.setAngle(38)
629        marker_symbol = QgsMarkerSymbol()
630        marker_symbol.changeSymbolLayer(0, marker)
631        marker_symbol.setDataDefinedAngle(QgsProperty.fromExpression('90'))
632        marker_line.setSubSymbol(marker_symbol)
633
634        s.appendSymbolLayer(marker_line.clone())
635
636        # rendering test with data-defined angle
637        rendered_image = self.renderGeometry(s, g)
638        self.assertTrue(self.imageCheck('markerline_datadefinedanglepolygon', 'markerline_datadefinedanglepolygon', rendered_image))
639
640    def testOpacityWithDataDefinedColor(self):
641        line_shp = os.path.join(TEST_DATA_DIR, 'lines.shp')
642        line_layer = QgsVectorLayer(line_shp, 'Lines', 'ogr')
643        self.assertTrue(line_layer.isValid())
644
645        s = QgsLineSymbol()
646        s.deleteSymbolLayer(0)
647        marker_line = QgsMarkerLineSymbolLayer(True)
648        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.CentralPoint)
649        simple_marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Circle, 10)
650        simple_marker.setColor(QColor(0, 255, 0))
651        simple_marker.setStrokeColor(QColor(255, 0, 0))
652        simple_marker.setStrokeWidth(1)
653        simple_marker.setDataDefinedProperty(QgsSymbolLayer.PropertyFillColor, QgsProperty.fromExpression(
654            "if(Name='Arterial', 'red', 'green')"))
655        simple_marker.setDataDefinedProperty(QgsSymbolLayer.PropertyStrokeColor, QgsProperty.fromExpression(
656            "if(Name='Arterial', 'magenta', 'blue')"))
657
658        marker_symbol = QgsMarkerSymbol()
659        marker_symbol.changeSymbolLayer(0, simple_marker)
660        marker_symbol.setOpacity(0.5)
661        marker_line.setSubSymbol(marker_symbol)
662        s.appendSymbolLayer(marker_line.clone())
663
664        # set opacity on both the symbol and subsymbol, to test that they get combined
665        s.setOpacity(0.5)
666
667        line_layer.setRenderer(QgsSingleSymbolRenderer(s))
668
669        ms = QgsMapSettings()
670        ms.setOutputSize(QSize(400, 400))
671        ms.setOutputDpi(96)
672        ms.setExtent(QgsRectangle(-118.5, 19.0, -81.4, 50.4))
673        ms.setLayers([line_layer])
674
675        # Test rendering
676        renderchecker = QgsMultiRenderChecker()
677        renderchecker.setMapSettings(ms)
678        renderchecker.setControlPathPrefix('symbol_markerline')
679        renderchecker.setControlName('expected_markerline_opacityddcolor')
680        res = renderchecker.runTest('expected_markerline_opacityddcolor')
681        self.report += renderchecker.report()
682        self.assertTrue(res)
683
684    def testDataDefinedOpacity(self):
685        line_shp = os.path.join(TEST_DATA_DIR, 'lines.shp')
686        line_layer = QgsVectorLayer(line_shp, 'Lines', 'ogr')
687        self.assertTrue(line_layer.isValid())
688
689        s = QgsLineSymbol()
690        s.deleteSymbolLayer(0)
691        marker_line = QgsMarkerLineSymbolLayer(True)
692        marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.CentralPoint)
693        simple_marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Circle, 10)
694        simple_marker.setColor(QColor(0, 255, 0))
695        simple_marker.setStrokeColor(QColor(255, 0, 0))
696        simple_marker.setStrokeWidth(1)
697        simple_marker.setDataDefinedProperty(QgsSymbolLayer.PropertyFillColor, QgsProperty.fromExpression(
698            "if(Name='Arterial', 'red', 'green')"))
699        simple_marker.setDataDefinedProperty(QgsSymbolLayer.PropertyStrokeColor, QgsProperty.fromExpression(
700            "if(Name='Arterial', 'magenta', 'blue')"))
701
702        marker_symbol = QgsMarkerSymbol()
703        marker_symbol.changeSymbolLayer(0, simple_marker)
704        marker_symbol.setOpacity(0.5)
705        marker_line.setSubSymbol(marker_symbol)
706        s.appendSymbolLayer(marker_line.clone())
707
708        s.setDataDefinedProperty(QgsSymbol.PropertyOpacity, QgsProperty.fromExpression("if(\"Value\" = 1, 25, 50)"))
709
710        line_layer.setRenderer(QgsSingleSymbolRenderer(s))
711
712        ms = QgsMapSettings()
713        ms.setOutputSize(QSize(400, 400))
714        ms.setOutputDpi(96)
715        ms.setExtent(QgsRectangle(-118.5, 19.0, -81.4, 50.4))
716        ms.setLayers([line_layer])
717
718        # Test rendering
719        renderchecker = QgsMultiRenderChecker()
720        renderchecker.setMapSettings(ms)
721        renderchecker.setControlPathPrefix('symbol_markerline')
722        renderchecker.setControlName('expected_markerline_ddopacity')
723        res = renderchecker.runTest('expected_markerline_ddopacity')
724        self.report += renderchecker.report()
725        self.assertTrue(res)
726
727    def renderGeometry(self, symbol, geom, buffer=20):
728        f = QgsFeature()
729        f.setGeometry(geom)
730
731        image = QImage(200, 200, QImage.Format_RGB32)
732
733        painter = QPainter()
734        ms = QgsMapSettings()
735        extent = geom.get().boundingBox()
736        # buffer extent by 10%
737        if extent.width() > 0:
738            extent = extent.buffered((extent.height() + extent.width()) / buffer)
739        else:
740            extent = extent.buffered(buffer / 2)
741
742        ms.setExtent(extent)
743        ms.setOutputSize(image.size())
744        context = QgsRenderContext.fromMapSettings(ms)
745        context.setPainter(painter)
746        context.setScaleFactor(96 / 25.4)  # 96 DPI
747        context.expressionContext().setFeature(f)
748
749        painter.begin(image)
750        try:
751            image.fill(QColor(0, 0, 0))
752            symbol.startRender(context)
753            symbol.renderFeature(f, context)
754            symbol.stopRender(context)
755        finally:
756            painter.end()
757
758        return image
759
760    def imageCheck(self, name, reference_image, image):
761        self.report += "<h2>Render {}</h2>\n".format(name)
762        temp_dir = QDir.tempPath() + '/'
763        file_name = temp_dir + 'symbol_' + name + ".png"
764        image.save(file_name, "PNG")
765        checker = QgsRenderChecker()
766        checker.setControlPathPrefix("symbol_markerline")
767        checker.setControlName("expected_" + reference_image)
768        checker.setRenderedImage(file_name)
769        checker.setColorTolerance(2)
770        result = checker.compareImages(name, 20)
771        self.report += checker.report()
772        print((self.report))
773        return result
774
775
776if __name__ == '__main__':
777    unittest.main()
778