Skip to content
Permalink
Browse files

Apply clipping regions with Intersect mode during vector layer rendering

  • Loading branch information
nyalldawson committed Jul 2, 2020
1 parent e4150b2 commit 291dbe8cbaaf64c3c985169f1100e6ddea71db31
@@ -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
};

/************************************************************************
@@ -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 );

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

@@ -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() );
@@ -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 )
@@ -163,6 +163,8 @@ class QgsVectorLayerRenderer : public QgsMapLayerRenderer
QList< QgsMapClippingRegion > mClippingRegions;
QgsGeometry mClipFilterGeom;
bool mApplyClipFilter = false;
QgsGeometry mClipFeatureGeom;
bool mApplyClipGeometries = false;
};


@@ -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)

@@ -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()
@@ -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)

Binary file not shown.

0 comments on commit 291dbe8

Please sign in to comment.
You can’t perform that action at this time.