Skip to content

Commit 10c40dc

Browse files
committedJan 30, 2017
Fix rendering offset lines as part of fill symbol (outside of
vector layers) results in broken offset outline
1 parent f80b92a commit 10c40dc

File tree

9 files changed

+215
-2
lines changed

9 files changed

+215
-2
lines changed
 

‎python/core/symbology-ng/qgssymbol.sip

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,9 @@ class QgsSymbolRenderContext
357357
//! Current feature being rendered - may be null
358358
const QgsFeature* feature() const;
359359

360+
void setOriginalGeometryType( QgsWkbTypes::GeometryType type );
361+
QgsWkbTypes::GeometryType originalGeometryType() const;
362+
360363
//! Fields of the layer. Currently only available in startRender() calls
361364
//! to allow symbols with data-defined properties prepare the expressions
362365
//! (other times fields() returns null)

‎src/core/symbology-ng/qgslinesymbollayer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ void QgsSimpleLineSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbo
328328
else
329329
{
330330
double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
331-
QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.feature() ? context.feature()->geometry().type() : QgsWkbTypes::LineGeometry );
331+
QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
332332
for ( int part = 0; part < mline.count(); ++part )
333333
{
334334
#if 0
@@ -876,7 +876,7 @@ void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbo
876876
else
877877
{
878878
context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
879-
QList<QPolygonF> mline = ::offsetLine( points, context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale ), context.feature() ? context.feature()->geometry().type() : QgsWkbTypes::LineGeometry );
879+
QList<QPolygonF> mline = ::offsetLine( points, context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale ), context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
880880

881881
for ( int part = 0; part < mline.count(); ++part )
882882
{

‎src/core/symbology-ng/qgssymbol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,6 +1629,7 @@ void QgsLineSymbol::renderPolyline( const QPolygonF& points, const QgsFeature* f
16291629
//save old painter
16301630
QPainter* renderPainter = context.painter();
16311631
QgsSymbolRenderContext symbolContext( context, outputUnit(), mAlpha, selected, mRenderHints, f, QgsFields(), mapUnitScale() );
1632+
symbolContext.setOriginalGeometryType( QgsWkbTypes::LineGeometry );
16321633
symbolContext.setGeometryPartCount( symbolRenderContext()->geometryPartCount() );
16331634
symbolContext.setGeometryPartNum( symbolRenderContext()->geometryPartNum() );
16341635

@@ -1709,6 +1710,7 @@ QgsFillSymbol::QgsFillSymbol( const QgsSymbolLayerList& layers )
17091710
void QgsFillSymbol::renderPolygon( const QPolygonF& points, QList<QPolygonF>* rings, const QgsFeature* f, QgsRenderContext& context, int layerIdx, bool selected )
17101711
{
17111712
QgsSymbolRenderContext symbolContext( context, outputUnit(), mAlpha, selected, mRenderHints, f, QgsFields(), mapUnitScale() );
1713+
symbolContext.setOriginalGeometryType( QgsWkbTypes::PolygonGeometry );
17121714
symbolContext.setGeometryPartCount( symbolRenderContext()->geometryPartCount() );
17131715
symbolContext.setGeometryPartNum( symbolRenderContext()->geometryPartNum() );
17141716

‎src/core/symbology-ng/qgssymbol.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,23 @@ class CORE_EXPORT QgsSymbolRenderContext
434434
//! Current feature being rendered - may be null
435435
const QgsFeature* feature() const { return mFeature; }
436436

437+
/**
438+
* Sets the geometry type for the original feature geometry being rendered.
439+
* @see originalGeometryType()
440+
* @note added in QGIS 3.0
441+
*/
442+
void setOriginalGeometryType( QgsWkbTypes::GeometryType type ) { mOriginalGeometryType = type; }
443+
444+
/**
445+
* Returns the geometry type for the original feature geometry being rendered. This can be
446+
* useful if symbol layers alter their appearance based on geometry type - eg offsetting a
447+
* simple line style will look different if the simple line is rendering a polygon feature
448+
* (a closed buffer) vs a line feature (an unclosed offset line).
449+
* @see originalGeometryType()
450+
* @note added in QGIS 3.0
451+
*/
452+
QgsWkbTypes::GeometryType originalGeometryType() const { return mOriginalGeometryType; }
453+
437454
//! Fields of the layer. Currently only available in startRender() calls
438455
//! to allow symbols with data-defined properties prepare the expressions
439456
//! (other times fields() returns null)
@@ -494,6 +511,7 @@ class CORE_EXPORT QgsSymbolRenderContext
494511
QgsFields mFields;
495512
int mGeometryPartCount;
496513
int mGeometryPartNum;
514+
QgsWkbTypes::GeometryType mOriginalGeometryType = QgsWkbTypes::UnknownGeometry;
497515

498516

499517
QgsSymbolRenderContext( const QgsSymbolRenderContext& rh );

‎tests/src/python/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ ADD_PYTHON_TEST(PyQgsExpression test_qgsexpression.py)
4747
ADD_PYTHON_TEST(PyQgsExpressionLineEdit test_qgsexpressionlineedit.py)
4848
ADD_PYTHON_TEST(PyQgsFeature test_qgsfeature.py)
4949
ADD_PYTHON_TEST(PyQgsFieldFormattersTest test_qgsfieldformatters.py)
50+
ADD_PYTHON_TEST(PyQgsFillSymbolLayers test_qgsfillsymbollayers.py)
5051
ADD_PYTHON_TEST(PyQgsProject test_qgsproject.py)
5152
ADD_PYTHON_TEST(PyQgsFeatureIterator test_qgsfeatureiterator.py)
5253
ADD_PYTHON_TEST(PyQgsField test_qgsfield.py)
@@ -61,6 +62,7 @@ ADD_PYTHON_TEST(PyQgsGeometryValidator test_qgsgeometryvalidator.py)
6162
ADD_PYTHON_TEST(PyQgsGraduatedSymbolRenderer test_qgsgraduatedsymbolrenderer.py)
6263
ADD_PYTHON_TEST(PyQgsInterval test_qgsinterval.py)
6364
ADD_PYTHON_TEST(PyQgsJSONUtils test_qgsjsonutils.py)
65+
ADD_PYTHON_TEST(PyQgsLineSymbolLayers test_qgslinesymbollayers.py)
6466
ADD_PYTHON_TEST(PyQgsMapCanvasAnnotationItem test_qgsmapcanvasannotationitem.py)
6567
ADD_PYTHON_TEST(PyQgsMapLayerModel test_qgsmaplayermodel.py)
6668
ADD_PYTHON_TEST(PyQgsMapUnitScale test_qgsmapunitscale.py)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsFillSymbolLayers.
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Nyall Dawson'
10+
__date__ = '2017-01'
11+
__copyright__ = 'Copyright 2017, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
16+
import qgis # NOQA
17+
18+
from qgis.testing import unittest
19+
from qgis.PyQt.QtCore import QDir
20+
from qgis.PyQt.QtGui import (QImage,
21+
QPainter,
22+
QColor)
23+
from qgis.core import (QgsRenderChecker,
24+
QgsSimpleLineSymbolLayer,
25+
QgsMapSettings,
26+
QgsFillSymbol,
27+
QgsGeometry,
28+
QgsFeature,
29+
QgsRenderContext)
30+
31+
32+
class TestQgsFillSymbolLayers(unittest.TestCase):
33+
34+
def setUp(self):
35+
self.report = "<h1>Python QgsFillSymbolLayer Tests</h1>\n"
36+
37+
def tearDown(self):
38+
report_file_path = "%s/qgistest.html" % QDir.tempPath()
39+
with open(report_file_path, 'a') as report_file:
40+
report_file.write(self.report)
41+
42+
def imageCheck(self, name, reference_image, image):
43+
self.report += "<h2>Render {}</h2>\n".format(name)
44+
temp_dir = QDir.tempPath() + '/'
45+
file_name = temp_dir + 'symbollayer_' + name + ".png"
46+
image.save(file_name, "PNG")
47+
checker = QgsRenderChecker()
48+
checker.setControlPathPrefix("symbol_layer")
49+
checker.setControlName("expected_" + reference_image)
50+
checker.setRenderedImage(file_name)
51+
checker.setColorTolerance(2)
52+
result = checker.compareImages(name, 0)
53+
self.report += checker.report()
54+
print((self.report))
55+
return result
56+
57+
def testSimpleLineWithOffset(self):
58+
""" test that rendering a polygon with simple line symbol with offset results in closed line"""
59+
layer = QgsSimpleLineSymbolLayer()
60+
layer.setOffset(-1)
61+
62+
symbol = QgsFillSymbol()
63+
symbol.changeSymbolLayer(0, layer)
64+
65+
image = QImage(200, 200, QImage.Format_RGB32)
66+
painter = QPainter()
67+
ms = QgsMapSettings()
68+
69+
geom = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0))')
70+
f = QgsFeature()
71+
f.setGeometry(geom)
72+
73+
extent = geom.geometry().boundingBox()
74+
# buffer extent by 10%
75+
extent = extent.buffer((extent.height() + extent.width()) / 20.0)
76+
77+
ms.setExtent(extent)
78+
ms.setOutputSize(image.size())
79+
context = QgsRenderContext.fromMapSettings(ms)
80+
context.setPainter(painter)
81+
context.setScaleFactor(96 / 25.4) # 96 DPI
82+
83+
painter.begin(image)
84+
image.fill(QColor(255, 255, 255))
85+
86+
symbol.startRender(context)
87+
symbol.renderFeature(f, context)
88+
symbol.stopRender(context)
89+
painter.end()
90+
91+
self.assertTrue(self.imageCheck('symbol_layer', 'fill_simpleline_offset', image))
92+
93+
if __name__ == '__main__':
94+
unittest.main()
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsLineSymbolLayers.
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Nyall Dawson'
10+
__date__ = '2017-01'
11+
__copyright__ = 'Copyright 2017, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
16+
import qgis # NOQA
17+
18+
from qgis.testing import unittest
19+
from qgis.PyQt.QtCore import QDir
20+
from qgis.PyQt.QtGui import (QImage,
21+
QPainter,
22+
QColor)
23+
from qgis.core import (QgsRenderChecker,
24+
QgsSimpleLineSymbolLayer,
25+
QgsMapSettings,
26+
QgsLineSymbol,
27+
QgsGeometry,
28+
QgsFeature,
29+
QgsRenderContext)
30+
31+
32+
class TestQgsLineSymbolLayers(unittest.TestCase):
33+
34+
def setUp(self):
35+
self.report = "<h1>Python QgsLineSymbolLayer Tests</h1>\n"
36+
37+
def tearDown(self):
38+
report_file_path = "%s/qgistest.html" % QDir.tempPath()
39+
with open(report_file_path, 'a') as report_file:
40+
report_file.write(self.report)
41+
42+
def imageCheck(self, name, reference_image, image):
43+
self.report += "<h2>Render {}</h2>\n".format(name)
44+
temp_dir = QDir.tempPath() + '/'
45+
file_name = temp_dir + 'symbollayer_' + name + ".png"
46+
image.save(file_name, "PNG")
47+
checker = QgsRenderChecker()
48+
checker.setControlPathPrefix("symbol_layer")
49+
checker.setControlName("expected_" + reference_image)
50+
checker.setRenderedImage(file_name)
51+
checker.setColorTolerance(2)
52+
result = checker.compareImages(name, 0)
53+
self.report += checker.report()
54+
print((self.report))
55+
return result
56+
57+
def testSimpleLineWithOffset(self):
58+
""" test that rendering a simple line symbol with offset"""
59+
layer = QgsSimpleLineSymbolLayer()
60+
layer.setOffset(1)
61+
62+
symbol = QgsLineSymbol()
63+
symbol.changeSymbolLayer(0, layer)
64+
65+
image = QImage(200, 200, QImage.Format_RGB32)
66+
painter = QPainter()
67+
ms = QgsMapSettings()
68+
69+
geom = QgsGeometry.fromWkt('LineString (0 0, 10 0, 10 10, 0 10, 0 0)')
70+
f = QgsFeature()
71+
f.setGeometry(geom)
72+
73+
extent = geom.geometry().boundingBox()
74+
# buffer extent by 10%
75+
extent = extent.buffer((extent.height() + extent.width()) / 20.0)
76+
77+
ms.setExtent(extent)
78+
ms.setOutputSize(image.size())
79+
context = QgsRenderContext.fromMapSettings(ms)
80+
context.setPainter(painter)
81+
context.setScaleFactor(96 / 25.4) # 96 DPI
82+
83+
painter.begin(image)
84+
image.fill(QColor(255, 255, 255))
85+
86+
symbol.startRender(context)
87+
symbol.renderFeature(f, context)
88+
symbol.stopRender(context)
89+
painter.end()
90+
91+
self.assertTrue(self.imageCheck('symbol_layer', 'simpleline_offset', image))
92+
93+
if __name__ == '__main__':
94+
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.