Skip to content

Commit

Permalink
Apply clipping regions with Intersect mode during vector layer rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jul 2, 2020
1 parent e4150b2 commit 291dbe8
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 4 deletions.
13 changes: 13 additions & 0 deletions python/core/auto_generated/qgsmapclippingutils.sip.in
Expand Up @@ -42,6 +42,19 @@ a feature request.
:return: combined clipping region for use when filtering features to render
%End

static QgsGeometry calculateFeatureIntersectionGeometry( const QList< QgsMapClippingRegion > &regions, const QgsRenderContext &context, bool &shouldClip );
%Docstring
Returns the geometry representing the intersection of clipping ``regions`` from ``context`` which should be used to clip individual
feature geometries prior to rendering.

The returned geometry will be automatically reprojected into the same CRS as the source layer, ready for use for clipping features.

:param regions: list of clip regions which apply to the layer
:param context: a render context
:param shouldClip: will be set to ``True`` if layer's features should be filtered, i.e. one or more clipping regions applies to the layer

:return: combined clipping region for use when rendering features
%End
};

/************************************************************************
Expand Down
49 changes: 49 additions & 0 deletions src/core/qgsmapclippingutils.cpp
Expand Up @@ -56,6 +56,9 @@ QgsGeometry QgsMapClippingUtils::calculateFeatureRequestGeometry( const QList< Q
}
}

if ( !shouldFilter )
return QgsGeometry();

// filter out polygon parts from result only
result.convertGeometryCollectionToSubclass( QgsWkbTypes::PolygonGeometry );

Expand All @@ -73,3 +76,49 @@ QgsGeometry QgsMapClippingUtils::calculateFeatureRequestGeometry( const QList< Q

return result;
}

QgsGeometry QgsMapClippingUtils::calculateFeatureIntersectionGeometry( const QList<QgsMapClippingRegion> &regions, const QgsRenderContext &context, bool &shouldClip )
{
QgsGeometry result;
bool first = true;
shouldClip = false;
for ( const QgsMapClippingRegion &region : regions )
{
if ( region.geometry().type() != QgsWkbTypes::PolygonGeometry )
continue;

if ( region.featureClip() != QgsMapClippingRegion::FeatureClippingType::Intersect )
continue;

shouldClip = true;
if ( first )
{
result = region.geometry();
first = false;
}
else
{
result = result.intersection( region.geometry() );
}
}

if ( !shouldClip )
return QgsGeometry();

// filter out polygon parts from result only
result.convertGeometryCollectionToSubclass( QgsWkbTypes::PolygonGeometry );

// lastly transform back to layer CRS
try
{
result.transform( context.coordinateTransform(), QgsCoordinateTransform::ReverseTransform );
}
catch ( QgsCsException & )
{
QgsDebugMsg( QStringLiteral( "Could not transform clipping region to layer CRS" ) );
shouldClip = false;
return QgsGeometry();
}

return result;
}
13 changes: 13 additions & 0 deletions src/core/qgsmapclippingutils.h
Expand Up @@ -56,6 +56,19 @@ class CORE_EXPORT QgsMapClippingUtils
*/
static QgsGeometry calculateFeatureRequestGeometry( const QList< QgsMapClippingRegion > &regions, const QgsRenderContext &context, bool &shouldFilter );

/**
* Returns the geometry representing the intersection of clipping \a regions from \a context which should be used to clip individual
* feature geometries prior to rendering.
*
* The returned geometry will be automatically reprojected into the same CRS as the source layer, ready for use for clipping features.
*
* \param regions list of clip regions which apply to the layer
* \param context a render context
* \param shouldClip will be set to TRUE if layer's features should be filtered, i.e. one or more clipping regions applies to the layer
*
* \returns combined clipping region for use when rendering features
*/
static QgsGeometry calculateFeatureIntersectionGeometry( const QList< QgsMapClippingRegion > &regions, const QgsRenderContext &context, bool &shouldClip );
};

#endif // QGSMAPCLIPPINGUTILS_H
14 changes: 14 additions & 0 deletions src/core/qgsvectorlayerrenderer.cpp
Expand Up @@ -187,6 +187,8 @@ bool QgsVectorLayerRenderer::render()
{
mClipFilterGeom = QgsMapClippingUtils::calculateFeatureRequestGeometry( mClippingRegions, context, mApplyClipFilter );
requestExtent = requestExtent.intersect( mClipFilterGeom.boundingBox() );

mClipFeatureGeom = QgsMapClippingUtils::calculateFeatureIntersectionGeometry( mClippingRegions, context, mApplyClipGeometries );
}
mRenderer->modifyRequestExtent( requestExtent, context );

Expand Down Expand Up @@ -345,6 +347,12 @@ void QgsVectorLayerRenderer::drawRenderer( QgsFeatureIterator &fit )
if ( clipEngine && !clipEngine->intersects( fet.geometry().constGet() ) )
continue; // skip features outside of clipping region

if ( mApplyClipGeometries )
{
QgsGeometry original = fet.geometry();
fet.setGeometry( original.intersection( mClipFeatureGeom ) );
}

context.expressionContext().setFeature( fet );

bool sel = context.showSelection() && mSelectedFeatureIds.contains( fet.id() );
Expand Down Expand Up @@ -439,6 +447,12 @@ void QgsVectorLayerRenderer::drawRendererLevels( QgsFeatureIterator &fit )
if ( clipEngine && !clipEngine->intersects( fet.geometry().constGet() ) )
continue; // skip features outside of clipping region

if ( mApplyClipGeometries )
{
QgsGeometry original = fet.geometry();
fet.setGeometry( original.intersection( mClipFeatureGeom ) );
}

context.expressionContext().setFeature( fet );
QgsSymbol *sym = mRenderer->symbolForFeature( fet, context );
if ( !sym )
Expand Down
2 changes: 2 additions & 0 deletions src/core/qgsvectorlayerrenderer.h
Expand Up @@ -163,6 +163,8 @@ class QgsVectorLayerRenderer : public QgsMapLayerRenderer
QList< QgsMapClippingRegion > mClippingRegions;
QgsGeometry mClipFilterGeom;
bool mApplyClipFilter = false;
QgsGeometry mClipFeatureGeom;
bool mApplyClipGeometries = false;
};


Expand Down
35 changes: 31 additions & 4 deletions tests/src/python/test_qgsvectorlayerrenderer.py
Expand Up @@ -61,7 +61,9 @@ def testRenderWithIntersectsRegions(self):
mapsettings.setLayers([poly_layer])

region = QgsMapClippingRegion(QgsGeometry.fromWkt('Polygon ((-11725957 5368254, -12222900 4807501, -12246014 3834025, -12014878 3496059, -11259833 3518307, -10751333 3621153, -10574129 4516741, -10847640 5194995, -11105742 5325957, -11725957 5368254))'))
region.setFeatureClip(QgsMapClippingRegion.FeatureClippingType.Intersects)
region2 = QgsMapClippingRegion(QgsGeometry.fromWkt('Polygon ((-11032549 5421399, -11533344 4693167, -11086481 4229112, -11167378 3742984, -10616504 3553984, -10161936 3925771, -9618766 4668482, -9472380 5620753, -10115709 5965063, -11032549 5421399))'))
region2.setFeatureClip(QgsMapClippingRegion.FeatureClippingType.Intersects)
mapsettings.addClippingRegion(region)
mapsettings.addClippingRegion(region2)

Expand All @@ -73,13 +75,24 @@ def testRenderWithIntersectsRegions(self):
self.report += renderchecker.report()
self.assertTrue(result)

def testRenderWithIntersectsRegionsSymbolLayers(self):
# also try with symbol levels
renderer.setUsingSymbolLevels(True)
poly_layer.setRenderer(renderer)

renderchecker = QgsMultiRenderChecker()
renderchecker.setMapSettings(mapsettings)
renderchecker.setControlPathPrefix('vectorlayerrenderer')
renderchecker.setControlName('expected_intersects_region')
result = renderchecker.runTest('expected_intersects_region')
self.report += renderchecker.report()
self.assertTrue(result)

def testRenderWithIntersectionRegions(self):
poly_layer = QgsVectorLayer(os.path.join(TEST_DATA_DIR, 'polys.shp'))
self.assertTrue(poly_layer.isValid())

sym1 = QgsFillSymbol.createSimple({'color': '#ff00ff', 'outline_color': '#000000', 'outline_width': '1'})
renderer = QgsSingleSymbolRenderer(sym1)
renderer.setUsingSymbolLevels(True)
poly_layer.setRenderer(renderer)

mapsettings = QgsMapSettings()
Expand All @@ -90,15 +103,29 @@ def testRenderWithIntersectsRegionsSymbolLayers(self):
mapsettings.setLayers([poly_layer])

region = QgsMapClippingRegion(QgsGeometry.fromWkt('Polygon ((-11725957 5368254, -12222900 4807501, -12246014 3834025, -12014878 3496059, -11259833 3518307, -10751333 3621153, -10574129 4516741, -10847640 5194995, -11105742 5325957, -11725957 5368254))'))
region.setFeatureClip(QgsMapClippingRegion.FeatureClippingType.Intersect)
region2 = QgsMapClippingRegion(QgsGeometry.fromWkt('Polygon ((-11032549 5421399, -11533344 4693167, -11086481 4229112, -11167378 3742984, -10616504 3553984, -10161936 3925771, -9618766 4668482, -9472380 5620753, -10115709 5965063, -11032549 5421399))'))
region2.setFeatureClip(QgsMapClippingRegion.FeatureClippingType.Intersect)
mapsettings.addClippingRegion(region)
mapsettings.addClippingRegion(region2)

renderchecker = QgsMultiRenderChecker()
renderchecker.setMapSettings(mapsettings)
renderchecker.setControlPathPrefix('vectorlayerrenderer')
renderchecker.setControlName('expected_intersects_region')
result = renderchecker.runTest('expected_intersects_region')
renderchecker.setControlName('expected_intersection_region')
result = renderchecker.runTest('expected_intersection_region')
self.report += renderchecker.report()
self.assertTrue(result)

# also try with symbol levels
renderer.setUsingSymbolLevels(True)
poly_layer.setRenderer(renderer)

renderchecker = QgsMultiRenderChecker()
renderchecker.setMapSettings(mapsettings)
renderchecker.setControlPathPrefix('vectorlayerrenderer')
renderchecker.setControlName('expected_intersection_region')
result = renderchecker.runTest('expected_intersection_region')
self.report += renderchecker.report()
self.assertTrue(result)

Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 291dbe8

Please sign in to comment.