Fix inconcequent use of DPI at generating the WMS legend
-
Consider DPI of QgsWmsRenderContext what is the OGC default (0.28 mm per pixel) or the passed WMS parameter - this is done by creating the QgsRenderContext by the mapSettings (with BBOX) or applying the dotsPerMm to the scaleFactor.
-
Additionally the image size needs to be calculated according to the QgsRenderContext now, what means it needs to be generated before.
-
The QPainter needs to be applied after to the context (since it's not passed by creating the context anymore).
QImage *QgsRenderer::getLegendGraphics( QgsLayerTreeModel &model )
{
// get layers
std::unique_ptr<QgsWmsRestorer> restorer;
restorer.reset( new QgsWmsRestorer( mContext ) );
// configure layers
QList<QgsMapLayer *> layers = mContext.layersToRender();
configureLayers( layers );
// init renderer
QgsLegendSettings settings = legendSettings();
QgsLegendRenderer renderer( &model, settings );
// create context
QgsRenderContext context;
if ( !mWmsParameters.bbox().isEmpty() )
{
QgsMapSettings mapSettings;
mapSettings.setFlag( Qgis::MapSettingsFlag::RenderBlocking );
std::unique_ptr<QImage> tmp( createImage( mContext.mapSize( false ) ) );
configureMapSettings( tmp.get(), mapSettings );
context = QgsRenderContext::fromMapSettings(mapSettings);
} else {
context.setScaleFactor( mContext.dotsPerMm() );
const double mmPerMapUnit = 1 / QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mProject );
context.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * context.scaleFactor() ) ) );
}
// create image according to context
std::unique_ptr<QImage> image;
const qreal dpmm = mContext.dotsPerMm();
const QSizeF minSize = renderer.minimumSize(&context);
const QSize size( static_cast<int>( minSize.width() * dpmm ), static_cast<int>( minSize.height() * dpmm ) );
if ( !mContext.isValidWidthHeight( size.width(), size.height() ) )
{
throw QgsServerException( QStringLiteral( "Legend image is too large" ) );
}
image.reset( createImage( size ) );
// configure painter and addapt to the context
QPainter painter( image.get() );
context.setPainter(&painter);
if ( painter.renderHints() & QPainter::SmoothPixmapTransform )
context.setFlag( Qgis::RenderContextFlag::HighQualityImageTransforms, true );
if ( painter.renderHints() & QPainter::LosslessImageRendering )
context.setFlag( Qgis::RenderContextFlag::LosslessImageRendering, true );
context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
QgsScopedRenderContextScaleToMm scaleContext( context );
// rendering
renderer.drawLegend( context );
painter.end();
return image.release();
}
Fix DPI on getLegendGraphic concerning a RULE. It should not take the context settings according to the mapSettings since it NEVER has a BBOX (since of RULE)
// create context
QgsRenderContext context = QgsRenderContext::fromQPainter( painter.get() );
context.setScaleFactor( mContext.dotsPerMm() );
const double mmPerMapUnit = 1 / QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mProject );
context.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * context.scaleFactor() ) ) );
QgsDistanceArea distanceArea = QgsDistanceArea();
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), mProject->transformContext() );
distanceArea.setEllipsoid( geoNone() );
context.setDistanceArea( distanceArea );
ctx.context = &context;
-> PUSH
fixing all the tests...
Append distance area when using defaultMapUnitsPerMm to consider the correct CRS. This fixes #50366
} else {
context.setScaleFactor( mContext.dotsPerMm() );
const double mmPerMapUnit = 1 / QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mProject );
context.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * context.scaleFactor() ) ) );
QgsDistanceArea distanceArea;
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), mProject->transformContext() );
distanceArea.setEllipsoid( geoNone() );
context.setDistanceArea(distanceArea);
}
- new tests...
- images to test
Note: Apparently in QGIS code dots are equal pixels...
Another Note It's working with EPSG:4326
The issue is that we do not set the CRS on the distanceArea
of the QgsRenderContext
and with that the default one is used (EPSG:4326) using Degrees instead of Meters as MapUnits, what gives us suuupersmall symbols on Meter-based CRS...
This can be solved by setting the corrects CRS, like e.g.
const QgsCoordinateTransformContext cordcontext;
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), cordcontext );
distanceArea.setEllipsoid( geoNone() );
context.setDistanceArea(distanceArea);
But not sure if we can do that...
-
Default Scale for Legend is converted to mU/mm on saving the settings:
double defaultMapUnitsPerMm = mWMSDefaultMapUnitScale->scale() / QgsUnitTypes::fromUnitToUnitFactor( QgsProject::instance()->crs().mapUnits(), QgsUnitTypes::DistanceMillimeters );
This is stored then to the setting: "WMSDefaultMapUnitsPerMm"
Means on 1:39542 we have 39.542 mU/mm
-
Now we need to set it in
QgsLegendSettings QgsRenderer::legendSettings()
:Without BBOX:
The
wmsDefaultMapUnitsPerMm
is used.defaultMapUnitsPerPixel = QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mContext.project() ) / mContext.dotsPerMm();
With this we have 39.542 mU/mm divided with e.g. 3.57 leading to 11.0718 mU/pixel.
With BBOX:
(we tried to make a more or less same bbox with the same scale)
mapSettings.mapUnitsPerPixel()
what is in our case 11.3052 mU/pixelBoth cases: After that it's set here
QgsLegendSettings::setMapUnitsPerPixel
where other fancy calculations are done:mMmPerMapUnit = 1 / mapUnitsPerPixel / ( mDpi / 25.4 );
Where mDpi is 96 (per default).
Leads to mm/mU 0.023897048 (on 11.0718 mU/pixel) and to mm/mU 0.023403684 (on 11.3052 mU/pixel).
And in
QgsLegendSettings::mapUnitsPerPixel()
it's calculated back to mU/px, but this is never done. Instead we do inQgsRenderer::getLegendGraphics
://setting the scale factor context.setRendererScale( settings.mapScale() ); //calculating map units per pixel (again) context.setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * context.scaleFactor() ) ) );
With BBOX:
And since we have a scale of 40375.7 this leads to a scale factor of 3.58268 this leads to:
QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * context.scaleFactor() ) )
=>QgsMapToPixel( 1 / ( 0.023403684 * 3.58268 ) )
`(While in QgsMapToPixel should be passed mU/px)
And with this we are back again on our 11.3052 mU/pixel.
We end up here
QgsSymbolLegendNode::drawSymbol
where we do this:const double size = markerSymbol->size( *context ) / context->scaleFactor();
And have this size (with and height) now: 2.10239e-05 (0.0000210239)
Then we read
dotsPerMM = context->scaleFactor();
again what is 3.58268.And in the end we calculate the with and the hight like this:
std::round( width * dotsPerMM ) )
Means we are on 0 now...
And no legend symbol with both:
-
Where do we go on...
It seems that this value here is way to small:
And have this size (with and height) now: 2.10239e-05 (0.0000210239)
We get it here here
QgsSymbolLegendNode::drawSymbol
where we do this:const double size = markerSymbol->size( *context ) / context->scaleFactor();
In the
size()
we callQgsRenderContext::convertToPainterUnits
and there we do this:size = convertMetersToMapUnits( size );
Now this should not change the size. Because our map unit is meters. I thought. But apparantely not. We end up here
QgsRenderContext::convertMetersToMapUnits
withQgsUnitTypes::DistanceDegrees
asmDistanceArea.sourceCrs().mapUnits()
because the default SRS is EPSG:4326 set in the construtor ofQgsDistanceArea::QgsDistanceArea()
.What we need to do now? Set somewhere the correct CRS in the
QgsDistanceArea
. Maybe ingetLegendGraphics
...
In QImage *QgsRenderer::getLegendGraphics( QgsLayerTreeModel &model )
:
image.reset( createImage( size ) );
// configure painter
QPainter painter( image.get() );
QgsRenderContext context = QgsRenderContext::fromQPainter(&painter);
context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
QgsScopedRenderContextScaleToMm scaleContext( context );
QgsDistanceArea distanceArea = QgsDistanceArea();
if ( !mWmsParameters.bbox().isEmpty() )
{
QgsMapSettings mapSettings;
mapSettings.setFlag( Qgis::MapSettingsFlag::RenderBlocking );
std::unique_ptr<QImage> tmp( createImage( mContext.mapSize( false ) ) );
configureMapSettings( tmp.get(), mapSettings );
context.setRendererScale( mapSettings.scale() );
context.setMapToPixel( mapSettings.mapToPixel() );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mapSettings.destinationCrs() ), mapSettings.transformContext() );
distanceArea.setEllipsoid( mapSettings.ellipsoid() );
}else{
const double defaultMapUnitsPerPixel = QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mContext.project() ) / mContext.dotsPerMm();
const double mmPerMapUnit = 1 / defaultMapUnitsPerPixel / ( 96 / 25.4 );
context.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * context.scaleFactor() ) ) );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), mProject->transformContext() );
distanceArea.setEllipsoid( geoNone() );
}
context.setDistanceArea(distanceArea);
// rendering
qDebug() << "let's draw a legend "<< size.height();
renderer.drawLegend( context );
painter.end();
return image.release();
And in QImage *QgsRenderer::getLegendGraphics( QgsLayerTreeModelLegendNode &nodeModel )
:
// configure painter
const qreal dpmm = mContext.dotsPerMm();
QPainter painter( image.get() );
painter.setRenderHint( QPainter::Antialiasing, true );
painter.scale( dpmm, dpmm );
// rendering
QgsLegendSettings settings = legendSettings();
QgsLayerTreeModelLegendNode::ItemContext ctx;
QgsRenderContext context = QgsRenderContext::fromQPainter(&painter);
context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
QgsScopedRenderContextScaleToMm scaleContext( context );
QgsDistanceArea distanceArea = QgsDistanceArea();
if ( !mWmsParameters.bbox().isEmpty() )
{
QgsMapSettings mapSettings;
mapSettings.setFlag( Qgis::MapSettingsFlag::RenderBlocking );
std::unique_ptr<QImage> tmp( createImage( mContext.mapSize( false ) ) );
configureMapSettings( tmp.get(), mapSettings );
context.setRendererScale( mapSettings.scale() );
context.setMapToPixel( mapSettings.mapToPixel() );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mapSettings.destinationCrs() ), mapSettings.transformContext() );
distanceArea.setEllipsoid( mapSettings.ellipsoid() );
}else
{
const double defaultMapUnitsPerPixel = QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mContext.project() ) / mContext.dotsPerMm();
const double mmPerMapUnit = 1 / defaultMapUnitsPerPixel / ( 96 / 25.4 );
context.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * context.scaleFactor() ) ) );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), mProject->transformContext() );
distanceArea.setEllipsoid( geoNone() );
}
context.setDistanceArea(distanceArea);
ctx.context = &context;
ctx.painter = &painter;
qDebug() << "let's draw a symbol "<< size.height();
nodeModel.drawSymbol( settings, &ctx, size.height() / dpmm );
painter.end();
return image.release();
This approach fixes sizes:
QImage *QgsRenderer::getLegendGraphics( QgsLayerTreeModel &model )
{
// get layers
std::unique_ptr<QgsWmsRestorer> restorer;
restorer.reset( new QgsWmsRestorer( mContext ) );
// configure layers
QList<QgsMapLayer *> layers = mContext.layersToRender();
configureLayers( layers );
// init renderer
QgsLegendSettings settings = legendSettings();
QgsLegendRenderer renderer( &model, settings );
// tmp context to calculate the size
QgsRenderContext tmpContext = QgsRenderContext::fromQPainter(nullptr);
QgsDistanceArea distanceArea = QgsDistanceArea();
if ( !mWmsParameters.bbox().isEmpty() )
{
QgsMapSettings mapSettings;
mapSettings.setFlag( Qgis::MapSettingsFlag::RenderBlocking );
std::unique_ptr<QImage> tmp( createImage( mContext.mapSize( false ) ) );
configureMapSettings( tmp.get(), mapSettings );
tmpContext.setRendererScale( mapSettings.scale() );
tmpContext.setMapToPixel( mapSettings.mapToPixel() );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mapSettings.destinationCrs() ), mapSettings.transformContext() );
distanceArea.setEllipsoid( mapSettings.ellipsoid() );
}else{
const double defaultMapUnitsPerPixel = QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mContext.project() ) / mContext.dotsPerMm();
const double mmPerMapUnit = 1 / defaultMapUnitsPerPixel / ( 96 / 25.4 );
tmpContext.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * tmpContext.scaleFactor() ) ) );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), mProject->transformContext() );
distanceArea.setEllipsoid( geoNone() );
}
tmpContext.setDistanceArea(distanceArea);
// create image
std::unique_ptr<QImage> image;
const qreal dpmm = mContext.dotsPerMm();
const QSizeF minSize = renderer.minimumSize(&tmpContext);
const QSize size( static_cast<int>( minSize.width() * dpmm ), static_cast<int>( minSize.height() * dpmm ) );
if ( !mContext.isValidWidthHeight( size.width(), size.height() ) )
{
throw QgsServerException( QStringLiteral( "Legend image is too large" ) );
}
image.reset( createImage( size ) );
// configure painter
QPainter painter( image.get() );
QgsRenderContext context = QgsRenderContext::fromQPainter(&painter);
context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
QgsScopedRenderContextScaleToMm scaleContext( context );
if ( !mWmsParameters.bbox().isEmpty() )
{
QgsMapSettings mapSettings;
mapSettings.setFlag( Qgis::MapSettingsFlag::RenderBlocking );
std::unique_ptr<QImage> tmp( createImage( mContext.mapSize( false ) ) );
configureMapSettings( tmp.get(), mapSettings );
context.setRendererScale( mapSettings.scale() );
context.setMapToPixel( mapSettings.mapToPixel() );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mapSettings.destinationCrs() ), mapSettings.transformContext() );
distanceArea.setEllipsoid( mapSettings.ellipsoid() );
}else{
const double defaultMapUnitsPerPixel = QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mContext.project() ) / mContext.dotsPerMm();
const double mmPerMapUnit = 1 / defaultMapUnitsPerPixel / ( 96 / 25.4 );
context.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * context.scaleFactor() ) ) );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), mProject->transformContext() );
distanceArea.setEllipsoid( geoNone() );
}
context.setDistanceArea(distanceArea);
// rendering
renderer.drawLegend( context );
painter.end();
return image.release();
}
Trying to apply painter after the QgsRenderContext
is evaluated. But this is apparently not a good sollution, since it leads to weird scaling.
QImage *QgsRenderer::getLegendGraphics( QgsLayerTreeModel &model )
{
// get layers
std::unique_ptr<QgsWmsRestorer> restorer;
restorer.reset( new QgsWmsRestorer( mContext ) );
// configure layers
QList<QgsMapLayer *> layers = mContext.layersToRender();
configureLayers( layers );
// init renderer
QgsLegendSettings settings = legendSettings();
QgsLegendRenderer renderer( &model, settings );
QgsRenderContext context;
context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
QgsDistanceArea distanceArea = QgsDistanceArea();
if ( !mWmsParameters.bbox().isEmpty() )
{
QgsMapSettings mapSettings;
mapSettings.setFlag( Qgis::MapSettingsFlag::RenderBlocking );
std::unique_ptr<QImage> tmp( createImage( mContext.mapSize( false ) ) );
configureMapSettings( tmp.get(), mapSettings );
context.setRendererScale( mapSettings.scale() );
context.setMapToPixel( mapSettings.mapToPixel() );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mapSettings.destinationCrs() ), mapSettings.transformContext() );
distanceArea.setEllipsoid( mapSettings.ellipsoid() );
}else{
const double defaultMapUnitsPerPixel = QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mContext.project() ) / mContext.dotsPerMm();
const double mmPerMapUnit = 1 / defaultMapUnitsPerPixel / ( 96 / 25.4 );
context.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * context.scaleFactor() ) ) );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), mProject->transformContext() );
distanceArea.setEllipsoid( geoNone() );
}
context.setDistanceArea(distanceArea);
// apply painter
// create image
std::unique_ptr<QImage> image;
const qreal dpmm = mContext.dotsPerMm();
const QSizeF minSize = renderer.minimumSize(&context);
const QSize size( static_cast<int>( minSize.width() * dpmm ), static_cast<int>( minSize.height() * dpmm ) );
if ( !mContext.isValidWidthHeight( size.width(), size.height() ) )
{
throw QgsServerException( QStringLiteral( "Legend image is too large" ) );
}
image.reset( createImage( size ) );
// configure painter
QPainter painter( image.get() );
context.applyPainter(&painter);
QgsScopedRenderContextScaleToMm scaleContext( context );
// rendering
renderer.drawLegend( context );
painter.end();
return image.release();
}
Issue the CRS (and with it the Map Units) have not been considered on GetLegendGraphics
requests what leaded to invisible small symbol sizes on using Meters on Scale. It worked for EPSG:4326 since it was the default value, but not for CRS using Meters (e.g. 2056).
It uses the MapUnits from QgsRenderContext::distanceArea()
what means this should be set. Since QgsLegendSettings
is marked as deprecated I set it from the mapSettings
(if BBOX provided) or from the mWmsParameters.crs()
(if no BBOX provided).
Since the painter's image size should now be calculated according to the QgsRenderContext
it needs to be defined before and consider a nullptr-painter.
I am not sure if this is the appropriate way to go. Happy for feedback.
- Not sure if it should take the
mapSettings.transformContext().sourceCrs
insetad ofmapSettings.destinationCrs
. -> See here it's done the same way: /home/dave/dev/qgis/QGIS/src/core/qgsrendercontext.cpp:263 - check out difference between
mProject
andcontext.project()
and use everywhere the same. -> it's the same
This context.setMapToPixel( mapSettings.mapToPixel() );
leads to verschobene Labels.
Und zwar aus folgendem Grund: mapSettings.mapToPixel() verwendet wohl mMapUnitsPerPixel 0.0476364 währenddem mit den LegendSettings anhand von mMapUnitsPerPixel 0.0519604 gerechnet wird. Zweiteres wird so berechnet: 1 / 5.55423 * 3.465 (wobei die Rechnung so ist 1 / ( settings.mmPerMapUnit() * tmpcontext.scaleFactor() )) Und jetzt kommt's. context.setScaleFactor( 3.465 ); //assume 88 dpi as standard value (88 dpi sind 3.465 dpmm) - währenddem in mapSettings offenbar von 96 dpi ausgegangen wird (und somit mit 3.7795 dpmm gerechnet wird)...
Frage 1: Weshalb 96 - weshalb 88?
Frage 2: Was ist richtig? Ist richtig so wie Map oder so wie bisher?
DistanceArea und setRendererScale( mapSettings.scale() )
macht keinen Unterschied.
"Hoselupf" leads to different size...
QgsScopedRenderContextScaleToMm scaleContext( context )
muss nach dem setzen des Painters gemacht werden aber offenbar vor dem setMapToPixel.
This Works:
QImage *QgsRenderer::getLegendGraphics( QgsLayerTreeModel &model )
{
// get layers
std::unique_ptr<QgsWmsRestorer> restorer;
restorer.reset( new QgsWmsRestorer( mContext ) );
// configure layers
QList<QgsMapLayer *> layers = mContext.layersToRender();
configureLayers( layers );
// init renderer
QgsLegendSettings settings = legendSettings();
QgsLegendRenderer renderer( &model, settings );
QgsRenderContext tmpcontext = QgsRenderContext::fromQPainter( nullptr );
tmpcontext.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
QgsDistanceArea distanceArea = QgsDistanceArea();
if ( !mWmsParameters.bbox().isEmpty() )
{
QgsMapSettings mapSettings;
mapSettings.setFlag( Qgis::MapSettingsFlag::RenderBlocking );
std::unique_ptr<QImage> tmp( createImage( mContext.mapSize( false ) ) );
configureMapSettings( tmp.get(), mapSettings );
tmpcontext.setRendererScale( mapSettings.scale() );
qDebug()<< "map units per pixel from mapsettings "<< mapSettings.mapUnitsPerPixel();
qDebug()<< "vs map units per pixel from calculation "<<1 / ( settings.mmPerMapUnit() * tmpcontext.scaleFactor() )<<" means 1 / "<<settings.mmPerMapUnit()<<" * "<< tmpcontext.scaleFactor();
tmpcontext.setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * tmpcontext.scaleFactor() ) ) );
//context.setMapToPixel( mapSettings.mapToPixel() );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mapSettings.destinationCrs() ), mapSettings.transformContext() );
distanceArea.setEllipsoid( mapSettings.ellipsoid() );
}
else
{
const double mmPerMapUnit = 1 / QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mProject );
tmpcontext.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * tmpcontext.scaleFactor() ) ) );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), mProject->transformContext() );
distanceArea.setEllipsoid( geoNone() );
}
tmpcontext.setDistanceArea( distanceArea );
tmpcontext.setFlag( Qgis::RenderContextFlag::ApplyScalingWorkaroundForTextRendering, true );
// create image with size according to the context
std::unique_ptr<QImage> image;
const qreal dpmm = mContext.dotsPerMm();
const QSizeF minSize = renderer.minimumSize( &tmpcontext );
const QSize size( static_cast<int>( minSize.width() * dpmm ), static_cast<int>( minSize.height() * dpmm ) );
if ( !mContext.isValidWidthHeight( size.width(), size.height() ) )
{
throw QgsServerException( QStringLiteral( "Legend image is too large" ) );
}
image.reset( createImage( size ) );
// set painter and scale context
QPainter painter( image.get() );
QgsRenderContext context = QgsRenderContext::fromQPainter( &painter );
context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
QgsScopedRenderContextScaleToMm scaleContext( context );
if ( !mWmsParameters.bbox().isEmpty() )
{
QgsMapSettings mapSettings;
mapSettings.setFlag( Qgis::MapSettingsFlag::RenderBlocking );
std::unique_ptr<QImage> tmp( createImage( mContext.mapSize( false ) ) );
configureMapSettings( tmp.get(), mapSettings );
context.setRendererScale( mapSettings.scale() );
context.setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * context.scaleFactor() ) ) );
//context.setMapToPixel( mapSettings.mapToPixel() );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mapSettings.destinationCrs() ), mapSettings.transformContext() );
distanceArea.setEllipsoid( mapSettings.ellipsoid() );
}
else
{
const double mmPerMapUnit = 1 / QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mProject );
context.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * context.scaleFactor() ) ) );
distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), mProject->transformContext() );
distanceArea.setEllipsoid( geoNone() );
}
context.setDistanceArea( distanceArea );
// rendering
renderer.drawLegend( context );
painter.end();
return image.release();
}
and This works:
QImage *QgsRenderer::getLegendGraphics( QgsLayerTreeModel &model )
{
// get layers
std::unique_ptr<QgsWmsRestorer> restorer;
restorer.reset( new QgsWmsRestorer( mContext ) );
// configure layers
QList<QgsMapLayer *> layers = mContext.layersToRender();
configureLayers( layers );
// init renderer
QgsLegendSettings settings = legendSettings();
QgsLegendRenderer renderer( &model, settings );
// create image
std::unique_ptr<QImage> image;
const qreal dpmm = mContext.dotsPerMm();
const QSizeF minSize = renderer.minimumSize();
const QSize size( static_cast<int>( minSize.width() * dpmm ), static_cast<int>( minSize.height() * dpmm ) );
if ( !mContext.isValidWidthHeight( size.width(), size.height() ) )
{
throw QgsServerException( QStringLiteral( "Legend image is too large" ) );
}
image.reset( createImage( size ) );
// configure painter
QPainter painter( image.get() );
QgsRenderContext context = QgsRenderContext::fromQPainter( nullptr );
context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
context.setPainter(&painter);
if ( painter.device() )
{
context.setScaleFactor( painter.device()->logicalDpiX() / 25.4 );
}
else
{
context.setScaleFactor( 3.465 ); //assume 88 dpi as standard value
}
if ( painter.renderHints() & QPainter::Antialiasing )
context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
if ( painter.renderHints() & QPainter::SmoothPixmapTransform )
context.setFlag( Qgis::RenderContextFlag::HighQualityImageTransforms, true );
if ( painter.renderHints() & QPainter::LosslessImageRendering )
context.setFlag( Qgis::RenderContextFlag::LosslessImageRendering, true );
QgsScopedRenderContextScaleToMm scaleContext( context );
// QGIS 4.0 -- take from real render context instead
Q_NOWARN_DEPRECATED_PUSH
context.setRendererScale( settings.mapScale() );
context.setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * context.scaleFactor() ) ) );
Q_NOWARN_DEPRECATED_POP
// rendering
renderer.drawLegend( context );
painter.end();
return image.release();
}
I'm a little stuck here. Issue was that the wmsDefaultMapUnitsPerMm on meter based CRS did not work, since the QgsRenderContext had no CRS. My goal is to apply it, and since it needs (a) calculate the image size according to the context and (b) create the context according to the painter, this needed a little reconstruction.
Current status is messy (and uses deprecated legendSettings). On cleaning up, I'm stuck at a point concerning the DPI/DPMM.
As far as I can see it we have:
- The DPI from
QgsWmsRenderContext
-> used then for the QPainter -> and used for the context. This is (when not passed by parameter) ~91 (calculated fromOGC_PX_M = 0.00028
). - The DPI that is set on default when passing no QPainter to
QgsRenderContext::fromQPainter
what is 88. - MapSettings provides a DPI from 96.
Which one should be used? I assume the one from the QgsWmsRenderContext
since we set it on the QImage.
-
Issue was that the CRS (and with it the Map Units) have not been considered on GetLegendGraphics requests what leaded to invisible small symbol sizes on using Meters on Scale. It worked for EPSG:4326 since it was the default value, but not for CRS using Meters (e.g. 2056).
-
It uses the MapUnits from QgsRenderContext::distanceArea() what means this should be set. Since QgsLegendSettings is marked as deprecated I set it from the mapSettings (if BBOX provided) or from the mWmsParameters.crs() (if no BBOX provided).
-
Since the painter's image size should now be calculated according to the QgsRenderContext it needs to be defined before and consider a nullptr-painter.
/*
* When using legend setting we did this: setMapUnitsPerPixel( mapSettings.mapUnitsPerPixel() ) where it multiplied it with 96 as dpi
* We would like to do this:
* context.setMapToPixel( mapSettings.mapToPixel() );
* but this leads to problems (most possible because of different DPI)
*
* When using legend setting we did this: setMapUnitsPerPixel( defaultMapUnitsPerPixel ) where it multiplied it with 96 as dpi
* We would like to do this:
* const double mmPerMapUnit = 1 / QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mProject );
* context.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * context.scaleFactor() ) ) );
* but this leads to problems (most possible because of different DPI)
*/