Skip to content
Permalink
Browse files
[api] Allow a symbology reference scale to be set on a QgsRenderContext
This should match the desired scale denominator for the rendered map,
eg 1000.0 for a 1:1000 map render. Set (by default) to -1 to disable
symbology scaling by reference scale.

The symbology reference scale is an optional property which specifies
the reference scale at which symbology in paper units (such as
millimeters or points) is fixed to. For instance, if \a scale is set
to 1000 then a 2mm thick line will be rendered at exactly 2mm thick
when a map is rendered at 1:1000, or 1mm thick when rendered at
1:2000, or 4mm thick at 1:500.
  • Loading branch information
nyalldawson committed Jun 28, 2021
1 parent 23048dd commit 54610b293cc8bf4f0fae8de5ad0c4ff6c39f59e4
@@ -335,6 +335,26 @@ for the rendered map, eg 1000.0 for a 1:1000 map render.
%End


double symbologyReferenceScale() const;
%Docstring
Returns the symbology reference ``scale``.

This represents the desired scale denominator for the rendered map, eg 1000.0 for a 1:1000 map render.
A value of -1 indicates that symbology scaling by reference scale is disabled.

The symbology reference scale is an optional property which specifies the reference
scale at which symbology in paper units (such a millimeters or points) is fixed
to. For instance, if the scale is 1000 then a 2mm thick line will be rendered at
exactly 2mm thick when a map is rendered at 1:1000, or 1mm thick when rendered at 1:2000, or 4mm thick at 1:500.

.. seealso:: :py:func:`setSymbologyReferenceScale`

.. seealso:: :py:func:`rendererScale`

.. versionadded:: 3.22
%End


QColor selectionColor() const;
%Docstring
Returns the color to use when rendering selected features.
@@ -451,6 +471,25 @@ Sets the renderer map scale. This should match the desired scale denominator
for the rendered map, eg 1000.0 for a 1:1000 map render.

.. seealso:: :py:func:`rendererScale`
%End

void setSymbologyReferenceScale( double scale );
%Docstring
Sets the symbology reference ``scale``.

This should match the desired scale denominator for the rendered map, eg 1000.0 for a 1:1000 map render.
Set to -1 to disable symbology scaling by reference scale.

The symbology reference scale is an optional property which specifies the reference
scale at which symbology in paper units (such a millimeters or points) is fixed
to. For instance, if ``scale`` is set to 1000 then a 2mm thick line will be rendered at
exactly 2mm thick when a map is rendered at 1:1000, or 1mm thick when rendered at 1:2000, or 4mm thick at 1:500.

.. seealso:: :py:func:`symbologyReferenceScale`

.. seealso:: :py:func:`rendererScale`

.. versionadded:: 3.22
%End

void setPainter( QPainter *p );
@@ -53,6 +53,7 @@ QgsRenderContext::QgsRenderContext( const QgsRenderContext &rh )
, mScaleFactor( rh.mScaleFactor )
, mDpiTarget( rh.mDpiTarget )
, mRendererScale( rh.mRendererScale )
, mSymbologyReferenceScale( rh.mSymbologyReferenceScale )
, mLabelingEngine( rh.mLabelingEngine )
, mSelectionColor( rh.mSelectionColor )
, mVectorSimplifyMethod( rh.mVectorSimplifyMethod )
@@ -91,6 +92,7 @@ QgsRenderContext &QgsRenderContext::operator=( const QgsRenderContext &rh )
mScaleFactor = rh.mScaleFactor;
mDpiTarget = rh.mDpiTarget;
mRendererScale = rh.mRendererScale;
mSymbologyReferenceScale = rh.mSymbologyReferenceScale;
mLabelingEngine = rh.mLabelingEngine;
mSelectionColor = rh.mSelectionColor;
mVectorSimplifyMethod = rh.mVectorSimplifyMethod;
@@ -381,13 +383,18 @@ double QgsRenderContext::convertToPainterUnits( double size, QgsUnitTypes::Rende
convertedSize = std::min( convertedSize, scale.maxSizeMM * mScaleFactor );
}

const double symbologyReferenceScaleFactor = mSymbologyReferenceScale > 1 ? mSymbologyReferenceScale / mRendererScale : 1;
convertedSize *= symbologyReferenceScaleFactor;

return convertedSize;
}

double QgsRenderContext::convertToMapUnits( double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale ) const
{
double mup = mMapToPixel.mapUnitsPerPixel();

const double symbologyReferenceScaleFactor = mSymbologyReferenceScale > 1 ? mSymbologyReferenceScale / mRendererScale : 1;

switch ( unit )
{
case QgsUnitTypes::RenderMetersInMapUnits:
@@ -425,19 +432,19 @@ double QgsRenderContext::convertToMapUnits( double size, QgsUnitTypes::RenderUni
}
case QgsUnitTypes::RenderMillimeters:
{
return size * mScaleFactor * mup;
return size * mScaleFactor * mup / symbologyReferenceScaleFactor;
}
case QgsUnitTypes::RenderPoints:
{
return size * mScaleFactor * mup / POINTS_TO_MM;
return size * mScaleFactor * mup / POINTS_TO_MM / symbologyReferenceScaleFactor;
}
case QgsUnitTypes::RenderInches:
{
return size * mScaleFactor * mup * INCH_TO_MM;
return size * mScaleFactor * mup * INCH_TO_MM / symbologyReferenceScaleFactor;
}
case QgsUnitTypes::RenderPixels:
{
return size * mup;
return size * mup / symbologyReferenceScaleFactor;
}

case QgsUnitTypes::RenderUnknownUnit:
@@ -451,6 +458,7 @@ double QgsRenderContext::convertToMapUnits( double size, QgsUnitTypes::RenderUni
double QgsRenderContext::convertFromMapUnits( double sizeInMapUnits, QgsUnitTypes::RenderUnit outputUnit ) const
{
double mup = mMapToPixel.mapUnitsPerPixel();
const double symbologyReferenceScaleFactor = mSymbologyReferenceScale > 1 ? mSymbologyReferenceScale / mRendererScale : 1;

switch ( outputUnit )
{
@@ -464,19 +472,19 @@ double QgsRenderContext::convertFromMapUnits( double sizeInMapUnits, QgsUnitType
}
case QgsUnitTypes::RenderMillimeters:
{
return sizeInMapUnits / ( mScaleFactor * mup );
return sizeInMapUnits / ( mScaleFactor * mup ) * symbologyReferenceScaleFactor;
}
case QgsUnitTypes::RenderPoints:
{
return sizeInMapUnits / ( mScaleFactor * mup / POINTS_TO_MM );
return sizeInMapUnits / ( mScaleFactor * mup / POINTS_TO_MM ) * symbologyReferenceScaleFactor;
}
case QgsUnitTypes::RenderInches:
{
return sizeInMapUnits / ( mScaleFactor * mup * INCH_TO_MM );
return sizeInMapUnits / ( mScaleFactor * mup * INCH_TO_MM ) * symbologyReferenceScaleFactor;
}
case QgsUnitTypes::RenderPixels:
{
return sizeInMapUnits / mup;
return sizeInMapUnits / mup * symbologyReferenceScaleFactor;
}

case QgsUnitTypes::RenderUnknownUnit:
@@ -384,6 +384,24 @@ class CORE_EXPORT QgsRenderContext : public QgsTemporalRangeObject
*/
double rendererScale() const {return mRendererScale;}


/**
* Returns the symbology reference \a scale.
*
* This represents the desired scale denominator for the rendered map, eg 1000.0 for a 1:1000 map render.
* A value of -1 indicates that symbology scaling by reference scale is disabled.
*
* The symbology reference scale is an optional property which specifies the reference
* scale at which symbology in paper units (such a millimeters or points) is fixed
* to. For instance, if the scale is 1000 then a 2mm thick line will be rendered at
* exactly 2mm thick when a map is rendered at 1:1000, or 1mm thick when rendered at 1:2000, or 4mm thick at 1:500.
*
* \see setSymbologyReferenceScale()
* \see rendererScale()
* \since QGIS 3.22
*/
double symbologyReferenceScale() const { return mSymbologyReferenceScale; }

/**
* Gets access to new labeling engine (may be NULLPTR)
* \note not available in Python bindings
@@ -498,6 +516,23 @@ class CORE_EXPORT QgsRenderContext : public QgsTemporalRangeObject
*/
void setRendererScale( double scale ) {mRendererScale = scale;}

/**
* Sets the symbology reference \a scale.
*
* This should match the desired scale denominator for the rendered map, eg 1000.0 for a 1:1000 map render.
* Set to -1 to disable symbology scaling by reference scale.
*
* The symbology reference scale is an optional property which specifies the reference
* scale at which symbology in paper units (such a millimeters or points) is fixed
* to. For instance, if \a scale is set to 1000 then a 2mm thick line will be rendered at
* exactly 2mm thick when a map is rendered at 1:1000, or 1mm thick when rendered at 1:2000, or 4mm thick at 1:500.
*
* \see symbologyReferenceScale()
* \see rendererScale()
* \since QGIS 3.22
*/
void setSymbologyReferenceScale( double scale ) { mSymbologyReferenceScale = scale; }

/**
* Sets the destination QPainter for the render operation. Ownership of the painter
* is not transferred and the QPainter destination must stay alive for the duration
@@ -949,6 +984,8 @@ class CORE_EXPORT QgsRenderContext : public QgsTemporalRangeObject
//! Map scale
double mRendererScale = 1.0;

double mSymbologyReferenceScale = -1;

//! Newer labeling engine implementation (can be NULLPTR)
QgsLabelingEngine *mLabelingEngine = nullptr;

@@ -63,6 +63,10 @@ def testGettersSetters(self):
c.setZRange(QgsDoubleRange(1, 10))
self.assertEqual(c.zRange(), QgsDoubleRange(1, 10))

self.assertEqual(c.symbologyReferenceScale(), -1)
c.setSymbologyReferenceScale(1000)
self.assertEqual(c.symbologyReferenceScale(), 1000)

def testCopyConstructor(self):
"""
Test the copy constructor
@@ -72,11 +76,13 @@ def testCopyConstructor(self):
c1.setTextRenderFormat(QgsRenderContext.TextFormatAlwaysText)
c1.setMapExtent(QgsRectangle(1, 2, 3, 4))
c1.setZRange(QgsDoubleRange(1, 10))
c1.setSymbologyReferenceScale(1000)

c2 = QgsRenderContext(c1)
self.assertEqual(c2.textRenderFormat(), QgsRenderContext.TextFormatAlwaysText)
self.assertEqual(c2.mapExtent(), QgsRectangle(1, 2, 3, 4))
self.assertEqual(c2.zRange(), QgsDoubleRange(1, 10))
self.assertEqual(c2.symbologyReferenceScale(), 1000)

c1.setTextRenderFormat(QgsRenderContext.TextFormatAlwaysOutlines)
c2 = QgsRenderContext(c1)
@@ -145,6 +151,7 @@ def testFromMapSettings(self):
self.assertTrue(rc.testFlag(QgsRenderContext.LosslessImageRendering))
self.assertTrue(rc.testFlag(QgsRenderContext.Render3DMap))
self.assertEqual(ms.zRange(), QgsDoubleRange(1, 10))
self.assertEqual(rc.symbologyReferenceScale(), -1)

ms.setTextRenderFormat(QgsRenderContext.TextFormatAlwaysOutlines)
ms.setZRange(QgsDoubleRange())
@@ -304,6 +311,45 @@ def testConvertSingleUnit(self):
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderPixels, c)
self.assertAlmostEqual(sf, 1.0, places=5)

# with symbologyReferenceScale set
c = QgsMapUnitScale()
r.setSymbologyReferenceScale(1000)
r.setRendererScale(1000)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderMapUnits, c)
self.assertAlmostEqual(sf, 0.5, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderMillimeters, c)
self.assertAlmostEqual(sf, 11.8110236, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderPoints, c)
self.assertAlmostEqual(sf, 4.166666665625, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderInches, c)
self.assertAlmostEqual(sf, 300.0, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderPixels, c)
self.assertAlmostEqual(sf, 1.0, places=5)

r.setRendererScale(2000)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderMapUnits, c)
self.assertAlmostEqual(sf, 0.5 / 2, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderMillimeters, c)
self.assertAlmostEqual(sf, 11.8110236 / 2, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderPoints, c)
self.assertAlmostEqual(sf, 4.166666665625 / 2, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderInches, c)
self.assertAlmostEqual(sf, 300.0 / 2, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderPixels, c)
self.assertAlmostEqual(sf, 1.0 / 2, places=5)

r.setRendererScale(500)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderMapUnits, c)
self.assertAlmostEqual(sf, 0.5 * 2, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderMillimeters, c)
self.assertAlmostEqual(sf, 11.8110236 * 2, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderPoints, c)
self.assertAlmostEqual(sf, 4.166666665625 * 2, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderInches, c)
self.assertAlmostEqual(sf, 300.0 * 2, places=5)
sf = r.convertToPainterUnits(1, QgsUnitTypes.RenderPixels, c)
self.assertAlmostEqual(sf, 1.0 * 2, places=5)

def testConvertToPainterUnits(self):
ms = QgsMapSettings()
ms.setExtent(QgsRectangle(0, 0, 100, 100))
@@ -372,14 +418,19 @@ def testConvertToMapUnits(self):

size = r.convertToMapUnits(2, QgsUnitTypes.RenderMapUnits, c)
self.assertEqual(size, 2.0)
self.assertEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderMapUnits), 2)
size = r.convertToMapUnits(2, QgsUnitTypes.RenderMillimeters, c)
self.assertAlmostEqual(size, 47.244094, places=5)
self.assertEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderMillimeters), 2)
size = r.convertToMapUnits(5.66929, QgsUnitTypes.RenderPoints, c)
self.assertAlmostEqual(size, 47.2440833, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderPoints), 5.66929, 4)
size = r.convertToMapUnits(5.66929, QgsUnitTypes.RenderInches, c)
self.assertAlmostEqual(size, 3401.574, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderInches), 5.66929, 4)
size = r.convertToMapUnits(2, QgsUnitTypes.RenderPixels, c)
self.assertAlmostEqual(size, 4.0, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderPixels), 2, 4)

# minimum size greater than the calculated size, so size should be limited to minSizeMM
c.minSizeMM = 5
@@ -443,6 +494,61 @@ def testConvertToMapUnits(self):
self.assertAlmostEqual(size, 4.0, places=5)
c.maxScale = 0

# with symbology reference scale
c = QgsMapUnitScale()
r.setSymbologyReferenceScale(1000)
r.setRendererScale(1000)

size = r.convertToMapUnits(2, QgsUnitTypes.RenderMapUnits, c)
self.assertEqual(size, 2.0)
self.assertEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderMapUnits), 2)
size = r.convertToMapUnits(2, QgsUnitTypes.RenderMillimeters, c)
self.assertAlmostEqual(size, 47.244094, places=5)
self.assertEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderMillimeters), 2)
size = r.convertToMapUnits(5.66929, QgsUnitTypes.RenderPoints, c)
self.assertAlmostEqual(size, 47.2440833, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderPoints), 5.66929, 4)
size = r.convertToMapUnits(5.66929, QgsUnitTypes.RenderInches, c)
self.assertAlmostEqual(size, 3401.574, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderInches), 5.66929, 4)
size = r.convertToMapUnits(2, QgsUnitTypes.RenderPixels, c)
self.assertAlmostEqual(size, 4.0, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderPixels), 2, 4)

r.setRendererScale(2000)
size = r.convertToMapUnits(2, QgsUnitTypes.RenderMapUnits, c)
self.assertEqual(size, 2.0)
self.assertEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderMapUnits), 2)
size = r.convertToMapUnits(2, QgsUnitTypes.RenderMillimeters, c)
self.assertAlmostEqual(size, 47.244094 * 2, places=5)
self.assertEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderMillimeters), 2)
size = r.convertToMapUnits(5.66929, QgsUnitTypes.RenderPoints, c)
self.assertAlmostEqual(size, 47.2440833 * 2, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderPoints), 5.66929, 4)
size = r.convertToMapUnits(5.66929, QgsUnitTypes.RenderInches, c)
self.assertAlmostEqual(size, 3401.574 * 2, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderInches), 5.66929, 4)
size = r.convertToMapUnits(2, QgsUnitTypes.RenderPixels, c)
self.assertAlmostEqual(size, 4.0 * 2, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderPixels), 2, 4)

r.setRendererScale(500)
size = r.convertToMapUnits(2, QgsUnitTypes.RenderMapUnits, c)
self.assertEqual(size, 2.0)
self.assertEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderMapUnits), 2)
size = r.convertToMapUnits(2, QgsUnitTypes.RenderMillimeters, c)
self.assertAlmostEqual(size, 47.244094 / 2, places=5)
self.assertEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderMillimeters), 2)
size = r.convertToMapUnits(5.66929, QgsUnitTypes.RenderPoints, c)
self.assertAlmostEqual(size, 47.2440833 / 2, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderPoints), 5.66929, 4)
size = r.convertToMapUnits(5.66929, QgsUnitTypes.RenderInches, c)
self.assertAlmostEqual(size, 3401.574 / 2, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderInches), 5.66929, 4)
size = r.convertToMapUnits(2, QgsUnitTypes.RenderPixels, c)
self.assertAlmostEqual(size, 4.0 / 2, places=5)
self.assertAlmostEqual(r.convertFromMapUnits(size, QgsUnitTypes.RenderPixels), 2, 4)

def testPixelSizeScaleFactor(self):
ms = QgsMapSettings()
ms.setExtent(QgsRectangle(0, 0, 100, 100))

0 comments on commit 54610b2

Please sign in to comment.