Skip to content

Commit

Permalink
Respect clipping intersects regions when rendering vector layers
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jul 2, 2020
1 parent b10b169 commit e4150b2
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 1 deletion.
30 changes: 29 additions & 1 deletion src/core/qgsvectorlayerrenderer.cpp
Expand Up @@ -39,6 +39,7 @@
#include "qgsexpressioncontextutils.h"
#include "qgsrenderedfeaturehandlerinterface.h"
#include "qgsvectorlayertemporalproperties.h"
#include "qgsmapclippingutils.h"

#include <QPicture>

Expand Down Expand Up @@ -124,8 +125,9 @@ QgsVectorLayerRenderer::QgsVectorLayerRenderer( QgsVectorLayer *layer, QgsRender
//register label and diagram layer to the labeling engine
prepareLabeling( layer, mAttrNames );
prepareDiagrams( layer, mAttrNames );
}

mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( context, layer );
}

QgsVectorLayerRenderer::~QgsVectorLayerRenderer()
{
Expand Down Expand Up @@ -181,6 +183,11 @@ bool QgsVectorLayerRenderer::render()
QString rendererFilter = mRenderer->filter( mFields );

QgsRectangle requestExtent = context.extent();
if ( !mClippingRegions.empty() )
{
mClipFilterGeom = QgsMapClippingUtils::calculateFeatureRequestGeometry( mClippingRegions, context, mApplyClipFilter );
requestExtent = requestExtent.intersect( mClipFilterGeom.boundingBox() );
}
mRenderer->modifyRequestExtent( requestExtent, context );

QgsFeatureRequest featureRequest = QgsFeatureRequest()
Expand Down Expand Up @@ -314,6 +321,13 @@ void QgsVectorLayerRenderer::drawRenderer( QgsFeatureIterator &fit )
QgsRenderContext &context = *renderContext();
context.expressionContext().appendScope( symbolScope );

std::unique_ptr< QgsGeometryEngine > clipEngine;
if ( mApplyClipFilter )
{
clipEngine.reset( QgsGeometry::createGeometryEngine( mClipFilterGeom.constGet() ) );
clipEngine->prepareGeometry();
}

QgsFeature fet;
while ( fit.nextFeature( fet ) )
{
Expand All @@ -328,6 +342,9 @@ void QgsVectorLayerRenderer::drawRenderer( QgsFeatureIterator &fit )
if ( !fet.hasGeometry() || fet.geometry().isEmpty() )
continue; // skip features without geometry

if ( clipEngine && !clipEngine->intersects( fet.geometry().constGet() ) )
continue; // skip features outside of clipping region

context.expressionContext().setFeature( fet );

bool sel = context.showSelection() && mSelectedFeatureIds.contains( fet.id() );
Expand Down Expand Up @@ -397,6 +414,14 @@ void QgsVectorLayerRenderer::drawRendererLevels( QgsFeatureIterator &fit )
QgsExpressionContextScope *symbolScope = QgsExpressionContextUtils::updateSymbolScope( nullptr, new QgsExpressionContextScope() );
std::unique_ptr< QgsExpressionContextScopePopper > scopePopper = qgis::make_unique< QgsExpressionContextScopePopper >( context.expressionContext(), symbolScope );


std::unique_ptr< QgsGeometryEngine > clipEngine;
if ( mApplyClipFilter )
{
clipEngine.reset( QgsGeometry::createGeometryEngine( mClipFilterGeom.constGet() ) );
clipEngine->prepareGeometry();
}

// 1. fetch features
QgsFeature fet;
while ( fit.nextFeature( fet ) )
Expand All @@ -411,6 +436,9 @@ void QgsVectorLayerRenderer::drawRendererLevels( QgsFeatureIterator &fit )
if ( !fet.hasGeometry() )
continue; // skip features without geometry

if ( clipEngine && !clipEngine->intersects( fet.geometry().constGet() ) )
continue; // skip features outside of clipping region

context.expressionContext().setFeature( fet );
QgsSymbol *sym = mRenderer->symbolForFeature( fet, context );
if ( !sym )
Expand Down
5 changes: 5 additions & 0 deletions src/core/qgsvectorlayerrenderer.h
Expand Up @@ -26,6 +26,7 @@ class QgsDiagramLayerSettings;

class QgsFeatureIterator;
class QgsSingleSymbolRenderer;
class QgsMapClippingRegion;

#define SIP_NO_FILE

Expand Down Expand Up @@ -158,6 +159,10 @@ class QgsVectorLayerRenderer : public QgsMapLayerRenderer

QgsVectorSimplifyMethod mSimplifyMethod;
bool mSimplifyGeometry;

QList< QgsMapClippingRegion > mClippingRegions;
QgsGeometry mClipFilterGeom;
bool mApplyClipFilter = false;
};


Expand Down
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -277,6 +277,7 @@ ADD_PYTHON_TEST(PyQgsVectorLayer test_qgsvectorlayer.py)
ADD_PYTHON_TEST(PyQgsVectorLayerCache test_qgsvectorlayercache.py)
ADD_PYTHON_TEST(PyQgsVectorLayerEditBuffer test_qgsvectorlayereditbuffer.py)
ADD_PYTHON_TEST(PyQgsVectorLayerNamedStyle test_qgsvectorlayer_namedstyle.py)
ADD_PYTHON_TEST(PyQgsVectorLayerRenderer test_qgsvectorlayerrenderer.py)
ADD_PYTHON_TEST(PyQgsVectorLayerSelectedFeatureSource test_qgsvectorlayerselectedfeaturesource.py)
ADD_PYTHON_TEST(PyQgsVectorLayerTemporalProperties test_qgsvectorlayertemporalproperties.py)
ADD_PYTHON_TEST(PyQgsVectorLayerUtils test_qgsvectorlayerutils.py)
Expand Down
107 changes: 107 additions & 0 deletions tests/src/python/test_qgsvectorlayerrenderer.py
@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsVectorLayerRenderer
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Nyall Dawson'
__date__ = '2020-06'
__copyright__ = 'Copyright 2020, The QGIS Project'

import qgis # NOQA

import os

from qgis.PyQt.QtCore import QSize, QDir

from qgis.core import (QgsVectorLayer,
QgsMapClippingRegion,
QgsRectangle,
QgsMultiRenderChecker,
QgsGeometry,
QgsSingleSymbolRenderer,
QgsMapSettings,
QgsFillSymbol,
QgsCoordinateReferenceSystem
)
from qgis.testing import start_app, unittest
from utilities import (unitTestDataPath)

# Convenience instances in case you may need them
# not used in this test
start_app()
TEST_DATA_DIR = unitTestDataPath()


class TestQgsVectorLayerRenderer(unittest.TestCase):

def setUp(self):
self.report = "<h1>Python QgsVectorLayerRenderer Tests</h1>\n"

def tearDown(self):
report_file_path = "%s/qgistest.html" % QDir.tempPath()
with open(report_file_path, 'a') as report_file:
report_file.write(self.report)

def testRenderWithIntersectsRegions(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))'))
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))'))
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')
self.report += renderchecker.report()
self.assertTrue(result)

def testRenderWithIntersectsRegionsSymbolLayers(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()
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))'))
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))'))
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')
self.report += renderchecker.report()
self.assertTrue(result)


if __name__ == '__main__':
unittest.main()
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 e4150b2

Please sign in to comment.