Add option to convert units during conversion

Because pixel based units are unfriendly for hi-dpi or print layouts
This commit is contained in:
Nyall Dawson 2020-09-06 17:18:27 +10:00
parent 3e54b7ab5f
commit 98ba6a47ff
3 changed files with 166 additions and 41 deletions

View File

@ -39,6 +39,51 @@ Returns a list of warning messages generated during the conversion.
void clearWarnings();
%Docstring
Clears the list of warning messages.
%End
QgsUnitTypes::RenderUnit targetUnit() const;
%Docstring
Returns the target unit type.
By default this is QgsUnitTypes.RenderPixels in order to exactly match the original
style rendering. But rendering in pixels can cause issues on hidpi displays or with print
layouts, so setting a target unit of QgsUnitTypes.Millimeters or another real-world unit
type is often more appropriate.
.. seealso:: :py:func:`setTargetUnit`
%End
void setTargetUnit( QgsUnitTypes::RenderUnit targetUnit );
%Docstring
Sets the target unit type.
By default this is QgsUnitTypes.RenderPixels in order to exactly match the original
style rendering. But rendering in pixels can cause issues on hidpi displays or with print
layouts, so setting a target unit of QgsUnitTypes.Millimeters or another real-world unit
type is often more appropriate.
If setting to a non-pixel unit, be sure to call :py:func:`~QgsMapBoxGlStyleConversionContext.setPixelSizeConversionFactor` in order
to setup an appropriate pixel-to-unit conversion factor to scale converted sizes
using. E.g. if the target unit is millimeters, the size conversion factor should be
set to a pixel-to-millimeter value.
.. seealso:: :py:func:`targetUnit`
%End
double pixelSizeConversionFactor() const;
%Docstring
Returns the pixel size conversion factor, used to scale the original pixel sizes
when converting styles.
.. seealso:: :py:func:`setSizeConversionFactor`
%End
void setPixelSizeConversionFactor( double sizeConversionFactor );
%Docstring
Sets the pixel size conversion factor, used to scale the original pixel sizes
when converting styles.
.. seealso:: :py:func:`pixelSizeConversionFactor`
%End
};
@ -75,7 +120,7 @@ Constructor for QgsMapBoxGlStyleConverter.
NoLayerList,
};
Result convert( const QVariantMap &style );
Result convert( const QVariantMap &style, QgsMapBoxGlStyleConversionContext *context = 0 );
%Docstring
Converts a JSON ``style`` map, and returns the resultant status of the conversion.
@ -84,9 +129,11 @@ by calling :py:func:`~QgsMapBoxGlStyleConverter.errorMessage`.
After conversion, the resultant labeling and style rules can be retrieved by calling
:py:func:`~QgsMapBoxGlStyleConverter.renderer` or :py:func:`~QgsMapBoxGlStyleConverter.labeling` respectively.
The optional ``context`` argument can be set to use a specific context during the conversion.
%End
Result convert( const QString &style );
Result convert( const QString &style, QgsMapBoxGlStyleConversionContext *context = 0 );
%Docstring
Converts a JSON ``style`` string, and returns the resultant status of the conversion.
@ -95,6 +142,8 @@ by calling :py:func:`~QgsMapBoxGlStyleConverter.errorMessage`.
After conversion, the resultant labeling and style rules can be retrieved by calling
:py:func:`~QgsMapBoxGlStyleConverter.renderer` or :py:func:`~QgsMapBoxGlStyleConverter.labeling` respectively.
The optional ``context`` argument can be set to use a specific context during the conversion.
%End
QString errorMessage() const;
@ -134,7 +183,7 @@ or ``None`` if the style could not be converted successfully.
Opacity,
};
void parseLayers( const QVariantList &layers );
void parseLayers( const QVariantList &layers, QgsMapBoxGlStyleConversionContext *context = 0 );
%Docstring
Parse list of ``layers`` from JSON.

View File

@ -33,19 +33,17 @@
#include "qgsblureffect.h"
constexpr double PIXEL_RATIO = 1;
QgsMapBoxGlStyleConverter::QgsMapBoxGlStyleConverter()
{
}
QgsMapBoxGlStyleConverter::Result QgsMapBoxGlStyleConverter::convert( const QVariantMap &style )
QgsMapBoxGlStyleConverter::Result QgsMapBoxGlStyleConverter::convert( const QVariantMap &style, QgsMapBoxGlStyleConversionContext *context )
{
mError.clear();
mWarnings.clear();
if ( style.contains( QStringLiteral( "layers" ) ) )
{
parseLayers( style.value( QStringLiteral( "layers" ) ).toList() );
parseLayers( style.value( QStringLiteral( "layers" ) ).toList(), context );
}
else
{
@ -55,16 +53,21 @@ QgsMapBoxGlStyleConverter::Result QgsMapBoxGlStyleConverter::convert( const QVar
return Success;
}
QgsMapBoxGlStyleConverter::Result QgsMapBoxGlStyleConverter::convert( const QString &style )
QgsMapBoxGlStyleConverter::Result QgsMapBoxGlStyleConverter::convert( const QString &style, QgsMapBoxGlStyleConversionContext *context )
{
return convert( QgsJsonUtils::parseJson( style ).toMap() );
return convert( QgsJsonUtils::parseJson( style ).toMap(), context );
}
QgsMapBoxGlStyleConverter::~QgsMapBoxGlStyleConverter() = default;
void QgsMapBoxGlStyleConverter::parseLayers( const QVariantList &layers )
void QgsMapBoxGlStyleConverter::parseLayers( const QVariantList &layers, QgsMapBoxGlStyleConversionContext *context )
{
QgsMapBoxGlStyleConversionContext context;
std::unique_ptr< QgsMapBoxGlStyleConversionContext > tmpContext;
if ( !context )
{
tmpContext = qgis::make_unique< QgsMapBoxGlStyleConversionContext >();
context = tmpContext.get();
}
QList<QgsVectorTileBasicRendererStyle> rendererStyles;
QList<QgsVectorTileBasicLabelingStyle> labelingStyles;
@ -88,7 +91,7 @@ void QgsMapBoxGlStyleConverter::parseLayers( const QVariantList &layers )
QString filterExpression;
if ( jsonLayer.contains( QStringLiteral( "filter" ) ) )
{
filterExpression = parseExpression( jsonLayer.value( QStringLiteral( "filter" ) ).toList(), context );
filterExpression = parseExpression( jsonLayer.value( QStringLiteral( "filter" ) ).toList(), *context );
}
QgsVectorTileBasicRendererStyle rendererStyle;
@ -98,15 +101,15 @@ void QgsMapBoxGlStyleConverter::parseLayers( const QVariantList &layers )
bool hasLabelingStyle = false;
if ( layerType == QLatin1String( "fill" ) )
{
hasRendererStyle = parseFillLayer( jsonLayer, rendererStyle, context );
hasRendererStyle = parseFillLayer( jsonLayer, rendererStyle, *context );
}
else if ( layerType == QLatin1String( "line" ) )
{
hasRendererStyle = parseLineLayer( jsonLayer, rendererStyle, context );
hasRendererStyle = parseLineLayer( jsonLayer, rendererStyle, *context );
}
else if ( layerType == QLatin1String( "symbol" ) )
{
parseSymbolLayer( jsonLayer, rendererStyle, hasRendererStyle, labelingStyle, hasLabelingStyle, context );
parseSymbolLayer( jsonLayer, rendererStyle, hasRendererStyle, labelingStyle, hasLabelingStyle, *context );
}
else
{
@ -137,8 +140,8 @@ void QgsMapBoxGlStyleConverter::parseLayers( const QVariantList &layers )
labelingStyles.append( labelingStyle );
}
mWarnings.append( context.warnings() );
context.clearWarnings();
mWarnings.append( context->warnings() );
context->clearWarnings();
}
mRenderer = qgis::make_unique< QgsVectorTileBasicRenderer >();
@ -275,8 +278,8 @@ bool QgsMapBoxGlStyleConverter::parseFillLayer( const QVariantMap &jsonLayer, Qg
QgsSimpleFillSymbolLayer *fillSymbol = dynamic_cast< QgsSimpleFillSymbolLayer * >( symbol->symbolLayer( 0 ) );
// set render units
symbol->setOutputUnit( QgsUnitTypes::RenderPixels );
fillSymbol->setOutputUnit( QgsUnitTypes::RenderPixels );
symbol->setOutputUnit( context.targetUnit() );
fillSymbol->setOutputUnit( context.targetUnit() );
if ( jsonPaint.contains( QStringLiteral( "fill-pattern" ) ) )
{
@ -387,17 +390,17 @@ bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, Qg
{
case QVariant::Int:
case QVariant::Double:
lineWidth = jsonLineWidth.toDouble();
lineWidth = jsonLineWidth.toDouble() * context.pixelSizeConversionFactor();
break;
case QVariant::Map:
lineWidth = -1;
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseInterpolateByZoom( jsonLineWidth.toMap(), context, PIXEL_RATIO ) );
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseInterpolateByZoom( jsonLineWidth.toMap(), context, context.pixelSizeConversionFactor() ) );
break;
case QVariant::List:
case QVariant::StringList:
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseInterpolateListByZoom( jsonLineWidth.toList(), PropertyType::Numeric, context, PIXEL_RATIO ) );
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseInterpolateListByZoom( jsonLineWidth.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor() ) );
break;
default:
@ -460,7 +463,7 @@ bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, Qg
const QVariantList dashSource = jsonLineDashArray.toMap().value( QStringLiteral( "stops" ) ).toList().last().toList().value( 1 ).toList();
for ( const QVariant &v : dashSource )
{
dashVector << v.toDouble() * PIXEL_RATIO;
dashVector << v.toDouble() * context.pixelSizeConversionFactor();
}
break;
}
@ -471,7 +474,7 @@ bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, Qg
const QVariantList dashSource = jsonLineDashArray.toList();
for ( const QVariant &v : dashSource )
{
dashVector << v.toDouble() * PIXEL_RATIO;
dashVector << v.toDouble() * context.pixelSizeConversionFactor();
}
break;
}
@ -501,8 +504,8 @@ bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, Qg
QgsSimpleLineSymbolLayer *lineSymbol = dynamic_cast< QgsSimpleLineSymbolLayer * >( symbol->symbolLayer( 0 ) );
// set render units
symbol->setOutputUnit( QgsUnitTypes::RenderPixels );
lineSymbol->setOutputUnit( QgsUnitTypes::RenderPixels );
symbol->setOutputUnit( context.targetUnit() );
lineSymbol->setOutputUnit( context.targetUnit() );
lineSymbol->setPenCapStyle( penCapStyle );
lineSymbol->setPenJoinStyle( penJoinStyle );
lineSymbol->setDataDefinedProperties( ddProperties );
@ -517,7 +520,7 @@ bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, Qg
}
if ( lineWidth != -1 )
{
lineSymbol->setWidth( lineWidth * PIXEL_RATIO );
lineSymbol->setWidth( lineWidth );
}
if ( !dashVector.empty() )
{
@ -560,18 +563,18 @@ void QgsMapBoxGlStyleConverter::parseSymbolLayer( const QVariantMap &jsonLayer,
{
case QVariant::Int:
case QVariant::Double:
textSize = jsonTextSize.toDouble();
textSize = jsonTextSize.toDouble() * context.pixelSizeConversionFactor();
break;
case QVariant::Map:
textSize = -1;
ddLabelProperties.setProperty( QgsPalLayerSettings::Size, parseInterpolateByZoom( jsonTextSize.toMap(), context ) );
ddLabelProperties.setProperty( QgsPalLayerSettings::Size, parseInterpolateByZoom( jsonTextSize.toMap(), context, context.pixelSizeConversionFactor() ) );
break;
case QVariant::List:
case QVariant::StringList:
textSize = -1;
ddLabelProperties.setProperty( QgsPalLayerSettings::Size, parseInterpolateListByZoom( jsonTextSize.toList(), PropertyType::Numeric, context ) );
ddLabelProperties.setProperty( QgsPalLayerSettings::Size, parseInterpolateListByZoom( jsonTextSize.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor() ) );
break;
default:
@ -690,18 +693,18 @@ void QgsMapBoxGlStyleConverter::parseSymbolLayer( const QVariantMap &jsonLayer,
{
case QVariant::Int:
case QVariant::Double:
bufferSize = jsonHaloWidth.toDouble();
bufferSize = jsonHaloWidth.toDouble() * context.pixelSizeConversionFactor();
break;
case QVariant::Map:
bufferSize = 1;
ddLabelProperties.setProperty( QgsPalLayerSettings::BufferSize, parseInterpolateByZoom( jsonHaloWidth.toMap(), context ) );
ddLabelProperties.setProperty( QgsPalLayerSettings::BufferSize, parseInterpolateByZoom( jsonHaloWidth.toMap(), context, context.pixelSizeConversionFactor() ) );
break;
case QVariant::List:
case QVariant::StringList:
bufferSize = 1;
ddLabelProperties.setProperty( QgsPalLayerSettings::BufferSize, parseInterpolateListByZoom( jsonHaloWidth.toList(), PropertyType::Numeric, context ) );
ddLabelProperties.setProperty( QgsPalLayerSettings::BufferSize, parseInterpolateListByZoom( jsonHaloWidth.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor() ) );
break;
default:
@ -719,7 +722,7 @@ void QgsMapBoxGlStyleConverter::parseSymbolLayer( const QVariantMap &jsonLayer,
case QVariant::Int:
case QVariant::Double:
{
haloBlurSize = jsonTextHaloBlur.toDouble();
haloBlurSize = jsonTextHaloBlur.toDouble() * context.pixelSizeConversionFactor();
break;
}
@ -730,7 +733,7 @@ void QgsMapBoxGlStyleConverter::parseSymbolLayer( const QVariantMap &jsonLayer,
}
QgsTextFormat format;
format.setSizeUnit( QgsUnitTypes::RenderPixels );
format.setSizeUnit( context.targetUnit() );
if ( textColor.isValid() )
format.setColor( textColor );
if ( textSize >= 0 )
@ -741,8 +744,8 @@ void QgsMapBoxGlStyleConverter::parseSymbolLayer( const QVariantMap &jsonLayer,
if ( bufferSize > 0 )
{
format.buffer().setEnabled( true );
format.buffer().setSize( bufferSize * PIXEL_RATIO );
format.buffer().setSizeUnit( QgsUnitTypes::RenderPixels );
format.buffer().setSize( bufferSize );
format.buffer().setSizeUnit( context.targetUnit() );
format.buffer().setColor( bufferColor );
if ( haloBlurSize > 0 )
@ -750,7 +753,7 @@ void QgsMapBoxGlStyleConverter::parseSymbolLayer( const QVariantMap &jsonLayer,
QgsEffectStack *stack = new QgsEffectStack();
QgsBlurEffect *blur = new QgsBlurEffect() ;
blur->setEnabled( true );
blur->setBlurUnit( QgsUnitTypes::RenderPixels );
blur->setBlurUnit( context.targetUnit() );
blur->setBlurLevel( haloBlurSize );
blur->setBlurMethod( QgsBlurEffect::StackBlur );
stack->appendEffect( blur );
@ -1470,3 +1473,23 @@ void QgsMapBoxGlStyleConversionContext::pushWarning( const QString &warning )
QgsDebugMsg( warning );
mWarnings << warning;
}
QgsUnitTypes::RenderUnit QgsMapBoxGlStyleConversionContext::targetUnit() const
{
return mTargetUnit;
}
void QgsMapBoxGlStyleConversionContext::setTargetUnit( QgsUnitTypes::RenderUnit targetUnit )
{
mTargetUnit = targetUnit;
}
double QgsMapBoxGlStyleConversionContext::pixelSizeConversionFactor() const
{
return mSizeConversionFactor;
}
void QgsMapBoxGlStyleConversionContext::setPixelSizeConversionFactor( double sizeConversionFactor )
{
mSizeConversionFactor = sizeConversionFactor;
}

View File

@ -52,9 +52,58 @@ class CORE_EXPORT QgsMapBoxGlStyleConversionContext
*/
void clearWarnings() { mWarnings.clear(); }
/**
* Returns the target unit type.
*
* By default this is QgsUnitTypes::RenderPixels in order to exactly match the original
* style rendering. But rendering in pixels can cause issues on hidpi displays or with print
* layouts, so setting a target unit of QgsUnitTypes::Millimeters or another real-world unit
* type is often more appropriate.
*
* \see setTargetUnit()
*/
QgsUnitTypes::RenderUnit targetUnit() const;
/**
* Sets the target unit type.
*
* By default this is QgsUnitTypes::RenderPixels in order to exactly match the original
* style rendering. But rendering in pixels can cause issues on hidpi displays or with print
* layouts, so setting a target unit of QgsUnitTypes::Millimeters or another real-world unit
* type is often more appropriate.
*
* If setting to a non-pixel unit, be sure to call setPixelSizeConversionFactor() in order
* to setup an appropriate pixel-to-unit conversion factor to scale converted sizes
* using. E.g. if the target unit is millimeters, the size conversion factor should be
* set to a pixel-to-millimeter value.
*
* \see targetUnit()
*/
void setTargetUnit( QgsUnitTypes::RenderUnit targetUnit );
/**
* Returns the pixel size conversion factor, used to scale the original pixel sizes
* when converting styles.
*
* \see setSizeConversionFactor()
*/
double pixelSizeConversionFactor() const;
/**
* Sets the pixel size conversion factor, used to scale the original pixel sizes
* when converting styles.
*
* \see pixelSizeConversionFactor()
*/
void setPixelSizeConversionFactor( double sizeConversionFactor );
private:
QStringList mWarnings;
QgsUnitTypes::RenderUnit mTargetUnit = QgsUnitTypes::RenderPixels;
double mSizeConversionFactor = 1.0;
};
/**
@ -99,8 +148,10 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter
*
* After conversion, the resultant labeling and style rules can be retrieved by calling
* renderer() or labeling() respectively.
*
* The optional \a context argument can be set to use a specific context during the conversion.
*/
Result convert( const QVariantMap &style );
Result convert( const QVariantMap &style, QgsMapBoxGlStyleConversionContext *context = nullptr );
/**
* Converts a JSON \a style string, and returns the resultant status of the conversion.
@ -110,8 +161,10 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter
*
* After conversion, the resultant labeling and style rules can be retrieved by calling
* renderer() or labeling() respectively.
*
* The optional \a context argument can be set to use a specific context during the conversion.
*/
Result convert( const QString &style );
Result convert( const QString &style, QgsMapBoxGlStyleConversionContext *context = nullptr );
/**
* Returns a descriptive error message if an error was encountered during the style conversion,
@ -158,7 +211,7 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter
* Parse list of \a layers from JSON.
* \warning This is private API only, and may change in future QGIS versions
*/
void parseLayers( const QVariantList &layers );
void parseLayers( const QVariantList &layers, QgsMapBoxGlStyleConversionContext *context = nullptr );
/**
* Parses a fill layer.