mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
Fix rendering offset lines as part of fill symbol (outside of
vector layers) results in broken offset outline
This commit is contained in:
parent
f80b92a23f
commit
10c40dcaab
@ -357,6 +357,9 @@ class QgsSymbolRenderContext
|
||||
//! Current feature being rendered - may be null
|
||||
const QgsFeature* feature() const;
|
||||
|
||||
void setOriginalGeometryType( QgsWkbTypes::GeometryType type );
|
||||
QgsWkbTypes::GeometryType originalGeometryType() const;
|
||||
|
||||
//! Fields of the layer. Currently only available in startRender() calls
|
||||
//! to allow symbols with data-defined properties prepare the expressions
|
||||
//! (other times fields() returns null)
|
||||
|
@ -328,7 +328,7 @@ void QgsSimpleLineSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbo
|
||||
else
|
||||
{
|
||||
double scaledOffset = context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
|
||||
QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.feature() ? context.feature()->geometry().type() : QgsWkbTypes::LineGeometry );
|
||||
QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
|
||||
for ( int part = 0; part < mline.count(); ++part )
|
||||
{
|
||||
#if 0
|
||||
@ -876,7 +876,7 @@ void QgsMarkerLineSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbo
|
||||
else
|
||||
{
|
||||
context.renderContext().setGeometry( nullptr ); //always use segmented geometry with offset
|
||||
QList<QPolygonF> mline = ::offsetLine( points, context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale ), context.feature() ? context.feature()->geometry().type() : QgsWkbTypes::LineGeometry );
|
||||
QList<QPolygonF> mline = ::offsetLine( points, context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale ), context.originalGeometryType() != QgsWkbTypes::UnknownGeometry ? context.originalGeometryType() : QgsWkbTypes::LineGeometry );
|
||||
|
||||
for ( int part = 0; part < mline.count(); ++part )
|
||||
{
|
||||
|
@ -1629,6 +1629,7 @@ void QgsLineSymbol::renderPolyline( const QPolygonF& points, const QgsFeature* f
|
||||
//save old painter
|
||||
QPainter* renderPainter = context.painter();
|
||||
QgsSymbolRenderContext symbolContext( context, outputUnit(), mAlpha, selected, mRenderHints, f, QgsFields(), mapUnitScale() );
|
||||
symbolContext.setOriginalGeometryType( QgsWkbTypes::LineGeometry );
|
||||
symbolContext.setGeometryPartCount( symbolRenderContext()->geometryPartCount() );
|
||||
symbolContext.setGeometryPartNum( symbolRenderContext()->geometryPartNum() );
|
||||
|
||||
@ -1709,6 +1710,7 @@ QgsFillSymbol::QgsFillSymbol( const QgsSymbolLayerList& layers )
|
||||
void QgsFillSymbol::renderPolygon( const QPolygonF& points, QList<QPolygonF>* rings, const QgsFeature* f, QgsRenderContext& context, int layerIdx, bool selected )
|
||||
{
|
||||
QgsSymbolRenderContext symbolContext( context, outputUnit(), mAlpha, selected, mRenderHints, f, QgsFields(), mapUnitScale() );
|
||||
symbolContext.setOriginalGeometryType( QgsWkbTypes::PolygonGeometry );
|
||||
symbolContext.setGeometryPartCount( symbolRenderContext()->geometryPartCount() );
|
||||
symbolContext.setGeometryPartNum( symbolRenderContext()->geometryPartNum() );
|
||||
|
||||
|
@ -434,6 +434,23 @@ class CORE_EXPORT QgsSymbolRenderContext
|
||||
//! Current feature being rendered - may be null
|
||||
const QgsFeature* feature() const { return mFeature; }
|
||||
|
||||
/**
|
||||
* Sets the geometry type for the original feature geometry being rendered.
|
||||
* @see originalGeometryType()
|
||||
* @note added in QGIS 3.0
|
||||
*/
|
||||
void setOriginalGeometryType( QgsWkbTypes::GeometryType type ) { mOriginalGeometryType = type; }
|
||||
|
||||
/**
|
||||
* Returns the geometry type for the original feature geometry being rendered. This can be
|
||||
* useful if symbol layers alter their appearance based on geometry type - eg offsetting a
|
||||
* simple line style will look different if the simple line is rendering a polygon feature
|
||||
* (a closed buffer) vs a line feature (an unclosed offset line).
|
||||
* @see originalGeometryType()
|
||||
* @note added in QGIS 3.0
|
||||
*/
|
||||
QgsWkbTypes::GeometryType originalGeometryType() const { return mOriginalGeometryType; }
|
||||
|
||||
//! Fields of the layer. Currently only available in startRender() calls
|
||||
//! to allow symbols with data-defined properties prepare the expressions
|
||||
//! (other times fields() returns null)
|
||||
@ -494,6 +511,7 @@ class CORE_EXPORT QgsSymbolRenderContext
|
||||
QgsFields mFields;
|
||||
int mGeometryPartCount;
|
||||
int mGeometryPartNum;
|
||||
QgsWkbTypes::GeometryType mOriginalGeometryType = QgsWkbTypes::UnknownGeometry;
|
||||
|
||||
|
||||
QgsSymbolRenderContext( const QgsSymbolRenderContext& rh );
|
||||
|
@ -47,6 +47,7 @@ ADD_PYTHON_TEST(PyQgsExpression test_qgsexpression.py)
|
||||
ADD_PYTHON_TEST(PyQgsExpressionLineEdit test_qgsexpressionlineedit.py)
|
||||
ADD_PYTHON_TEST(PyQgsFeature test_qgsfeature.py)
|
||||
ADD_PYTHON_TEST(PyQgsFieldFormattersTest test_qgsfieldformatters.py)
|
||||
ADD_PYTHON_TEST(PyQgsFillSymbolLayers test_qgsfillsymbollayers.py)
|
||||
ADD_PYTHON_TEST(PyQgsProject test_qgsproject.py)
|
||||
ADD_PYTHON_TEST(PyQgsFeatureIterator test_qgsfeatureiterator.py)
|
||||
ADD_PYTHON_TEST(PyQgsField test_qgsfield.py)
|
||||
@ -61,6 +62,7 @@ ADD_PYTHON_TEST(PyQgsGeometryValidator test_qgsgeometryvalidator.py)
|
||||
ADD_PYTHON_TEST(PyQgsGraduatedSymbolRenderer test_qgsgraduatedsymbolrenderer.py)
|
||||
ADD_PYTHON_TEST(PyQgsInterval test_qgsinterval.py)
|
||||
ADD_PYTHON_TEST(PyQgsJSONUtils test_qgsjsonutils.py)
|
||||
ADD_PYTHON_TEST(PyQgsLineSymbolLayers test_qgslinesymbollayers.py)
|
||||
ADD_PYTHON_TEST(PyQgsMapCanvasAnnotationItem test_qgsmapcanvasannotationitem.py)
|
||||
ADD_PYTHON_TEST(PyQgsMapLayerModel test_qgsmaplayermodel.py)
|
||||
ADD_PYTHON_TEST(PyQgsMapUnitScale test_qgsmapunitscale.py)
|
||||
|
94
tests/src/python/test_qgsfillsymbollayers.py
Normal file
94
tests/src/python/test_qgsfillsymbollayers.py
Normal file
@ -0,0 +1,94 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""QGIS Unit tests for QgsFillSymbolLayers.
|
||||
|
||||
.. note:: This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
"""
|
||||
__author__ = 'Nyall Dawson'
|
||||
__date__ = '2017-01'
|
||||
__copyright__ = 'Copyright 2017, The QGIS Project'
|
||||
# This will get replaced with a git SHA1 when you do a git archive
|
||||
__revision__ = '$Format:%H$'
|
||||
|
||||
|
||||
import qgis # NOQA
|
||||
|
||||
from qgis.testing import unittest
|
||||
from qgis.PyQt.QtCore import QDir
|
||||
from qgis.PyQt.QtGui import (QImage,
|
||||
QPainter,
|
||||
QColor)
|
||||
from qgis.core import (QgsRenderChecker,
|
||||
QgsSimpleLineSymbolLayer,
|
||||
QgsMapSettings,
|
||||
QgsFillSymbol,
|
||||
QgsGeometry,
|
||||
QgsFeature,
|
||||
QgsRenderContext)
|
||||
|
||||
|
||||
class TestQgsFillSymbolLayers(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.report = "<h1>Python QgsFillSymbolLayer Tests</h1>\n"
|
||||
|
||||
def tearDown(self):
|
||||
report_file_path = "%s/qgistest.html" % QDir.tempPath()
|
||||
with open(report_file_path, 'a') as report_file:
|
||||
report_file.write(self.report)
|
||||
|
||||
def imageCheck(self, name, reference_image, image):
|
||||
self.report += "<h2>Render {}</h2>\n".format(name)
|
||||
temp_dir = QDir.tempPath() + '/'
|
||||
file_name = temp_dir + 'symbollayer_' + name + ".png"
|
||||
image.save(file_name, "PNG")
|
||||
checker = QgsRenderChecker()
|
||||
checker.setControlPathPrefix("symbol_layer")
|
||||
checker.setControlName("expected_" + reference_image)
|
||||
checker.setRenderedImage(file_name)
|
||||
checker.setColorTolerance(2)
|
||||
result = checker.compareImages(name, 0)
|
||||
self.report += checker.report()
|
||||
print((self.report))
|
||||
return result
|
||||
|
||||
def testSimpleLineWithOffset(self):
|
||||
""" test that rendering a polygon with simple line symbol with offset results in closed line"""
|
||||
layer = QgsSimpleLineSymbolLayer()
|
||||
layer.setOffset(-1)
|
||||
|
||||
symbol = QgsFillSymbol()
|
||||
symbol.changeSymbolLayer(0, layer)
|
||||
|
||||
image = QImage(200, 200, QImage.Format_RGB32)
|
||||
painter = QPainter()
|
||||
ms = QgsMapSettings()
|
||||
|
||||
geom = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0))')
|
||||
f = QgsFeature()
|
||||
f.setGeometry(geom)
|
||||
|
||||
extent = geom.geometry().boundingBox()
|
||||
# buffer extent by 10%
|
||||
extent = extent.buffer((extent.height() + extent.width()) / 20.0)
|
||||
|
||||
ms.setExtent(extent)
|
||||
ms.setOutputSize(image.size())
|
||||
context = QgsRenderContext.fromMapSettings(ms)
|
||||
context.setPainter(painter)
|
||||
context.setScaleFactor(96 / 25.4) # 96 DPI
|
||||
|
||||
painter.begin(image)
|
||||
image.fill(QColor(255, 255, 255))
|
||||
|
||||
symbol.startRender(context)
|
||||
symbol.renderFeature(f, context)
|
||||
symbol.stopRender(context)
|
||||
painter.end()
|
||||
|
||||
self.assertTrue(self.imageCheck('symbol_layer', 'fill_simpleline_offset', image))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
94
tests/src/python/test_qgslinesymbollayers.py
Normal file
94
tests/src/python/test_qgslinesymbollayers.py
Normal file
@ -0,0 +1,94 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""QGIS Unit tests for QgsLineSymbolLayers.
|
||||
|
||||
.. note:: This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
"""
|
||||
__author__ = 'Nyall Dawson'
|
||||
__date__ = '2017-01'
|
||||
__copyright__ = 'Copyright 2017, The QGIS Project'
|
||||
# This will get replaced with a git SHA1 when you do a git archive
|
||||
__revision__ = '$Format:%H$'
|
||||
|
||||
|
||||
import qgis # NOQA
|
||||
|
||||
from qgis.testing import unittest
|
||||
from qgis.PyQt.QtCore import QDir
|
||||
from qgis.PyQt.QtGui import (QImage,
|
||||
QPainter,
|
||||
QColor)
|
||||
from qgis.core import (QgsRenderChecker,
|
||||
QgsSimpleLineSymbolLayer,
|
||||
QgsMapSettings,
|
||||
QgsLineSymbol,
|
||||
QgsGeometry,
|
||||
QgsFeature,
|
||||
QgsRenderContext)
|
||||
|
||||
|
||||
class TestQgsLineSymbolLayers(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.report = "<h1>Python QgsLineSymbolLayer Tests</h1>\n"
|
||||
|
||||
def tearDown(self):
|
||||
report_file_path = "%s/qgistest.html" % QDir.tempPath()
|
||||
with open(report_file_path, 'a') as report_file:
|
||||
report_file.write(self.report)
|
||||
|
||||
def imageCheck(self, name, reference_image, image):
|
||||
self.report += "<h2>Render {}</h2>\n".format(name)
|
||||
temp_dir = QDir.tempPath() + '/'
|
||||
file_name = temp_dir + 'symbollayer_' + name + ".png"
|
||||
image.save(file_name, "PNG")
|
||||
checker = QgsRenderChecker()
|
||||
checker.setControlPathPrefix("symbol_layer")
|
||||
checker.setControlName("expected_" + reference_image)
|
||||
checker.setRenderedImage(file_name)
|
||||
checker.setColorTolerance(2)
|
||||
result = checker.compareImages(name, 0)
|
||||
self.report += checker.report()
|
||||
print((self.report))
|
||||
return result
|
||||
|
||||
def testSimpleLineWithOffset(self):
|
||||
""" test that rendering a simple line symbol with offset"""
|
||||
layer = QgsSimpleLineSymbolLayer()
|
||||
layer.setOffset(1)
|
||||
|
||||
symbol = QgsLineSymbol()
|
||||
symbol.changeSymbolLayer(0, layer)
|
||||
|
||||
image = QImage(200, 200, QImage.Format_RGB32)
|
||||
painter = QPainter()
|
||||
ms = QgsMapSettings()
|
||||
|
||||
geom = QgsGeometry.fromWkt('LineString (0 0, 10 0, 10 10, 0 10, 0 0)')
|
||||
f = QgsFeature()
|
||||
f.setGeometry(geom)
|
||||
|
||||
extent = geom.geometry().boundingBox()
|
||||
# buffer extent by 10%
|
||||
extent = extent.buffer((extent.height() + extent.width()) / 20.0)
|
||||
|
||||
ms.setExtent(extent)
|
||||
ms.setOutputSize(image.size())
|
||||
context = QgsRenderContext.fromMapSettings(ms)
|
||||
context.setPainter(painter)
|
||||
context.setScaleFactor(96 / 25.4) # 96 DPI
|
||||
|
||||
painter.begin(image)
|
||||
image.fill(QColor(255, 255, 255))
|
||||
|
||||
symbol.startRender(context)
|
||||
symbol.renderFeature(f, context)
|
||||
symbol.stopRender(context)
|
||||
painter.end()
|
||||
|
||||
self.assertTrue(self.imageCheck('symbol_layer', 'simpleline_offset', image))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Binary file not shown.
After Width: | Height: | Size: 646 B |
BIN
tests/testdata/control_images/symbol_layer/expected_simpleline_offset/expected_simpleline_offset.png
vendored
Normal file
BIN
tests/testdata/control_images/symbol_layer/expected_simpleline_offset/expected_simpleline_offset.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 652 B |
Loading…
x
Reference in New Issue
Block a user