Initial work on rendered feature handler interface

Adds an interface for classes which provider custom
handlers for features rendered as part of a map render job.

QgsRenderedFeatureHandlerInterface objects are registered
in the QgsMapSettings objects used to construct map render
jobs. During the rendering operation, the handleRenderedFeature()
method will be called once for every rendered feature, allowing
the handler to perform some custom task based on the provided
information.

They can be used for custom tasks which operate on a set of
rendered features, such as creating spatial indexes of the
location and rendered symbology bounding box of all features
rendered on a map.
This commit is contained in:
Nyall Dawson 2019-08-05 10:01:01 +10:00
parent 2cc6775354
commit c71cd4b5fc
12 changed files with 296 additions and 1 deletions

View File

@ -656,6 +656,27 @@ The default is to use no global simplification, and fallback to individual layer
.. seealso:: :py:func:`setSimplifyMethod`
.. versionadded:: 3.10
%End
void addRenderedFeatureHandler( QgsRenderedFeatureHandlerInterface *handler );
%Docstring
Adds a rendered feature ``handler`` to use while rendering the map settings.
Ownership of ``handler`` is NOT transferred, and it is the caller's responsibility to ensure
that the handler exists for the lifetime of the map render job.
.. seealso:: :py:func:`renderedFeatureHandlers`
.. versionadded:: 3.10
%End
QList< QgsRenderedFeatureHandlerInterface * > renderedFeatureHandlers() const;
%Docstring
Returns the list of rendered feature handlers to use while rendering the map settings.
.. seealso:: :py:func:`addRenderedFeatureHandler`
.. versionadded:: 3.10
%End

View File

@ -569,6 +569,24 @@ Sets the text render ``format``, which dictates how text is rendered (e.g. as pa
.. seealso:: :py:func:`textRenderFormat`
.. versionadded:: 3.4.3
%End
QList<QgsRenderedFeatureHandlerInterface *> renderedFeatureHandlers() const;
%Docstring
Returns the list of rendered feature handlers to use while rendering map layers.
.. seealso:: :py:func:`hasRenderedFeatureHandlers`
.. versionadded:: 3.10
%End
bool hasRenderedFeatureHandlers() const;
%Docstring
Returns ``True`` if the context has any rendered feature handlers.
.. seealso:: :py:func:`renderedFeatureHandlers`
.. versionadded:: 3.10
%End
};

View File

@ -0,0 +1,68 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsrenderedfeaturehandlerinterface.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsRenderedFeatureHandlerInterface
{
%Docstring
An interface for classes which provider custom handlers for features rendered
as part of a map render job.
QgsRenderedFeatureHandlerInterface objects are registered in the QgsMapSettings
objects used to construct map render jobs. During the rendering operation,
the handleRenderedFeature() method will be called once for every rendered feature,
allowing the handler to perform some custom task based on the provided information.
They can be used for custom tasks which operate on a set of rendered features,
such as creating spatial indexes of the location and rendered symbology bounding box
of all features rendered on a map.
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsrenderedfeaturehandlerinterface.h"
%End
public:
virtual ~QgsRenderedFeatureHandlerInterface();
struct RenderedFeatureContext
{
bool dummy;
};
virtual void handleRenderedFeature( const QgsFeature &feature, const QgsGeometry &renderedBounds, const QgsRenderedFeatureHandlerInterface::RenderedFeatureContext &context ) = 0;
%Docstring
Called whenever a ``feature`` is rendered during a map render job.
The ``renderedBounds`` argument specifies the (approximate) bounds of the rendered feature's
symbology. E.g. for point geometry features, this will be the bounding box of the marker symbol
used to symbolize the point. ``renderedBounds`` geometries are specified in painter units (not
map units).
.. warning::
This method may be called from many different threads (for multi-threaded map render operations),
and accordingly care must be taken to ensure that handleRenderedFeature() implementations are
appropriately thread safe.
The ``context`` argument is used to provide additional context relating to the rendering of a feature.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsrenderedfeaturehandlerinterface.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -112,6 +112,7 @@
%Include auto_generated/qgsreadwritelocker.sip
%Include auto_generated/qgsrenderchecker.sip
%Include auto_generated/qgsrendercontext.sip
%Include auto_generated/qgsrenderedfeaturehandlerinterface.sip
%Include auto_generated/qgsrulebasedlabeling.sip
%Include auto_generated/qgsruntimeprofiler.sip
%Include auto_generated/qgsscalecalculator.sip

View File

@ -994,6 +994,7 @@ SET(QGIS_CORE_HDRS
qgsreadwritelocker.h
qgsrenderchecker.h
qgsrendercontext.h
qgsrenderedfeaturehandlerinterface.h
qgsrulebasedlabeling.h
qgsruntimeprofiler.h
qgsscalecalculator.h

View File

@ -677,3 +677,13 @@ void QgsMapSettings::setLabelBoundaryGeometry( const QgsGeometry &boundary )
{
mLabelBoundaryGeometry = boundary;
}
void QgsMapSettings::addRenderedFeatureHandler( QgsRenderedFeatureHandlerInterface *handler )
{
mRenderedFeatureHandlers.append( handler );
}
QList<QgsRenderedFeatureHandlerInterface *> QgsMapSettings::renderedFeatureHandlers() const
{
return mRenderedFeatureHandlers;
}

View File

@ -38,6 +38,7 @@ class QPainter;
class QgsCoordinateTransform;
class QgsScaleCalculator;
class QgsMapRendererJob;
class QgsRenderedFeatureHandlerInterface;
/**
* \class QgsLabelBlockingRegion
@ -578,6 +579,24 @@ class CORE_EXPORT QgsMapSettings
*/
const QgsVectorSimplifyMethod &simplifyMethod() const { return mSimplifyMethod; }
/**
* Adds a rendered feature \a handler to use while rendering the map settings.
*
* Ownership of \a handler is NOT transferred, and it is the caller's responsibility to ensure
* that the handler exists for the lifetime of the map render job.
*
* \see renderedFeatureHandlers()
* \since QGIS 3.10
*/
void addRenderedFeatureHandler( QgsRenderedFeatureHandlerInterface *handler );
/**
* Returns the list of rendered feature handlers to use while rendering the map settings.
* \see addRenderedFeatureHandler()
* \since QGIS 3.10
*/
QList< QgsRenderedFeatureHandlerInterface * > renderedFeatureHandlers() const;
protected:
double mDpi;
@ -642,6 +661,7 @@ class CORE_EXPORT QgsMapSettings
private:
QList< QgsLabelBlockingRegion > mLabelBlockingRegions;
QList< QgsRenderedFeatureHandlerInterface * > mRenderedFeatureHandlers;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsMapSettings::Flags )

View File

@ -59,6 +59,8 @@ QgsRenderContext::QgsRenderContext( const QgsRenderContext &rh )
, mTransformContext( rh.mTransformContext )
, mPathResolver( rh.mPathResolver )
, mTextRenderFormat( rh.mTextRenderFormat )
, mRenderedFeatureHandlers( rh.mRenderedFeatureHandlers )
, mHasRenderedFeatureHandlers( rh.mHasRenderedFeatureHandlers )
#ifdef QGISDEBUG
, mHasTransformContext( rh.mHasTransformContext )
#endif
@ -88,6 +90,8 @@ QgsRenderContext &QgsRenderContext::operator=( const QgsRenderContext &rh )
mTransformContext = rh.mTransformContext;
mPathResolver = rh.mPathResolver;
mTextRenderFormat = rh.mTextRenderFormat;
mRenderedFeatureHandlers = rh.mRenderedFeatureHandlers;
mHasRenderedFeatureHandlers = rh.mHasRenderedFeatureHandlers;
#ifdef QGISDEBUG
mHasTransformContext = rh.mHasTransformContext;
#endif
@ -185,6 +189,8 @@ QgsRenderContext QgsRenderContext::fromMapSettings( const QgsMapSettings &mapSet
ctx.setPathResolver( mapSettings.pathResolver() );
ctx.setTextRenderFormat( mapSettings.textRenderFormat() );
ctx.setVectorSimplifyMethod( mapSettings.simplifyMethod() );
ctx.mRenderedFeatureHandlers = mapSettings.renderedFeatureHandlers();
ctx.mHasRenderedFeatureHandlers = !mapSettings.renderedFeatureHandlers().isEmpty();
//this flag is only for stopping during the current rendering progress,
//so must be false at every new render operation
ctx.setRenderingStopped( false );
@ -460,4 +466,9 @@ double QgsRenderContext::convertMetersToMapUnits( double meters ) const
return meters;
}
QList<QgsRenderedFeatureHandlerInterface *> QgsRenderContext::renderedFeatureHandlers() const
{
return mRenderedFeatureHandlers;
}

View File

@ -38,6 +38,7 @@ class QPainter;
class QgsAbstractGeometry;
class QgsLabelingEngine;
class QgsMapSettings;
class QgsRenderedFeatureHandlerInterface;
/**
@ -595,6 +596,20 @@ class CORE_EXPORT QgsRenderContext
mTextRenderFormat = format;
}
/**
* Returns the list of rendered feature handlers to use while rendering map layers.
* \see hasRenderedFeatureHandlers()
* \since QGIS 3.10
*/
QList<QgsRenderedFeatureHandlerInterface *> renderedFeatureHandlers() const;
/**
* Returns TRUE if the context has any rendered feature handlers.
* \see renderedFeatureHandlers()
* \since QGIS 3.10
*/
bool hasRenderedFeatureHandlers() const { return mHasRenderedFeatureHandlers; }
private:
Flags mFlags;
@ -653,6 +668,8 @@ class CORE_EXPORT QgsRenderContext
QgsPathResolver mPathResolver;
TextRenderFormat mTextRenderFormat = TextFormatAlwaysOutlines;
QList< QgsRenderedFeatureHandlerInterface * > mRenderedFeatureHandlers;
bool mHasRenderedFeatureHandlers = false;
#ifdef QGISDEBUG
bool mHasTransformContext = false;

View File

@ -0,0 +1,72 @@
/***************************************************************************
qgsrenderedfeaturehandlerinterface.h
--------------------------------------
Date : August 2019
Copyright : (C) 2019 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSRENDEREDFEATUREHANDLERINTERFACE_H
#define QGSRENDEREDFEATUREHANDLERINTERFACE_H
#include "qgis_core.h"
class QgsFeature;
class QgsGeometry;
/**
* \ingroup core
*
* An interface for classes which provider custom handlers for features rendered
* as part of a map render job.
*
* QgsRenderedFeatureHandlerInterface objects are registered in the QgsMapSettings
* objects used to construct map render jobs. During the rendering operation,
* the handleRenderedFeature() method will be called once for every rendered feature,
* allowing the handler to perform some custom task based on the provided information.
*
* They can be used for custom tasks which operate on a set of rendered features,
* such as creating spatial indexes of the location and rendered symbology bounding box
* of all features rendered on a map.
*
* \since QGIS 3.10
*/
class CORE_EXPORT QgsRenderedFeatureHandlerInterface
{
public:
virtual ~QgsRenderedFeatureHandlerInterface() = default;
struct CORE_EXPORT RenderedFeatureContext
{
///@cond PRIVATE
// required to allow compilation only until real members are present
bool dummy;
///@endcond
};
/**
* Called whenever a \a feature is rendered during a map render job.
*
* The \a renderedBounds argument specifies the (approximate) bounds of the rendered feature's
* symbology. E.g. for point geometry features, this will be the bounding box of the marker symbol
* used to symbolize the point. \a renderedBounds geometries are specified in painter units (not
* map units).
*
* \warning This method may be called from many different threads (for multi-threaded map render operations),
* and accordingly care must be taken to ensure that handleRenderedFeature() implementations are
* appropriately thread safe.
*
* The \a context argument is used to provide additional context relating to the rendering of a feature.
*/
virtual void handleRenderedFeature( const QgsFeature &feature, const QgsGeometry &renderedBounds, const QgsRenderedFeatureHandlerInterface::RenderedFeatureContext &context ) = 0;
};
#endif // QGSRENDEREDFEATUREHANDLERINTERFACE_H

View File

@ -29,6 +29,15 @@
#include "qgsvectorlayer.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsexpressioncontextutils.h"
#include "qgsrenderedfeaturehandlerinterface.h"
class TestHandler : public QgsRenderedFeatureHandlerInterface
{
public:
void handleRenderedFeature( const QgsFeature &, const QgsGeometry &, const QgsRenderedFeatureHandlerInterface::RenderedFeatureContext & ) override {}
};
class TestQgsMapSettings: public QObject
{
@ -50,6 +59,7 @@ class TestQgsMapSettings: public QObject
void testSetLayers();
void testLabelBoundary();
void testExpressionContext();
void testRenderedFeatureHandlers();
private:
QString toString( const QPolygonF &p, int decimalPlaces = 2 ) const;
@ -487,5 +497,21 @@ void TestQgsMapSettings::testExpressionContext()
QCOMPARE( r.toString(), QStringLiteral( "WGS84" ) );
}
void TestQgsMapSettings::testRenderedFeatureHandlers()
{
std::unique_ptr< TestHandler > testHandler = qgis::make_unique< TestHandler >();
std::unique_ptr< TestHandler > testHandler2 = qgis::make_unique< TestHandler >();
std::unique_ptr< QgsMapSettings> mapSettings = qgis::make_unique< QgsMapSettings >();
QVERIFY( mapSettings->renderedFeatureHandlers().isEmpty() );
mapSettings->addRenderedFeatureHandler( testHandler.get() );
mapSettings->addRenderedFeatureHandler( testHandler2.get() );
QCOMPARE( mapSettings->renderedFeatureHandlers(), QList< QgsRenderedFeatureHandlerInterface * >() << testHandler.get() << testHandler2.get() );
//ownership should NOT be transferred, i.e. it won't delete the registered handlers upon QgsMapSettings destruction
mapSettings.reset();
// should be no double-delete here
}
QGSTEST_MAIN( TestQgsMapSettings )
#include "testqgsmapsettings.moc"

View File

@ -21,7 +21,8 @@ from qgis.core import (QgsRenderContext,
QgsUnitTypes,
QgsProject,
QgsRectangle,
QgsVectorSimplifyMethod)
QgsVectorSimplifyMethod,
QgsRenderedFeatureHandlerInterface)
from qgis.PyQt.QtCore import QSize
from qgis.PyQt.QtGui import QPainter, QImage
from qgis.testing import start_app, unittest
@ -32,6 +33,12 @@ import math
start_app()
class TestFeatureHandler(QgsRenderedFeatureHandlerInterface):
def handleRenderedFeature(self, feature, geometry, context):
pass
class TestQgsRenderContext(unittest.TestCase):
def testGettersSetters(self):
@ -135,6 +142,29 @@ class TestQgsRenderContext(unittest.TestCase):
rc2 = QgsRenderContext(rc)
self.assertEqual(rc2.vectorSimplifyMethod().simplifyHints(), QgsVectorSimplifyMethod.GeometrySimplification)
def testRenderedFeatureHandlers(self):
rc = QgsRenderContext()
self.assertFalse(rc.renderedFeatureHandlers())
self.assertFalse(rc.hasRenderedFeatureHandlers())
ms = QgsMapSettings()
rc = QgsRenderContext.fromMapSettings(ms)
self.assertFalse(rc.renderedFeatureHandlers())
self.assertFalse(rc.hasRenderedFeatureHandlers())
handler = TestFeatureHandler()
handler2 = TestFeatureHandler()
ms.addRenderedFeatureHandler(handler)
ms.addRenderedFeatureHandler(handler2)
rc = QgsRenderContext.fromMapSettings(ms)
self.assertEqual(rc.renderedFeatureHandlers(), [handler, handler2])
self.assertTrue(rc.hasRenderedFeatureHandlers())
rc2 = QgsRenderContext(rc)
self.assertEqual(rc2.renderedFeatureHandlers(), [handler, handler2])
self.assertTrue(rc2.hasRenderedFeatureHandlers())
def testRenderMetersInMapUnits(self):
crs_wsg84 = QgsCoordinateReferenceSystem.fromOgcWmsCrs('EPSG:4326')