Skip to content

Commit f3f226a

Browse files
committedSep 21, 2020
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
1 parent 3ec1154 commit f3f226a

File tree

4 files changed

+33
-9
lines changed

4 files changed

+33
-9
lines changed
 

‎python/core/auto_generated/qgsrendercontext.sip.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ to be rendered etc.
4747
LosslessImageRendering,
4848
ApplyScalingWorkaroundForTextRendering,
4949
Render3DMap,
50+
ApplyClipAfterReprojection,
5051
};
5152
typedef QFlags<QgsRenderContext::Flag> Flags;
5253

‎src/core/qgsmaprendererjob.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,10 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEn
344344
QgsCoordinateTransform ct;
345345

346346
ct = mSettings.layerTransform( ml );
347+
bool haveExtentInLayerCrs = true;
347348
if ( ct.isValid() )
348349
{
349-
reprojectToLayerExtent( ml, ct, r1, r2 );
350+
haveExtentInLayerCrs = reprojectToLayerExtent( ml, ct, r1, r2 );
350351
}
351352
QgsDebugMsgLevel( "extent: " + r1.toString(), 3 );
352353
if ( !r1.isFinite() || !r2.isFinite() )
@@ -382,6 +383,8 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEn
382383
job.context.setLabelingEngine( labelingEngine2 );
383384
job.context.setCoordinateTransform( ct );
384385
job.context.setExtent( r1 );
386+
if ( !haveExtentInLayerCrs )
387+
job.context.setFlag( QgsRenderContext::ApplyClipAfterReprojection, true );
385388

386389
if ( mFeatureFilterProvider )
387390
job.context.setFeatureFilterProvider( mFeatureFilterProvider );

‎src/core/qgsrendercontext.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class CORE_EXPORT QgsRenderContext : public QgsTemporalRangeObject
8484
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.
8585
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.
8686
Render3DMap = 0x4000, //!< Render is for a 3D map
87+
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().
8788
};
8889
Q_DECLARE_FLAGS( Flags, Flag )
8990

‎src/core/symbology/qgssymbol.cpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ QPolygonF QgsSymbol::_getLineString( QgsRenderContext &context, const QgsCurve &
110110
QPolygonF pts;
111111

112112
//apply clipping for large lines to achieve a better rendering performance
113-
if ( clipToExtent && nPoints > 1 )
113+
if ( clipToExtent && nPoints > 1 && !( context.flags() & QgsRenderContext::ApplyClipAfterReprojection ) )
114114
{
115-
const QgsRectangle &e = context.extent();
115+
const QgsRectangle e = context.extent();
116116
const double cw = e.width() / 10;
117117
const double ch = e.height() / 10;
118118
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 &
143143
return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
144144
} ), pts.end() );
145145

146+
if ( clipToExtent && nPoints > 1 && context.flags() & QgsRenderContext::ApplyClipAfterReprojection )
147+
{
148+
// early clipping was not possible, so we have to apply it here after transformation
149+
const QgsRectangle e = context.mapExtent();
150+
const double cw = e.width() / 10;
151+
const double ch = e.height() / 10;
152+
const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
153+
pts = QgsClipper::clippedLine( pts, clipRect );
154+
}
155+
146156
QPointF *ptr = pts.data();
147157
for ( int i = 0; i < pts.size(); ++i, ++ptr )
148158
{
@@ -156,10 +166,6 @@ QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve
156166
{
157167
const QgsCoordinateTransform ct = context.coordinateTransform();
158168
const QgsMapToPixel &mtp = context.mapToPixel();
159-
const QgsRectangle &e = context.extent();
160-
const double cw = e.width() / 10;
161-
const double ch = e.height() / 10;
162-
QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
163169

164170
QPolygonF poly = curve.asQPolygonF();
165171

@@ -176,9 +182,12 @@ QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve
176182
}
177183

178184
//clip close to view extent, if needed
179-
const QRectF ptsRect = poly.boundingRect();
180-
if ( clipToExtent && !context.extent().contains( ptsRect ) )
185+
if ( clipToExtent && !( context.flags() & QgsRenderContext::ApplyClipAfterReprojection ) && !context.extent().contains( poly.boundingRect() ) )
181186
{
187+
const QgsRectangle e = context.extent();
188+
const double cw = e.width() / 10;
189+
const double ch = e.height() / 10;
190+
const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
182191
QgsClipper::trimPolygon( poly, clipRect );
183192
}
184193

@@ -202,6 +211,16 @@ QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve
202211
return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
203212
} ), poly.end() );
204213

214+
if ( clipToExtent && context.flags() & QgsRenderContext::ApplyClipAfterReprojection && !context.mapExtent().contains( poly.boundingRect() ) )
215+
{
216+
// early clipping was not possible, so we have to apply it here after transformation
217+
const QgsRectangle e = context.mapExtent();
218+
const double cw = e.width() / 10;
219+
const double ch = e.height() / 10;
220+
const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
221+
QgsClipper::trimPolygon( poly, clipRect );
222+
}
223+
205224
QPointF *ptr = poly.data();
206225
for ( int i = 0; i < poly.size(); ++i, ++ptr )
207226
{

0 commit comments

Comments
 (0)
Please sign in to comment.