Allow for exact calculation of symbol sizes with mixed layer units

Fixes #21143
This commit is contained in:
Nyall Dawson 2019-02-04 10:48:41 +10:00
parent 427cb0b27a
commit 867e39947b
13 changed files with 383 additions and 15 deletions

View File

@ -223,6 +223,8 @@ Create a new MarkerLineSymbolLayerV2 from SLD
virtual double width() const;
virtual double width( const QgsRenderContext &context ) const;
virtual double estimateMaxBleed( const QgsRenderContext &context ) const;

View File

@ -687,6 +687,10 @@ Will take ownership.
class QgsMarkerSymbol : QgsSymbol
{
%Docstring
A marker symbol type, for rendering Point and MultiPoint geometries.
%End
%TypeHeaderCode
#include "qgssymbol.h"
@ -700,6 +704,11 @@ This is a convenience method for easier creation of marker symbols.
%End
QgsMarkerSymbol( const QgsSymbolLayerList &layers /Transfer/ = QgsSymbolLayerList() );
%Docstring
Constructor for QgsMarkerSymbol, with the specified list of initial symbol ``layers``.
Ownership of the ``layers`` are transferred to the symbol.
%End
void setAngle( double symbolAngle );
%Docstring
@ -770,14 +779,37 @@ will be scaled to maintain their current relative size to the whole symbol size.
double size() const;
%Docstring
Returns the size for the whole symbol, which is the maximum size of
Returns the estimated size for the whole symbol, which is the maximum size of
all marker symbol layers in the symbol.
.. warning::
This returned value is inaccurate if the symbol consists of multiple
symbol layers with different size units. Use the overload accepting a :py:class:`QgsRenderContext`
argument instead for accurate sizes in this case.
.. seealso:: :py:func:`setSize`
.. seealso:: :py:func:`sizeUnit`
.. seealso:: :py:func:`sizeMapUnitScale`
%End
double size( const QgsRenderContext &context ) const;
%Docstring
Returns the symbol size, in painter units. This is the maximum size of
all marker symbol layers in the symbol.
This method returns an accurate size by calculating the actual rendered
size of each symbol layer using the provided render ``context``.
.. seealso:: :py:func:`setSize`
.. seealso:: :py:func:`sizeUnit`
.. seealso:: :py:func:`sizeMapUnitScale`
.. versionadded:: 3.4.5
%End
void setSizeUnit( QgsUnitTypes::RenderUnit unit );
@ -890,6 +922,10 @@ and stopRender() calls, or data defined rotation and offset will not be correctl
class QgsLineSymbol : QgsSymbol
{
%Docstring
A line symbol type, for rendering LineString and MultiLineString geometries.
%End
%TypeHeaderCode
#include "qgssymbol.h"
@ -903,9 +939,46 @@ This is a convenience method for easier creation of line symbols.
%End
QgsLineSymbol( const QgsSymbolLayerList &layers /Transfer/ = QgsSymbolLayerList() );
%Docstring
Constructor for QgsLineSymbol, with the specified list of initial symbol ``layers``.
Ownership of the ``layers`` are transferred to the symbol.
%End
void setWidth( double width );
%Docstring
Sets the ``width`` for the whole line symbol. Individual symbol layer sizes
will be scaled to maintain their current relative size to the whole symbol size.
.. seealso:: :py:func:`width`
%End
double width() const;
%Docstring
Returns the estimated width for the whole symbol, which is the maximum width of
all marker symbol layers in the symbol.
.. warning::
This returned value is inaccurate if the symbol consists of multiple
symbol layers with different width units. Use the overload accepting a :py:class:`QgsRenderContext`
argument instead for accurate sizes in this case.
.. seealso:: :py:func:`setWidth`
%End
double width( const QgsRenderContext &context ) const;
%Docstring
Returns the symbol width, in painter units. This is the maximum width of
all marker symbol layers in the symbol.
This method returns an accurate width by calculating the actual rendered
width of each symbol layer using the provided render ``context``.
.. seealso:: :py:func:`setWidth`
.. versionadded:: 3.4.5
%End
void setDataDefinedWidth( const QgsProperty &property );
%Docstring
@ -938,6 +1011,10 @@ Returns data defined width for whole symbol (including all symbol layers).
class QgsFillSymbol : QgsSymbol
{
%Docstring
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
%End
%TypeHeaderCode
#include "qgssymbol.h"
@ -951,6 +1028,11 @@ This is a convenience method for easier creation of fill symbols.
%End
QgsFillSymbol( const QgsSymbolLayerList &layers /Transfer/ = QgsSymbolLayerList() );
%Docstring
Constructor for QgsFillSymbol, with the specified list of initial symbol ``layers``.
Ownership of the ``layers`` are transferred to the symbol.
%End
void setAngle( double angle );
void renderPolygon( const QPolygonF &points, QList<QPolygonF> *rings, const QgsFeature *f, QgsRenderContext &context, int layer = -1, bool selected = false );

View File

@ -840,7 +840,31 @@ class QgsLineSymbolLayer : QgsSymbolLayer
virtual void renderPolygonStroke( const QPolygonF &points, QList<QPolygonF> *rings, QgsSymbolRenderContext &context );
virtual void setWidth( double width );
virtual double width() const;
%Docstring
Returns the estimated width for the line symbol layer.
.. warning::
This returned value is inaccurate if the symbol layer has sub-symbols with
different width units. Use the overload accepting a :py:class:`QgsRenderContext`
argument instead for accurate sizes in this case.
.. seealso:: :py:func:`setWidth`
%End
virtual double width( const QgsRenderContext &context ) const;
%Docstring
Returns the line symbol layer width, in painter units.
This method returns an accurate width by calculating the actual rendered
width of the symbol layer using the provided render ``context``.
.. seealso:: :py:func:`setWidth`
.. versionadded:: 3.4.5
%End
double offset() const;
void setOffset( double offset );

View File

@ -421,7 +421,7 @@ QSizeF QgsSymbolLegendNode::drawSymbol( const QgsLegendSettings &settings, ItemC
if ( QgsMarkerSymbol *markerSymbol = dynamic_cast<QgsMarkerSymbol *>( s ) )
{
// allow marker symbol to occupy bigger area if necessary
double size = context.convertToPainterUnits( markerSymbol->size(), markerSymbol->sizeUnit(), markerSymbol->sizeMapUnitScale() ) / context.scaleFactor();
double size = markerSymbol->size( context ) / context.scaleFactor();
height = size;
width = size;
if ( width < settings.symbolSize().width() )

View File

@ -1113,7 +1113,8 @@ void QgsMarkerLineSymbolLayer::renderPolylineVertex( const QPolygonF &points, Qg
|| ( placement == CurvePoint && vId.type == QgsVertexId::CurveVertex ) )
{
//transform
x = vPoint.x(), y = vPoint.y();
x = vPoint.x();
y = vPoint.y();
z = 0.0;
if ( ct.isValid() )
{
@ -1618,6 +1619,11 @@ double QgsMarkerLineSymbolLayer::width() const
return mMarker->size();
}
double QgsMarkerLineSymbolLayer::width( const QgsRenderContext &context ) const
{
return mMarker->size( context );
}
void QgsMarkerLineSymbolLayer::setOutputUnit( QgsUnitTypes::RenderUnit unit )
{
QgsLineSymbolLayer::setOutputUnit( unit );
@ -1675,7 +1681,7 @@ bool QgsMarkerLineSymbolLayer::hasDataDefinedProperties() const
double QgsMarkerLineSymbolLayer::estimateMaxBleed( const QgsRenderContext &context ) const
{
return context.convertToPainterUnits( ( mMarker->size() / 2.0 ), mMarker->sizeUnit(), mMarker->sizeMapUnitScale() ) +
return ( mMarker->size( context ) / 2.0 ) +
context.convertToPainterUnits( std::fabs( mOffset ), mOffsetUnit, mOffsetMapUnitScale );
}

View File

@ -234,6 +234,7 @@ class CORE_EXPORT QgsMarkerLineSymbolLayer : public QgsLineSymbolLayer
void setWidth( double width ) override;
double width() const override;
double width( const QgsRenderContext &context ) const override;
double estimateMaxBleed( const QgsRenderContext &context ) const override;

View File

@ -61,12 +61,11 @@ void QgsPointDisplacementRenderer::drawGroup( QPointF centerPoint, QgsRenderCont
//calculate max diagonal size from all symbols in group
double diagonal = 0;
Q_FOREACH ( const GroupedFeature &feature, group )
for ( const GroupedFeature &feature : group )
{
if ( QgsMarkerSymbol *symbol = feature.symbol() )
{
diagonal = std::max( diagonal, context.convertToPainterUnits( M_SQRT2 * symbol->size(),
symbol->sizeUnit(), symbol->sizeMapUnitScale() ) );
diagonal = std::max( diagonal, M_SQRT2 * symbol->size( context ) );
}
}
@ -274,8 +273,7 @@ void QgsPointDisplacementRenderer::calculateSymbolAndLabelPositions( QgsSymbolRe
}
case ConcentricRings:
{
double centerDiagonal = symbolContext.renderContext().convertToPainterUnits( M_SQRT2 * mCenterSymbol->size(),
mCenterSymbol->sizeUnit(), mCenterSymbol->sizeMapUnitScale() );
double centerDiagonal = mCenterSymbol->size( symbolContext.renderContext() ) * M_SQRT2;
int pointsRemaining = nPosition;
int ringNumber = 1;
@ -307,8 +305,7 @@ void QgsPointDisplacementRenderer::calculateSymbolAndLabelPositions( QgsSymbolRe
}
case Grid:
{
double centerDiagonal = symbolContext.renderContext().convertToPainterUnits( M_SQRT2 * mCenterSymbol->size(),
mCenterSymbol->sizeUnit(), mCenterSymbol->sizeMapUnitScale() );
double centerDiagonal = mCenterSymbol->size( symbolContext.renderContext() ) * M_SQRT2;
int pointsRemaining = nPosition;
gridSize = std::ceil( std::sqrt( pointsRemaining ) );
if ( pointsRemaining - std::pow( gridSize - 1, 2 ) < gridSize )

View File

@ -1312,6 +1312,21 @@ double QgsMarkerSymbol::size() const
return maxSize;
}
double QgsMarkerSymbol::size( const QgsRenderContext &context ) const
{
// return size of the largest symbol
double maxSize = 0;
for ( QgsSymbolLayer *layer : mLayers )
{
if ( layer->type() != QgsSymbol::Marker )
continue;
const QgsMarkerSymbolLayer *markerLayer = static_cast<const QgsMarkerSymbolLayer *>( layer );
const double layerSize = context.convertToPainterUnits( markerLayer->size(), markerLayer->sizeUnit(), markerLayer->sizeMapUnitScale() );
maxSize = std::max( maxSize, layerSize );
}
return maxSize;
}
void QgsMarkerSymbol::setSizeUnit( QgsUnitTypes::RenderUnit unit )
{
Q_FOREACH ( QgsSymbolLayer *layer, mLayers )
@ -1634,6 +1649,21 @@ double QgsLineSymbol::width() const
return maxWidth;
}
double QgsLineSymbol::width( const QgsRenderContext &context ) const
{
// return width of the largest symbol
double maxWidth = 0;
for ( QgsSymbolLayer *layer : mLayers )
{
if ( layer->type() != QgsSymbol::Line )
continue;
const QgsLineSymbolLayer *lineLayer = static_cast<const QgsLineSymbolLayer *>( layer );
const double layerWidth = lineLayer->width( context );
maxWidth = std::max( maxWidth, layerWidth );
}
return maxWidth;
}
void QgsLineSymbol::setDataDefinedWidth( const QgsProperty &property )
{
const double symbolWidth = width();

View File

@ -726,6 +726,8 @@ class CORE_EXPORT QgsSymbolRenderContext
/**
* \ingroup core
* \class QgsMarkerSymbol
*
* A marker symbol type, for rendering Point and MultiPoint geometries.
*/
class CORE_EXPORT QgsMarkerSymbol : public QgsSymbol
{
@ -737,6 +739,11 @@ class CORE_EXPORT QgsMarkerSymbol : public QgsSymbol
*/
static QgsMarkerSymbol *createSimple( const QgsStringMap &properties ) SIP_FACTORY;
/**
* Constructor for QgsMarkerSymbol, with the specified list of initial symbol \a layers.
*
* Ownership of the \a layers are transferred to the symbol.
*/
QgsMarkerSymbol( const QgsSymbolLayerList &layers SIP_TRANSFER = QgsSymbolLayerList() );
/**
@ -792,14 +799,34 @@ class CORE_EXPORT QgsMarkerSymbol : public QgsSymbol
void setSize( double size );
/**
* Returns the size for the whole symbol, which is the maximum size of
* Returns the estimated size for the whole symbol, which is the maximum size of
* all marker symbol layers in the symbol.
*
* \warning This returned value is inaccurate if the symbol consists of multiple
* symbol layers with different size units. Use the overload accepting a QgsRenderContext
* argument instead for accurate sizes in this case.
*
* \see setSize()
* \see sizeUnit()
* \see sizeMapUnitScale()
*/
double size() const;
/**
* Returns the symbol size, in painter units. This is the maximum size of
* all marker symbol layers in the symbol.
*
* This method returns an accurate size by calculating the actual rendered
* size of each symbol layer using the provided render \a context.
*
* \see setSize()
* \see sizeUnit()
* \see sizeMapUnitScale()
*
* \since QGIS 3.4.5
*/
double size( const QgsRenderContext &context ) const;
/**
* Sets the size units for the whole symbol (including all symbol layers).
* \param unit size units
@ -887,6 +914,8 @@ class CORE_EXPORT QgsMarkerSymbol : public QgsSymbol
/**
* \ingroup core
* \class QgsLineSymbol
*
* A line symbol type, for rendering LineString and MultiLineString geometries.
*/
class CORE_EXPORT QgsLineSymbol : public QgsSymbol
{
@ -898,11 +927,46 @@ class CORE_EXPORT QgsLineSymbol : public QgsSymbol
*/
static QgsLineSymbol *createSimple( const QgsStringMap &properties ) SIP_FACTORY;
/**
* Constructor for QgsLineSymbol, with the specified list of initial symbol \a layers.
*
* Ownership of the \a layers are transferred to the symbol.
*/
QgsLineSymbol( const QgsSymbolLayerList &layers SIP_TRANSFER = QgsSymbolLayerList() );
/**
* Sets the \a width for the whole line symbol. Individual symbol layer sizes
* will be scaled to maintain their current relative size to the whole symbol size.
*
* \see width()
*/
void setWidth( double width );
/**
* Returns the estimated width for the whole symbol, which is the maximum width of
* all marker symbol layers in the symbol.
*
* \warning This returned value is inaccurate if the symbol consists of multiple
* symbol layers with different width units. Use the overload accepting a QgsRenderContext
* argument instead for accurate sizes in this case.
*
* \see setWidth()
*/
double width() const;
/**
* Returns the symbol width, in painter units. This is the maximum width of
* all marker symbol layers in the symbol.
*
* This method returns an accurate width by calculating the actual rendered
* width of each symbol layer using the provided render \a context.
*
* \see setWidth()
*
* \since QGIS 3.4.5
*/
double width( const QgsRenderContext &context ) const;
/**
* Set data defined width for whole symbol (including all symbol layers).
* \see dataDefinedWidth()
@ -933,6 +997,8 @@ class CORE_EXPORT QgsLineSymbol : public QgsSymbol
/**
* \ingroup core
* \class QgsFillSymbol
*
* A fill symbol type, for rendering Polygon and MultiPolygon geometries.
*/
class CORE_EXPORT QgsFillSymbol : public QgsSymbol
{
@ -944,6 +1010,11 @@ class CORE_EXPORT QgsFillSymbol : public QgsSymbol
*/
static QgsFillSymbol *createSimple( const QgsStringMap &properties ) SIP_FACTORY;
/**
* Constructor for QgsFillSymbol, with the specified list of initial symbol \a layers.
*
* Ownership of the \a layers are transferred to the symbol.
*/
QgsFillSymbol( const QgsSymbolLayerList &layers SIP_TRANSFER = QgsSymbolLayerList() );
void setAngle( double angle );
void renderPolygon( const QPolygonF &points, QList<QPolygonF> *rings, const QgsFeature *f, QgsRenderContext &context, int layer = -1, bool selected = false );

View File

@ -656,6 +656,11 @@ void QgsLineSymbolLayer::renderPolygonStroke( const QPolygonF &points, QList<QPo
}
}
double QgsLineSymbolLayer::width( const QgsRenderContext &context ) const
{
return context.convertToPainterUnits( mWidth, mWidthUnit, mWidthMapUnitScale );
}
double QgsLineSymbolLayer::dxfWidth( const QgsDxfExport &e, QgsSymbolRenderContext &context ) const
{
Q_UNUSED( context );

View File

@ -791,8 +791,30 @@ class CORE_EXPORT QgsLineSymbolLayer : public QgsSymbolLayer
virtual void renderPolygonStroke( const QPolygonF &points, QList<QPolygonF> *rings, QgsSymbolRenderContext &context );
virtual void setWidth( double width ) { mWidth = width; }
/**
* Returns the estimated width for the line symbol layer.
*
* \warning This returned value is inaccurate if the symbol layer has sub-symbols with
* different width units. Use the overload accepting a QgsRenderContext
* argument instead for accurate sizes in this case.
*
* \see setWidth()
*/
virtual double width() const { return mWidth; }
/**
* Returns the line symbol layer width, in painter units.
*
* This method returns an accurate width by calculating the actual rendered
* width of the symbol layer using the provided render \a context.
*
* \see setWidth()
*
* \since QGIS 3.4.5
*/
virtual double width( const QgsRenderContext &context ) const;
double offset() const { return mOffset; }
void setOffset( double offset ) { mOffset = offset; }

View File

@ -27,7 +27,7 @@ import qgis # NOQA
from utilities import unitTestDataPath
from qgis.PyQt.QtCore import QDir, Qt
from qgis.PyQt.QtCore import QDir, Qt, QSize
from qgis.PyQt.QtGui import QImage, QColor, QPainter
from qgis.PyQt.QtXml import QDomDocument
@ -49,7 +49,9 @@ from qgis.core import (QgsGeometry,
QgsFontUtils,
QgsLineSymbol,
QgsSymbolLayer,
QgsProperty
QgsProperty,
QgsRectangle,
QgsUnitTypes
)
from qgis.testing import unittest, start_app
@ -68,6 +70,38 @@ class TestQgsMarkerLineSymbolLayer(unittest.TestCase):
with open(report_file_path, 'a') as report_file:
report_file.write(self.report)
def testWidth(self):
ms = QgsMapSettings()
extent = QgsRectangle(100, 200, 100, 200)
ms.setExtent(extent)
ms.setOutputSize(QSize(400, 400))
context = QgsRenderContext.fromMapSettings(ms)
context.setScaleFactor(96 / 25.4) # 96 DPI
ms.setExtent(QgsRectangle(100, 150, 100, 150))
ms.setOutputDpi(ms.outputDpi() * 2)
context2 = QgsRenderContext.fromMapSettings(ms)
context2.setScaleFactor(300 / 25.4)
s = QgsFillSymbol()
s.deleteSymbolLayer(0)
marker_line = QgsMarkerLineSymbolLayer(True)
marker_line.setPlacement(QgsMarkerLineSymbolLayer.FirstVertex)
marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 10)
marker.setColor(QColor(255, 0, 0))
marker.setStrokeStyle(Qt.NoPen)
marker_symbol = QgsMarkerSymbol()
marker_symbol.changeSymbolLayer(0, marker)
marker_line.setSubSymbol(marker_symbol)
self.assertEqual(marker_line.width(), 10)
self.assertAlmostEqual(marker_line.width(context), 37.795275590551185, 3)
self.assertAlmostEqual(marker_line.width(context2), 118.11023622047244, 3)
marker_line.subSymbol().setSizeUnit(QgsUnitTypes.RenderPixels)
self.assertAlmostEqual(marker_line.width(context), 10.0, 3)
self.assertAlmostEqual(marker_line.width(context2), 10.0, 3)
def testRingFilter(self):
# test filtering rings during rendering
s = QgsFillSymbol()

View File

@ -27,7 +27,7 @@ import qgis # NOQA
from utilities import unitTestDataPath
from qgis.PyQt.QtCore import QDir, Qt
from qgis.PyQt.QtCore import QDir, Qt, QSize
from qgis.PyQt.QtGui import QImage, QColor, QPainter
from qgis.PyQt.QtXml import QDomDocument
@ -48,6 +48,7 @@ from qgis.core import (QgsGeometry,
QgsRenderChecker,
QgsSimpleMarkerSymbolLayer,
QgsSimpleMarkerSymbolLayerBase,
QgsSimpleLineSymbolLayer,
QgsSimpleFillSymbolLayer,
QgsUnitTypes,
QgsWkbTypes,
@ -509,6 +510,16 @@ class TestQgsMarkerSymbol(unittest.TestCase):
def testSize(self):
# test size and setSize
ms = QgsMapSettings()
extent = QgsRectangle(100, 200, 100, 200)
ms.setExtent(extent)
ms.setOutputSize(QSize(400, 400))
context = QgsRenderContext.fromMapSettings(ms)
context.setScaleFactor(96 / 25.4) # 96 DPI
ms.setExtent(QgsRectangle(100, 150, 100, 150))
ms.setOutputDpi(ms.outputDpi() * 2)
context2 = QgsRenderContext.fromMapSettings(ms)
context2.setScaleFactor(300 / 25.4)
# create a marker symbol with a single layer
markerSymbol = QgsMarkerSymbol()
@ -517,9 +528,13 @@ class TestQgsMarkerSymbol(unittest.TestCase):
QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayerBase.Star, color=QColor(255, 0, 0),
strokeColor=QColor(0, 255, 0), size=10))
self.assertEqual(markerSymbol.size(), 10)
self.assertAlmostEqual(markerSymbol.size(context), 37.795275590551185, 3)
self.assertAlmostEqual(markerSymbol.size(context2), 118.11023622047244, 3)
markerSymbol.setSize(20)
self.assertEqual(markerSymbol.size(), 20)
self.assertEqual(markerSymbol.symbolLayer(0).size(), 20)
self.assertAlmostEqual(markerSymbol.size(context), 75.59055118, 3)
self.assertAlmostEqual(markerSymbol.size(context2), 236.2204724409449, 3)
# add additional layers
markerSymbol.appendSymbolLayer(
@ -529,6 +544,9 @@ class TestQgsMarkerSymbol(unittest.TestCase):
QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayerBase.Star, color=QColor(255, 0, 0),
strokeColor=QColor(0, 255, 0), size=30))
self.assertEqual(markerSymbol.size(), 30)
self.assertAlmostEqual(markerSymbol.size(context), 113.38582677165356, 3)
self.assertAlmostEqual(markerSymbol.size(context2), 354.33070866141736, 3)
markerSymbol.setSize(3)
self.assertEqual(markerSymbol.size(), 3)
# layer sizes should maintain relative size
@ -536,6 +554,17 @@ class TestQgsMarkerSymbol(unittest.TestCase):
self.assertEqual(markerSymbol.symbolLayer(1).size(), 1)
self.assertEqual(markerSymbol.symbolLayer(2).size(), 3)
# symbol layer in different size
markerSymbol.symbolLayer(1).setSize(15)
self.assertAlmostEqual(markerSymbol.size(context), 56.69291338582678, 3)
self.assertAlmostEqual(markerSymbol.size(context2), 177.16535433070868, 3)
markerSymbol.symbolLayer(1).setSizeUnit(QgsUnitTypes.RenderPixels)
self.assertAlmostEqual(markerSymbol.size(context), 15, 3)
self.assertAlmostEqual(markerSymbol.size(context2), 35.43307086614173, 3)
markerSymbol.symbolLayer(1).setSize(45)
self.assertAlmostEqual(markerSymbol.size(context), 45, 3)
self.assertAlmostEqual(markerSymbol.size(context2), 45, 3)
def testAngle(self):
# test angle and setAngle
@ -628,6 +657,71 @@ class TestQgsMarkerSymbol(unittest.TestCase):
self.assertEqual(markerSymbol.symbolLayer(2).sizeMapUnitScale(), QgsMapUnitScale(3000, 4000))
class TestQgsLineSymbol(unittest.TestCase):
def setUp(self):
self.report = "<h1>Python QgsLineSymbol 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 testWidth(self):
# test width and setWidth
ms = QgsMapSettings()
extent = QgsRectangle(100, 200, 100, 200)
ms.setExtent(extent)
ms.setOutputSize(QSize(400, 400))
context = QgsRenderContext.fromMapSettings(ms)
context.setScaleFactor(96 / 25.4) # 96 DPI
ms.setExtent(QgsRectangle(100, 150, 100, 150))
ms.setOutputDpi(ms.outputDpi() * 2)
context2 = QgsRenderContext.fromMapSettings(ms)
context2.setScaleFactor(300 / 25.4)
# create a line symbol with a single layer
line_symbol = QgsLineSymbol()
line_symbol.deleteSymbolLayer(0)
line_symbol.appendSymbolLayer(
QgsSimpleLineSymbolLayer(color=QColor(255, 0, 0), width=10))
self.assertEqual(line_symbol.width(), 10)
self.assertAlmostEqual(line_symbol.width(context), 37.795275590551185, 3)
self.assertAlmostEqual(line_symbol.width(context2), 118.11023622047244, 3)
line_symbol.setWidth(20)
self.assertEqual(line_symbol.width(), 20)
self.assertEqual(line_symbol.symbolLayer(0).width(), 20)
self.assertAlmostEqual(line_symbol.width(context), 75.59055118, 3)
self.assertAlmostEqual(line_symbol.width(context2), 236.2204724409449, 3)
# add additional layers
line_symbol.appendSymbolLayer(
QgsSimpleLineSymbolLayer(color=QColor(255, 0, 0), width=10))
line_symbol.appendSymbolLayer(
QgsSimpleLineSymbolLayer(color=QColor(255, 0, 0), width=30))
self.assertEqual(line_symbol.width(), 30)
self.assertAlmostEqual(line_symbol.width(context), 113.38582677165356, 3)
self.assertAlmostEqual(line_symbol.width(context2), 354.33070866141736, 3)
line_symbol.setWidth(3)
self.assertEqual(line_symbol.width(), 3)
# layer widths should maintain relative size
self.assertEqual(line_symbol.symbolLayer(0).width(), 2)
self.assertEqual(line_symbol.symbolLayer(1).width(), 1)
self.assertEqual(line_symbol.symbolLayer(2).width(), 3)
# symbol layer in different size
line_symbol.symbolLayer(1).setWidth(15)
self.assertAlmostEqual(line_symbol.width(context), 56.69291338582678, 3)
self.assertAlmostEqual(line_symbol.width(context2), 177.16535433070868, 3)
line_symbol.symbolLayer(1).setWidthUnit(QgsUnitTypes.RenderPixels)
self.assertAlmostEqual(line_symbol.width(context), 15, 3)
self.assertAlmostEqual(line_symbol.width(context2), 35.43307086614173, 3)
line_symbol.symbolLayer(1).setWidth(45)
self.assertAlmostEqual(line_symbol.width(context), 45, 3)
self.assertAlmostEqual(line_symbol.width(context2), 45, 3)
class TestQgsFillSymbol(unittest.TestCase):
def setUp(self):