In cases where we cannot convert the map extent back to a layer's

extent and have had to resort to fetching all features from a layer,
defer the geometry clipping the map extent so that it occurs
AFTER transforming the layer's geometries to the target map extent.

This allows us to correctly clip the feature geometries in the case
that the visible extent available from the render context for the layer
is not accurate (i.e. it's a whole of globe fallback), and avoids
rendering features which fall far outside of the visible map
region.

Fixes #38878
This commit is contained in:
Nyall Dawson 2020-09-21 13:39:22 +10:00
parent 3ec1154796
commit f3f226aa69
4 changed files with 33 additions and 9 deletions

View File

@ -47,6 +47,7 @@ to be rendered etc.
LosslessImageRendering,
ApplyScalingWorkaroundForTextRendering,
Render3DMap,
ApplyClipAfterReprojection,
};
typedef QFlags<QgsRenderContext::Flag> Flags;

View File

@ -344,9 +344,10 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEn
QgsCoordinateTransform ct;
ct = mSettings.layerTransform( ml );
bool haveExtentInLayerCrs = true;
if ( ct.isValid() )
{
reprojectToLayerExtent( ml, ct, r1, r2 );
haveExtentInLayerCrs = reprojectToLayerExtent( ml, ct, r1, r2 );
}
QgsDebugMsgLevel( "extent: " + r1.toString(), 3 );
if ( !r1.isFinite() || !r2.isFinite() )
@ -382,6 +383,8 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEn
job.context.setLabelingEngine( labelingEngine2 );
job.context.setCoordinateTransform( ct );
job.context.setExtent( r1 );
if ( !haveExtentInLayerCrs )
job.context.setFlag( QgsRenderContext::ApplyClipAfterReprojection, true );
if ( mFeatureFilterProvider )
job.context.setFeatureFilterProvider( mFeatureFilterProvider );

View File

@ -84,6 +84,7 @@ class CORE_EXPORT QgsRenderContext : public QgsTemporalRangeObject
LosslessImageRendering = 0x1000, //!< Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some destination devices (e.g. PDF). This flag only works with builds based on Qt 5.13 or later.
ApplyScalingWorkaroundForTextRendering = 0x2000, //!< Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters scaled out by a large amount) when rendering text. Generally this is recommended, but it may incur some performance cost.
Render3DMap = 0x4000, //!< Render is for a 3D map
ApplyClipAfterReprojection = 0x8000, //!< Feature geometry clipping to mapExtent() must be performed after the geometries are transformed using coordinateTransform(). Usually feature geometry clipping occurs using the extent() in the layer's CRS prior to geometry transformation, but in some cases when extent() could not be accurately calculated it is necessary to clip geometries to mapExtent() AFTER transforming them using coordinateTransform().
};
Q_DECLARE_FLAGS( Flags, Flag )

View File

@ -110,9 +110,9 @@ QPolygonF QgsSymbol::_getLineString( QgsRenderContext &context, const QgsCurve &
QPolygonF pts;
//apply clipping for large lines to achieve a better rendering performance
if ( clipToExtent && nPoints > 1 )
if ( clipToExtent && nPoints > 1 && !( context.flags() & QgsRenderContext::ApplyClipAfterReprojection ) )
{
const QgsRectangle &e = context.extent();
const QgsRectangle e = context.extent();
const double cw = e.width() / 10;
const double ch = e.height() / 10;
const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
@ -143,6 +143,16 @@ QPolygonF QgsSymbol::_getLineString( QgsRenderContext &context, const QgsCurve &
return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
} ), pts.end() );
if ( clipToExtent && nPoints > 1 && context.flags() & QgsRenderContext::ApplyClipAfterReprojection )
{
// early clipping was not possible, so we have to apply it here after transformation
const QgsRectangle e = context.mapExtent();
const double cw = e.width() / 10;
const double ch = e.height() / 10;
const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
pts = QgsClipper::clippedLine( pts, clipRect );
}
QPointF *ptr = pts.data();
for ( int i = 0; i < pts.size(); ++i, ++ptr )
{
@ -156,10 +166,6 @@ QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve
{
const QgsCoordinateTransform ct = context.coordinateTransform();
const QgsMapToPixel &mtp = context.mapToPixel();
const QgsRectangle &e = context.extent();
const double cw = e.width() / 10;
const double ch = e.height() / 10;
QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
QPolygonF poly = curve.asQPolygonF();
@ -176,9 +182,12 @@ QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve
}
//clip close to view extent, if needed
const QRectF ptsRect = poly.boundingRect();
if ( clipToExtent && !context.extent().contains( ptsRect ) )
if ( clipToExtent && !( context.flags() & QgsRenderContext::ApplyClipAfterReprojection ) && !context.extent().contains( poly.boundingRect() ) )
{
const QgsRectangle e = context.extent();
const double cw = e.width() / 10;
const double ch = e.height() / 10;
const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
QgsClipper::trimPolygon( poly, clipRect );
}
@ -202,6 +211,16 @@ QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve
return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
} ), poly.end() );
if ( clipToExtent && context.flags() & QgsRenderContext::ApplyClipAfterReprojection && !context.mapExtent().contains( poly.boundingRect() ) )
{
// early clipping was not possible, so we have to apply it here after transformation
const QgsRectangle e = context.mapExtent();
const double cw = e.width() / 10;
const double ch = e.height() / 10;
const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
QgsClipper::trimPolygon( poly, clipRect );
}
QPointF *ptr = poly.data();
for ( int i = 0; i < poly.size(); ++i, ++ptr )
{