Skip to content

Commit

Permalink
In cases where we cannot convert the map extent back to a layer's
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nyalldawson committed Sep 21, 2020
1 parent 3ec1154 commit f3f226a
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 9 deletions.
1 change: 1 addition & 0 deletions python/core/auto_generated/qgsrendercontext.sip.in
Expand Up @@ -47,6 +47,7 @@ to be rendered etc.
LosslessImageRendering,
ApplyScalingWorkaroundForTextRendering,
Render3DMap,
ApplyClipAfterReprojection,
};
typedef QFlags<QgsRenderContext::Flag> Flags;

Expand Down
5 changes: 4 additions & 1 deletion src/core/qgsmaprendererjob.cpp
Expand Up @@ -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() )
Expand Down Expand Up @@ -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 );
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsrendercontext.h
Expand Up @@ -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 )

Expand Down
35 changes: 27 additions & 8 deletions src/core/symbology/qgssymbol.cpp
Expand Up @@ -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 );
Expand Down Expand Up @@ -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 )
{
Expand All @@ -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();

Expand All @@ -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 );
}

Expand All @@ -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 )
{
Expand Down

0 comments on commit f3f226a

Please sign in to comment.