mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-09 00:08:52 -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
|
: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
|
: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
|
%End
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -122,3 +122,42 @@ QgsGeometry QgsMapClippingUtils::calculateFeatureIntersectionGeometry( const QLi
|
|||||||
|
|
||||||
return result;
|
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_core.h"
|
||||||
#include "qgis_sip.h"
|
#include "qgis_sip.h"
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QPainterPath>
|
||||||
|
|
||||||
class QgsRenderContext;
|
class QgsRenderContext;
|
||||||
class QgsMapLayer;
|
class QgsMapLayer;
|
||||||
@ -69,6 +70,20 @@ class CORE_EXPORT QgsMapClippingUtils
|
|||||||
* \returns combined clipping region for use when rendering features
|
* \returns combined clipping region for use when rendering features
|
||||||
*/
|
*/
|
||||||
static QgsGeometry calculateFeatureIntersectionGeometry( const QList< QgsMapClippingRegion > ®ions, const QgsRenderContext &context, bool &shouldClip );
|
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
|
#endif // QGSMAPCLIPPINGUTILS_H
|
||||||
|
@ -189,6 +189,11 @@ bool QgsVectorLayerRenderer::render()
|
|||||||
requestExtent = requestExtent.intersect( mClipFilterGeom.boundingBox() );
|
requestExtent = requestExtent.intersect( mClipFilterGeom.boundingBox() );
|
||||||
|
|
||||||
mClipFeatureGeom = QgsMapClippingUtils::calculateFeatureIntersectionGeometry( mClippingRegions, context, mApplyClipGeometries );
|
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 );
|
mRenderer->modifyRequestExtent( requestExtent, context );
|
||||||
|
|
||||||
|
@ -22,7 +22,8 @@ from qgis.core import (
|
|||||||
QgsVectorLayer,
|
QgsVectorLayer,
|
||||||
QgsCoordinateTransform,
|
QgsCoordinateTransform,
|
||||||
QgsCoordinateReferenceSystem,
|
QgsCoordinateReferenceSystem,
|
||||||
QgsProject
|
QgsProject,
|
||||||
|
QgsMapToPixel
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -112,6 +113,42 @@ class TestQgsMapClippingUtils(unittest.TestCase):
|
|||||||
self.assertTrue(should_clip)
|
self.assertTrue(should_clip)
|
||||||
self.assertEqual(geom.asWkt(0), 'Polygon ((11132 0, 0 0, 0 111325, 11132 111325, 11132 0))')
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -129,6 +129,48 @@ class TestQgsVectorLayerRenderer(unittest.TestCase):
|
|||||||
self.report += renderchecker.report()
|
self.report += renderchecker.report()
|
||||||
self.assertTrue(result)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 460 KiB |
Loading…
x
Reference in New Issue
Block a user