mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-05 00:09:32 -04:00
Apply painter clip regions when rendering vector layers
This commit is contained in:
parent
30a3582a61
commit
0e67b65f8d
@ -54,6 +54,20 @@ The returned geometry will be automatically reprojected into the same CRS as the
|
||||
: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
|
||||
|
||||
static QPainterPath calculatePainterClipRegion( const QList< QgsMapClippingRegion > ®ions, const QgsRenderContext &context, bool &shouldClip );
|
||||
%Docstring
|
||||
Returns a QPainterPath representing the intersection of clipping ``regions`` from ``context`` which should be used to clip the painter
|
||||
during rendering.
|
||||
|
||||
The returned coordinates are in painter coordinates for the destination ``context``.
|
||||
|
||||
:param regions: list of clip regions which apply to the layer
|
||||
:param context: a render context
|
||||
:param shouldClip: will be set to ``True`` if the clipping path should be applied
|
||||
|
||||
:return: combined painter clipping region for use when rendering maps
|
||||
%End
|
||||
};
|
||||
|
||||
|
@ -122,3 +122,42 @@ QgsGeometry QgsMapClippingUtils::calculateFeatureIntersectionGeometry( const QLi
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QPainterPath QgsMapClippingUtils::calculatePainterClipRegion( const QList<QgsMapClippingRegion> ®ions, const QgsRenderContext &context, bool &shouldClip )
|
||||
{
|
||||
QgsGeometry result;
|
||||
bool first = true;
|
||||
shouldClip = false;
|
||||
for ( const QgsMapClippingRegion ®ion : regions )
|
||||
{
|
||||
if ( region.geometry().type() != QgsWkbTypes::PolygonGeometry )
|
||||
continue;
|
||||
|
||||
if ( region.featureClip() != QgsMapClippingRegion::FeatureClippingType::PainterClip )
|
||||
continue;
|
||||
|
||||
shouldClip = true;
|
||||
if ( first )
|
||||
{
|
||||
result = region.geometry();
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = result.intersection( region.geometry() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !shouldClip )
|
||||
return QPainterPath();
|
||||
|
||||
// filter out polygon parts from result only
|
||||
result.convertGeometryCollectionToSubclass( QgsWkbTypes::PolygonGeometry );
|
||||
|
||||
// transform to painter coordinates
|
||||
result.mapToPixel( context.mapToPixel() );
|
||||
|
||||
QPainterPath path;
|
||||
path.addPolygon( result.asQPolygonF() );
|
||||
return path;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "qgis_core.h"
|
||||
#include "qgis_sip.h"
|
||||
#include <QList>
|
||||
#include <QPainterPath>
|
||||
|
||||
class QgsRenderContext;
|
||||
class QgsMapLayer;
|
||||
@ -69,6 +70,20 @@ class CORE_EXPORT QgsMapClippingUtils
|
||||
* \returns combined clipping region for use when rendering features
|
||||
*/
|
||||
static QgsGeometry calculateFeatureIntersectionGeometry( const QList< QgsMapClippingRegion > ®ions, const QgsRenderContext &context, bool &shouldClip );
|
||||
|
||||
/**
|
||||
* Returns a QPainterPath representing the intersection of clipping \a regions from \a context which should be used to clip the painter
|
||||
* during rendering.
|
||||
*
|
||||
* The returned coordinates are in painter coordinates for the destination \a context.
|
||||
*
|
||||
* \param regions list of clip regions which apply to the layer
|
||||
* \param context a render context
|
||||
* \param shouldClip will be set to TRUE if the clipping path should be applied
|
||||
*
|
||||
* \returns combined painter clipping region for use when rendering maps
|
||||
*/
|
||||
static QPainterPath calculatePainterClipRegion( const QList< QgsMapClippingRegion > ®ions, const QgsRenderContext &context, bool &shouldClip );
|
||||
};
|
||||
|
||||
#endif // QGSMAPCLIPPINGUTILS_H
|
||||
|
@ -189,6 +189,11 @@ bool QgsVectorLayerRenderer::render()
|
||||
requestExtent = requestExtent.intersect( mClipFilterGeom.boundingBox() );
|
||||
|
||||
mClipFeatureGeom = QgsMapClippingUtils::calculateFeatureIntersectionGeometry( mClippingRegions, context, mApplyClipGeometries );
|
||||
|
||||
bool needsPainterClipPath = false;
|
||||
const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, context, needsPainterClipPath );
|
||||
if ( needsPainterClipPath )
|
||||
context.painter()->setClipPath( path, Qt::IntersectClip );
|
||||
}
|
||||
mRenderer->modifyRequestExtent( requestExtent, context );
|
||||
|
||||
|
@ -22,7 +22,8 @@ from qgis.core import (
|
||||
QgsVectorLayer,
|
||||
QgsCoordinateTransform,
|
||||
QgsCoordinateReferenceSystem,
|
||||
QgsProject
|
||||
QgsProject,
|
||||
QgsMapToPixel
|
||||
)
|
||||
|
||||
|
||||
@ -112,6 +113,42 @@ class TestQgsMapClippingUtils(unittest.TestCase):
|
||||
self.assertTrue(should_clip)
|
||||
self.assertEqual(geom.asWkt(0), 'Polygon ((11132 0, 0 0, 0 111325, 11132 111325, 11132 0))')
|
||||
|
||||
def testPainterClipPath(self):
|
||||
region = QgsMapClippingRegion(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))'))
|
||||
region.setFeatureClip(QgsMapClippingRegion.FeatureClippingType.PainterClip)
|
||||
region2 = QgsMapClippingRegion(QgsGeometry.fromWkt('Polygon((0 0, 0.1 0, 0.1 2, 0 2, 0 0))'))
|
||||
region2.setFeatureClip(QgsMapClippingRegion.FeatureClippingType.Intersects)
|
||||
region3 = QgsMapClippingRegion(QgsGeometry.fromWkt('Polygon((0 0, 0.1 0, 0.1 2, 0 2, 0 0))'))
|
||||
region3.setFeatureClip(QgsMapClippingRegion.FeatureClippingType.PainterClip)
|
||||
|
||||
rc = QgsRenderContext()
|
||||
|
||||
path, should_clip = QgsMapClippingUtils.calculatePainterClipRegion([], rc)
|
||||
self.assertFalse(should_clip)
|
||||
self.assertEqual(path.elementCount(), 0)
|
||||
|
||||
path, should_clip = QgsMapClippingUtils.calculatePainterClipRegion([region], rc)
|
||||
self.assertTrue(should_clip)
|
||||
self.assertEqual(QgsGeometry.fromQPolygonF(path.toFillPolygon()).asWkt(1), 'Polygon ((0 1, 1 1, 1 0, 0 0, 0 1))')
|
||||
|
||||
# region2 is a Intersects type clipping region, should not apply here
|
||||
path, should_clip = QgsMapClippingUtils.calculatePainterClipRegion([region2], rc)
|
||||
self.assertFalse(should_clip)
|
||||
self.assertEqual(path.elementCount(), 0)
|
||||
|
||||
path, should_clip = QgsMapClippingUtils.calculatePainterClipRegion([region, region2], rc)
|
||||
self.assertTrue(should_clip)
|
||||
self.assertEqual(QgsGeometry.fromQPolygonF(path.toFillPolygon()).asWkt(1), 'Polygon ((0 1, 1 1, 1 0, 0 0, 0 1))')
|
||||
|
||||
path, should_clip = QgsMapClippingUtils.calculatePainterClipRegion([region, region2, region3], rc)
|
||||
self.assertTrue(should_clip)
|
||||
self.assertEqual(QgsGeometry.fromQPolygonF(path.toFillPolygon()).asWkt(1), 'Polygon ((0.1 1, 0 1, 0 0, 0.1 0, 0.1 1))')
|
||||
|
||||
rc.setMapToPixel(QgsMapToPixel(5, 10, 11, 200, 150, 0))
|
||||
path, should_clip = QgsMapClippingUtils.calculatePainterClipRegion([region, region3], rc)
|
||||
self.assertTrue(should_clip)
|
||||
self.assertEqual(QgsGeometry.fromQPolygonF(path.toFillPolygon()).asWkt(0), 'Polygon ((98 77, 98 77, 98 77, 98 77, 98 77))')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -129,6 +129,48 @@ class TestQgsVectorLayerRenderer(unittest.TestCase):
|
||||
self.report += renderchecker.report()
|
||||
self.assertTrue(result)
|
||||
|
||||
def testRenderWithPainterClipRegions(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)
|
||||
poly_layer.setRenderer(renderer)
|
||||
|
||||
mapsettings = QgsMapSettings()
|
||||
mapsettings.setOutputSize(QSize(400, 400))
|
||||
mapsettings.setOutputDpi(96)
|
||||
mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
|
||||
mapsettings.setExtent(QgsRectangle(-13875783.2, 2266009.4, -8690110.7, 6673344.5))
|
||||
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.PainterClip)
|
||||
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.PainterClip)
|
||||
mapsettings.addClippingRegion(region)
|
||||
mapsettings.addClippingRegion(region2)
|
||||
|
||||
renderchecker = QgsMultiRenderChecker()
|
||||
renderchecker.setMapSettings(mapsettings)
|
||||
renderchecker.setControlPathPrefix('vectorlayerrenderer')
|
||||
renderchecker.setControlName('expected_painterclip_region')
|
||||
result = renderchecker.runTest('expected_painterclip_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_painterclip_region')
|
||||
result = renderchecker.runTest('expected_painterclip_region')
|
||||
self.report += renderchecker.report()
|
||||
self.assertTrue(result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 460 KiB |
Loading…
x
Reference in New Issue
Block a user