mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-06 00:07:29 -04:00
[api] Add framework for collecting rendered item details during map
renderer operations This follow a similar pattern as how labeling results could be collected after a map render job, but generalises the API so that it can be used for storing details of rendered items of any type. It's currently used for storing details of rendered annotation items, so that map tools can retrieve details of annotation items visible in the canvas in an optimised way.
This commit is contained in:
parent
b09994abcc
commit
660433d9a9
@ -0,0 +1,57 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/annotations/qgsrenderedannotationitemdetails.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsRenderedAnnotationItemDetails : QgsRenderedItemDetails
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
Contains information about a rendered annotation item.
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsrenderedannotationitemdetails.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsRenderedAnnotationItemDetails( const QString &layerId, const QString &itemId );
|
||||
%Docstring
|
||||
Constructor for QgsRenderedAnnotationItemDetails.
|
||||
%End
|
||||
|
||||
SIP_PYOBJECT __repr__();
|
||||
%MethodCode
|
||||
QString str = QStringLiteral( "<QgsRenderedAnnotationItemDetails: %1 - %2>" ).arg( sipCpp->layerId(), sipCpp->itemId() );
|
||||
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
|
||||
%End
|
||||
|
||||
virtual QgsRenderedAnnotationItemDetails *clone() const /Factory/;
|
||||
|
||||
|
||||
QString layerId() const;
|
||||
%Docstring
|
||||
Returns the layer ID of the associated map layer.
|
||||
%End
|
||||
|
||||
QString itemId() const;
|
||||
%Docstring
|
||||
Returns the item ID of the associated annotation item.
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/annotations/qgsrenderedannotationitemdetails.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
@ -49,6 +49,8 @@ The following subclasses are available:
|
||||
|
||||
QgsMapRendererJob( const QgsMapSettings &settings );
|
||||
|
||||
~QgsMapRendererJob();
|
||||
|
||||
void start();
|
||||
%Docstring
|
||||
Start the rendering job and immediately return.
|
||||
@ -95,6 +97,15 @@ Gets pointer to internal labeling engine (in order to get access to the results)
|
||||
This should not be used if cached labeling was redrawn - see :py:func:`~QgsMapRendererJob.usedCachedLabels`.
|
||||
|
||||
.. seealso:: :py:func:`usedCachedLabels`
|
||||
%End
|
||||
|
||||
QgsRenderedItemResults *takeRenderedItemResults() /Transfer/;
|
||||
%Docstring
|
||||
Takes the rendered item results from the map render job and returns them.
|
||||
|
||||
Ownership is transferred to the caller.
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
void setFeatureFilterProvider( const QgsFeatureFilterProvider *f );
|
||||
@ -194,6 +205,7 @@ emitted when asynchronous rendering is finished (or canceled).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,53 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/maprenderer/qgsrendereditemresults.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsRenderedItemResults
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
Stores collated details of rendered items during a map rendering operation.
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsrendereditemresults.h"
|
||||
%End
|
||||
public:
|
||||
QgsRenderedItemResults();
|
||||
~QgsRenderedItemResults();
|
||||
|
||||
|
||||
QList< QgsRenderedItemDetails * > renderedItems() const;
|
||||
%Docstring
|
||||
Returns a list of all rendered items.
|
||||
%End
|
||||
|
||||
QList<const QgsRenderedAnnotationItemDetails *> renderedAnnotationItemsInBounds( const QgsRectangle &bounds ) const;
|
||||
%Docstring
|
||||
Returns a list with details of the rendered annotation items within the specified ``bounds``.
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
|
||||
private:
|
||||
QgsRenderedItemResults( const QgsRenderedItemResults & );
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/maprenderer/qgsrendereditemresults.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
@ -110,11 +110,35 @@ least partially) some data
|
||||
%End
|
||||
|
||||
|
||||
QList< QgsRenderedItemDetails * > takeRenderedItemDetails() /TransferBack/;
|
||||
%Docstring
|
||||
Takes the list of rendered item details from the renderer.
|
||||
|
||||
Ownership of items is transferred to the caller.
|
||||
|
||||
.. seealso:: :py:func:`appendRenderedItemDetails`
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
|
||||
|
||||
void appendRenderedItemDetails( QgsRenderedItemDetails *details /Transfer/ );
|
||||
%Docstring
|
||||
Appends the ``details`` of a rendered item to the renderer.
|
||||
|
||||
Rendered item details can be retrieved by calling :py:func:`~QgsMapLayerRenderer.takeRenderedItemDetails`.
|
||||
|
||||
Ownership of ``details`` is transferred to the renderer.
|
||||
|
||||
.. seealso:: :py:func:`takeRenderedItemDetails`
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
|
61
python/core/auto_generated/qgsrendereditemdetails.sip.in
Normal file
61
python/core/auto_generated/qgsrendereditemdetails.sip.in
Normal file
@ -0,0 +1,61 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/qgsrendereditemdetails.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsRenderedItemDetails
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
Base class for detailed information about a rendered item.
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsrendereditemdetails.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
%ConvertToSubClassCode
|
||||
if ( dynamic_cast<QgsRenderedAnnotationItemDetails *>( sipCpp ) )
|
||||
sipType = sipType_QgsRenderedAnnotationItemDetails;
|
||||
else
|
||||
sipType = 0;
|
||||
%End
|
||||
|
||||
virtual ~QgsRenderedItemDetails();
|
||||
|
||||
virtual QgsRenderedItemDetails *clone() const = 0 /Factory/;
|
||||
%Docstring
|
||||
Clones the details.
|
||||
%End
|
||||
|
||||
QgsRectangle boundingBox() const;
|
||||
%Docstring
|
||||
Returns the bounding box of the item (in map units).
|
||||
|
||||
.. seealso:: :py:func:`setBoundingBox`
|
||||
%End
|
||||
|
||||
void setBoundingBox( const QgsRectangle &bounds );
|
||||
%Docstring
|
||||
Sets the bounding box of the item (in map units).
|
||||
|
||||
.. seealso:: :py:func:`boundingBox`
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/qgsrendereditemdetails.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
@ -152,6 +152,7 @@
|
||||
%Include auto_generated/qgsrenderchecker.sip
|
||||
%Include auto_generated/qgsrendercontext.sip
|
||||
%Include auto_generated/qgsrenderedfeaturehandlerinterface.sip
|
||||
%Include auto_generated/qgsrendereditemdetails.sip
|
||||
%Include auto_generated/qgsrunprocess.sip
|
||||
%Include auto_generated/qgsruntimeprofiler.sip
|
||||
%Include auto_generated/qgsscalecalculator.sip
|
||||
@ -212,6 +213,7 @@
|
||||
%Include auto_generated/annotations/qgsannotationpointtextitem.sip
|
||||
%Include auto_generated/annotations/qgsannotationpolygonitem.sip
|
||||
%Include auto_generated/annotations/qgshtmlannotation.sip
|
||||
%Include auto_generated/annotations/qgsrenderedannotationitemdetails.sip
|
||||
%Include auto_generated/annotations/qgssvgannotation.sip
|
||||
%Include auto_generated/annotations/qgstextannotation.sip
|
||||
%Include auto_generated/auth/qgsauthcertutils.sip
|
||||
@ -419,6 +421,7 @@
|
||||
%Include auto_generated/maprenderer/qgsmaprendererparalleljob.sip
|
||||
%Include auto_generated/maprenderer/qgsmaprenderersequentialjob.sip
|
||||
%Include auto_generated/maprenderer/qgsmaprenderertask.sip
|
||||
%Include auto_generated/maprenderer/qgsrendereditemresults.sip
|
||||
%Include auto_generated/mesh/qgsmesh3daveraging.sip
|
||||
%Include auto_generated/mesh/qgsmesheditor.sip
|
||||
%Include auto_generated/mesh/qgsmeshdataprovider.sip
|
||||
|
@ -115,6 +115,17 @@ Since QGIS 3.20, if the ``allowOutdatedResults`` flag is ``False`` then outdated
|
||||
as a result of an ongoing canvas render) will not be returned, and instead ``None`` will be returned.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
%End
|
||||
|
||||
const QgsRenderedItemResults *renderedItemResults( bool allowOutdatedResults = true ) const;
|
||||
%Docstring
|
||||
Gets access to the rendered item results (may be ``None``), which includes the results of rendering
|
||||
annotation items in the canvas map.
|
||||
|
||||
If the ``allowOutdatedResults`` flag is ``False`` then outdated rendered item results (e.g.
|
||||
as a result of an ongoing canvas render) will not be returned, and instead ``None`` will be returned.
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
void setCachingEnabled( bool enabled );
|
||||
|
@ -184,6 +184,7 @@ set(QGIS_CORE_SRCS
|
||||
annotations/qgsannotationpointtextitem.cpp
|
||||
annotations/qgsannotationpolygonitem.cpp
|
||||
annotations/qgshtmlannotation.cpp
|
||||
annotations/qgsrenderedannotationitemdetails.cpp
|
||||
annotations/qgssvgannotation.cpp
|
||||
annotations/qgstextannotation.cpp
|
||||
|
||||
@ -397,6 +398,7 @@ set(QGIS_CORE_SRCS
|
||||
qgsmaplayerlegend.cpp
|
||||
qgsmaplayermodel.cpp
|
||||
qgsmaplayerproxymodel.cpp
|
||||
qgsmaplayerrenderer.cpp
|
||||
qgsmaplayerstore.cpp
|
||||
qgsmaplayerstyle.cpp
|
||||
qgsmaplayerstylemanager.cpp
|
||||
@ -448,6 +450,7 @@ set(QGIS_CORE_SRCS
|
||||
qgsremappingproxyfeaturesink.cpp
|
||||
qgsrenderchecker.cpp
|
||||
qgsrendercontext.cpp
|
||||
qgsrendereditemdetails.cpp
|
||||
qgsrunprocess.cpp
|
||||
qgsruntimeprofiler.cpp
|
||||
qgsscalecalculator.cpp
|
||||
@ -588,6 +591,7 @@ set(QGIS_CORE_SRCS
|
||||
maprenderer/qgsmaprenderersequentialjob.cpp
|
||||
maprenderer/qgsmaprendererstagedrenderjob.cpp
|
||||
maprenderer/qgsmaprenderertask.cpp
|
||||
maprenderer/qgsrendereditemresults.cpp
|
||||
|
||||
pal/costcalculator.cpp
|
||||
pal/feature.cpp
|
||||
@ -1084,6 +1088,7 @@ set(QGIS_CORE_HDRS
|
||||
qgsrenderchecker.h
|
||||
qgsrendercontext.h
|
||||
qgsrenderedfeaturehandlerinterface.h
|
||||
qgsrendereditemdetails.h
|
||||
qgsrunprocess.h
|
||||
qgsruntimeprofiler.h
|
||||
qgsscalecalculator.h
|
||||
@ -1158,6 +1163,7 @@ set(QGIS_CORE_HDRS
|
||||
annotations/qgsannotationpolygonitem.h
|
||||
annotations/qgsannotationregistry.h
|
||||
annotations/qgshtmlannotation.h
|
||||
annotations/qgsrenderedannotationitemdetails.h
|
||||
annotations/qgssvgannotation.h
|
||||
annotations/qgstextannotation.h
|
||||
|
||||
@ -1404,6 +1410,7 @@ set(QGIS_CORE_HDRS
|
||||
maprenderer/qgsmaprenderersequentialjob.h
|
||||
maprenderer/qgsmaprendererstagedrenderjob.h
|
||||
maprenderer/qgsmaprenderertask.h
|
||||
maprenderer/qgsrendereditemresults.h
|
||||
|
||||
mesh/qgsmesh3daveraging.h
|
||||
mesh/qgsmesheditor.h
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "qgsannotationlayerrenderer.h"
|
||||
#include "qgsannotationlayer.h"
|
||||
#include "qgsfeedback.h"
|
||||
#include "qgsrenderedannotationitemdetails.h"
|
||||
|
||||
QgsAnnotationLayerRenderer::QgsAnnotationLayerRenderer( QgsAnnotationLayer *layer, QgsRenderContext &context )
|
||||
: QgsMapLayerRenderer( layer->id(), &context )
|
||||
@ -39,15 +40,18 @@ QgsAnnotationLayerRenderer::QgsAnnotationLayerRenderer( QgsAnnotationLayer *laye
|
||||
|
||||
mItems.reserve( items.size() );
|
||||
std::transform( items.begin(), items.end(), std::back_inserter( mItems ),
|
||||
[layer]( const QString & id ) -> QgsAnnotationItem* { return layer->item( id )->clone(); } );
|
||||
[layer]( const QString & id ) ->std::pair< QString, std::unique_ptr< QgsAnnotationItem > >
|
||||
{
|
||||
return std::make_pair( id, std::unique_ptr< QgsAnnotationItem >( layer->item( id )->clone() ) );
|
||||
} );
|
||||
|
||||
std::sort( mItems.begin(), mItems.end(), []( QgsAnnotationItem * a, QgsAnnotationItem * b ) { return a->zIndex() < b->zIndex(); } ); //clazy:exclude=detaching-member
|
||||
std::sort( mItems.begin(), mItems.end(), [](
|
||||
const std::pair< QString, std::unique_ptr< QgsAnnotationItem > > &a,
|
||||
const std::pair< QString, std::unique_ptr< QgsAnnotationItem > > &b )
|
||||
{ return a.second->zIndex() < b.second->zIndex(); } );
|
||||
}
|
||||
|
||||
QgsAnnotationLayerRenderer::~QgsAnnotationLayerRenderer()
|
||||
{
|
||||
qDeleteAll( mItems );
|
||||
}
|
||||
QgsAnnotationLayerRenderer::~QgsAnnotationLayerRenderer() = default;
|
||||
|
||||
QgsFeedback *QgsAnnotationLayerRenderer::feedback() const
|
||||
{
|
||||
@ -59,7 +63,7 @@ bool QgsAnnotationLayerRenderer::render()
|
||||
QgsRenderContext &context = *renderContext();
|
||||
|
||||
bool canceled = false;
|
||||
for ( QgsAnnotationItem *item : std::as_const( mItems ) )
|
||||
for ( const std::pair< QString, std::unique_ptr< QgsAnnotationItem > > &item : std::as_const( mItems ) )
|
||||
{
|
||||
if ( mFeedback->isCanceled() )
|
||||
{
|
||||
@ -67,7 +71,14 @@ bool QgsAnnotationLayerRenderer::render()
|
||||
break;
|
||||
}
|
||||
|
||||
item->render( context, mFeedback.get() );
|
||||
const QgsRectangle bounds = item.second->boundingBox( context );
|
||||
if ( bounds.intersects( context.extent() ) )
|
||||
{
|
||||
item.second->render( context, mFeedback.get() );
|
||||
std::unique_ptr< QgsRenderedAnnotationItemDetails > details = std::make_unique< QgsRenderedAnnotationItemDetails >( mLayerID, item.first );
|
||||
details->setBoundingBox( bounds );
|
||||
appendRenderedItemDetails( details.release() );
|
||||
}
|
||||
}
|
||||
return !canceled;
|
||||
}
|
||||
|
@ -23,6 +23,9 @@
|
||||
#include "qgis_sip.h"
|
||||
#include "qgsmaplayerrenderer.h"
|
||||
#include "qgsannotationitem.h"
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
class QgsAnnotationLayer;
|
||||
|
||||
@ -47,7 +50,7 @@ class CORE_EXPORT QgsAnnotationLayerRenderer : public QgsMapLayerRenderer
|
||||
bool forceRasterRender() const override;
|
||||
|
||||
private:
|
||||
QVector< QgsAnnotationItem *> mItems;
|
||||
std::vector < std::pair< QString, std::unique_ptr< QgsAnnotationItem > > > mItems;
|
||||
std::unique_ptr< QgsFeedback > mFeedback;
|
||||
double mLayerOpacity = 1.0;
|
||||
|
||||
|
29
src/core/annotations/qgsrenderedannotationitemdetails.cpp
Normal file
29
src/core/annotations/qgsrenderedannotationitemdetails.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
/***************************************************************************
|
||||
qgsrenderedannotationitemdetails.cpp
|
||||
----------------
|
||||
copyright : (C) 2021 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgsrenderedannotationitemdetails.h"
|
||||
|
||||
QgsRenderedAnnotationItemDetails::QgsRenderedAnnotationItemDetails( const QString &layerId, const QString &itemId )
|
||||
: mLayerId( layerId )
|
||||
, mItemId( itemId )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QgsRenderedAnnotationItemDetails *QgsRenderedAnnotationItemDetails::clone() const
|
||||
{
|
||||
return new QgsRenderedAnnotationItemDetails( *this );
|
||||
}
|
65
src/core/annotations/qgsrenderedannotationitemdetails.h
Normal file
65
src/core/annotations/qgsrenderedannotationitemdetails.h
Normal file
@ -0,0 +1,65 @@
|
||||
/***************************************************************************
|
||||
qgsrenderedannotationitemdetails.h
|
||||
----------------
|
||||
copyright : (C) 2021 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 QGSRENDEREDANNOTATIONITEMDETAILS_H
|
||||
#define QGSRENDEREDANNOTATIONITEMDETAILS_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgis_sip.h"
|
||||
#include "qgsrendereditemdetails.h"
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \brief Contains information about a rendered annotation item.
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
class CORE_EXPORT QgsRenderedAnnotationItemDetails : public QgsRenderedItemDetails
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsRenderedAnnotationItemDetails.
|
||||
*/
|
||||
QgsRenderedAnnotationItemDetails( const QString &layerId, const QString &itemId );
|
||||
|
||||
#ifdef SIP_RUN
|
||||
SIP_PYOBJECT __repr__();
|
||||
% MethodCode
|
||||
QString str = QStringLiteral( "<QgsRenderedAnnotationItemDetails: %1 - %2>" ).arg( sipCpp->layerId(), sipCpp->itemId() );
|
||||
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
|
||||
% End
|
||||
#endif
|
||||
|
||||
QgsRenderedAnnotationItemDetails *clone() const override SIP_FACTORY;
|
||||
|
||||
/**
|
||||
* Returns the layer ID of the associated map layer.
|
||||
*/
|
||||
QString layerId() const { return mLayerId; }
|
||||
|
||||
/**
|
||||
* Returns the item ID of the associated annotation item.
|
||||
*/
|
||||
QString itemId() const { return mItemId; }
|
||||
|
||||
private:
|
||||
|
||||
QString mLayerId;
|
||||
QString mItemId;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSRENDEREDANNOTATIONITEMDETAILS_H
|
@ -43,6 +43,7 @@
|
||||
#include "qgsmaplayertemporalproperties.h"
|
||||
#include "qgsmaplayerelevationproperties.h"
|
||||
#include "qgsvectorlayerrenderer.h"
|
||||
#include "qgsrendereditemresults.h"
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
@ -130,8 +131,11 @@ bool LayerRenderJob::imageCanBeComposed() const
|
||||
|
||||
QgsMapRendererJob::QgsMapRendererJob( const QgsMapSettings &settings )
|
||||
: mSettings( settings )
|
||||
, mRenderedItemResults( std::make_unique< QgsRenderedItemResults >() )
|
||||
{}
|
||||
|
||||
QgsMapRendererJob::~QgsMapRendererJob() = default;
|
||||
|
||||
void QgsMapRendererJob::start()
|
||||
{
|
||||
if ( mSettings.hasValidSettings() )
|
||||
@ -143,6 +147,11 @@ void QgsMapRendererJob::start()
|
||||
}
|
||||
}
|
||||
|
||||
QgsRenderedItemResults *QgsMapRendererJob::takeRenderedItemResults()
|
||||
{
|
||||
return mRenderedItemResults.release();
|
||||
}
|
||||
|
||||
QgsMapRendererQImageJob::QgsMapRendererQImageJob( const QgsMapSettings &settings )
|
||||
: QgsMapRendererJob( settings )
|
||||
{
|
||||
@ -810,6 +819,8 @@ void QgsMapRendererJob::cleanupJobs( std::vector<LayerRenderJob> &jobs )
|
||||
for ( const QString &message : errors )
|
||||
mErrors.append( Error( job.renderer->layerId(), message ) );
|
||||
|
||||
mRenderedItemResults->appendResults( job.renderer->takeRenderedItemDetails(), *job.context() );
|
||||
|
||||
delete job.renderer;
|
||||
job.renderer = nullptr;
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ class QgsLabelingResults;
|
||||
class QgsMapLayerRenderer;
|
||||
class QgsMapRendererCache;
|
||||
class QgsFeatureFilterProvider;
|
||||
class QgsRenderedItemResults;
|
||||
|
||||
#ifndef SIP_RUN
|
||||
/// @cond PRIVATE
|
||||
@ -250,6 +251,8 @@ class CORE_EXPORT QgsMapRendererJob : public QObject SIP_ABSTRACT
|
||||
|
||||
QgsMapRendererJob( const QgsMapSettings &settings );
|
||||
|
||||
~QgsMapRendererJob() override;
|
||||
|
||||
/**
|
||||
* Start the rendering job and immediately return.
|
||||
* Does nothing if the rendering is already in progress.
|
||||
@ -291,6 +294,15 @@ class CORE_EXPORT QgsMapRendererJob : public QObject SIP_ABSTRACT
|
||||
*/
|
||||
virtual QgsLabelingResults *takeLabelingResults() = 0 SIP_TRANSFER;
|
||||
|
||||
/**
|
||||
* Takes the rendered item results from the map render job and returns them.
|
||||
*
|
||||
* Ownership is transferred to the caller.
|
||||
*
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
QgsRenderedItemResults *takeRenderedItemResults() SIP_TRANSFER;
|
||||
|
||||
/**
|
||||
* Set the feature filter provider used by the QgsRenderContext of
|
||||
* each LayerRenderJob.
|
||||
@ -421,6 +433,10 @@ class CORE_EXPORT QgsMapRendererJob : public QObject SIP_ABSTRACT
|
||||
*/
|
||||
bool mRecordRenderingTime = true;
|
||||
|
||||
#ifndef SIP_RUN
|
||||
std::unique_ptr< QgsRenderedItemResults > mRenderedItemResults;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Prepares the cache for storing the result of labeling. Returns FALSE if
|
||||
* the render cannot use cached labels and should not cache the result.
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "qgsmaprenderercustompainterjob.h"
|
||||
#include "qgspallabeling.h"
|
||||
#include "qgslabelingresults.h"
|
||||
#include "qgsrendereditemresults.h"
|
||||
|
||||
QgsMapRendererSequentialJob::QgsMapRendererSequentialJob( const QgsMapSettings &settings )
|
||||
: QgsMapRendererQImageJob( settings )
|
||||
@ -138,6 +139,8 @@ void QgsMapRendererSequentialJob::internalFinished()
|
||||
mLabelingResults.reset( mInternalJob->takeLabelingResults() );
|
||||
mUsedCachedLabels = mInternalJob->usedCachedLabels();
|
||||
|
||||
mRenderedItemResults.reset( mInternalJob->takeRenderedItemResults() );
|
||||
|
||||
mErrors = mInternalJob->errors();
|
||||
|
||||
// now we are in a slot called from mInternalJob - do not delete it immediately
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "qgsproject.h"
|
||||
#include "qgsmaplayerrenderer.h"
|
||||
#include "qgsmaplayerlistutils.h"
|
||||
#include "qgsrendereditemresults.h"
|
||||
|
||||
QgsMapRendererStagedRenderJob::QgsMapRendererStagedRenderJob( const QgsMapSettings &settings, Flags flags )
|
||||
: QgsMapRendererAbstractCustomPainterJob( settings )
|
||||
|
129
src/core/maprenderer/qgsrendereditemresults.cpp
Normal file
129
src/core/maprenderer/qgsrendereditemresults.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
/***************************************************************************
|
||||
qgsrendereditemresults.cpp
|
||||
-------------------
|
||||
begin : August 2021
|
||||
copyright : (C) 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgsrendereditemresults.h"
|
||||
#include "qgsrendereditemdetails.h"
|
||||
#include "qgsrendercontext.h"
|
||||
#include "qgslogger.h"
|
||||
#include "qgsrenderedannotationitemdetails.h"
|
||||
#include "RTree.h"
|
||||
|
||||
|
||||
///@cond PRIVATE
|
||||
class QgsRenderedItemResultsSpatialIndex : public RTree<const QgsRenderedItemDetails *, float, 2, float>
|
||||
{
|
||||
public:
|
||||
|
||||
void insert( const QgsRenderedItemDetails *details, const QgsRectangle &bounds )
|
||||
{
|
||||
std::array< float, 4 > scaledBounds = scaleBounds( bounds );
|
||||
this->Insert(
|
||||
{
|
||||
scaledBounds[0], scaledBounds[ 1]
|
||||
},
|
||||
{
|
||||
scaledBounds[2], scaledBounds[3]
|
||||
},
|
||||
details );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an intersection check against the index, for data intersecting the specified \a bounds.
|
||||
*
|
||||
* The \a callback function will be called once for each matching data object encountered.
|
||||
*/
|
||||
bool intersects( const QgsRectangle &bounds, const std::function< bool( const QgsRenderedItemDetails *details )> &callback ) const
|
||||
{
|
||||
std::array< float, 4 > scaledBounds = scaleBounds( bounds );
|
||||
this->Search(
|
||||
{
|
||||
scaledBounds[0], scaledBounds[ 1]
|
||||
},
|
||||
{
|
||||
scaledBounds[2], scaledBounds[3]
|
||||
},
|
||||
callback );
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<float, 4> scaleBounds( const QgsRectangle &bounds ) const
|
||||
{
|
||||
return
|
||||
{
|
||||
static_cast< float >( bounds.xMinimum() ),
|
||||
static_cast< float >( bounds.yMinimum() ),
|
||||
static_cast< float >( bounds.xMaximum() ),
|
||||
static_cast< float >( bounds.yMaximum() )
|
||||
};
|
||||
}
|
||||
};
|
||||
///@endcond
|
||||
|
||||
QgsRenderedItemResults::QgsRenderedItemResults()
|
||||
: mAnnotationItemsIndex( std::make_unique< QgsRenderedItemResultsSpatialIndex >() )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QgsRenderedItemResults::~QgsRenderedItemResults() = default;
|
||||
|
||||
QList<QgsRenderedItemDetails *> QgsRenderedItemResults::renderedItems() const
|
||||
{
|
||||
QList< QgsRenderedItemDetails * > res;
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
res.reserve( static_cast< int >( mDetails.size() ) );
|
||||
#else
|
||||
res.reserve( mDetails.size() );
|
||||
#endif
|
||||
std::transform( mDetails.begin(), mDetails.end(), std::back_inserter( res ), []( const auto & detail ) {return detail.get();} );
|
||||
return res;
|
||||
}
|
||||
|
||||
QList<const QgsRenderedAnnotationItemDetails *> QgsRenderedItemResults::renderedAnnotationItemsInBounds( const QgsRectangle &bounds ) const
|
||||
{
|
||||
QList<const QgsRenderedAnnotationItemDetails *> res;
|
||||
|
||||
mAnnotationItemsIndex->intersects( bounds, [&res]( const QgsRenderedItemDetails * details )->bool
|
||||
{
|
||||
res << qgis::down_cast< const QgsRenderedAnnotationItemDetails * >( details );
|
||||
return true;
|
||||
} );
|
||||
return res;
|
||||
}
|
||||
|
||||
void QgsRenderedItemResults::appendResults( const QList<QgsRenderedItemDetails *> &results, const QgsRenderContext &context )
|
||||
{
|
||||
const QgsCoordinateTransform layerToMapTransform = context.coordinateTransform();
|
||||
for ( QgsRenderedItemDetails *details : results )
|
||||
{
|
||||
try
|
||||
{
|
||||
const QgsRectangle transformedBounds = layerToMapTransform.transformBoundingBox( details->boundingBox() );
|
||||
details->setBoundingBox( transformedBounds );
|
||||
}
|
||||
catch ( QgsCsException & )
|
||||
{
|
||||
QgsDebugMsg( QStringLiteral( "Could not transform rendered item's bounds to map CRS" ) );
|
||||
}
|
||||
|
||||
if ( QgsRenderedAnnotationItemDetails *annotationDetails = dynamic_cast< QgsRenderedAnnotationItemDetails * >( details ) )
|
||||
mAnnotationItemsIndex->insert( annotationDetails, annotationDetails->boundingBox() );
|
||||
|
||||
mDetails.emplace_back( std::unique_ptr< QgsRenderedItemDetails >( details ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
89
src/core/maprenderer/qgsrendereditemresults.h
Normal file
89
src/core/maprenderer/qgsrendereditemresults.h
Normal file
@ -0,0 +1,89 @@
|
||||
/***************************************************************************
|
||||
qgsrendereditemresults.h
|
||||
-------------------
|
||||
begin : August 2021
|
||||
copyright : (C) 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 QGSRENDEREDITEMRESULTS_H
|
||||
#define QGSRENDEREDITEMRESULTS_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgis_sip.h"
|
||||
#include <memory>
|
||||
#include <QList>
|
||||
#include <vector>
|
||||
|
||||
class QgsRenderedItemDetails;
|
||||
class QgsRenderContext;
|
||||
class QgsRenderedAnnotationItemDetails;
|
||||
class QgsRectangle;
|
||||
|
||||
///@cond PRIVATE
|
||||
class QgsRenderedItemResultsSpatialIndex;
|
||||
///@endcond
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \brief Stores collated details of rendered items during a map rendering operation.
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
class CORE_EXPORT QgsRenderedItemResults
|
||||
{
|
||||
public:
|
||||
QgsRenderedItemResults();
|
||||
~QgsRenderedItemResults();
|
||||
|
||||
//! QgsRenderedItemResults cannot be copied.
|
||||
QgsRenderedItemResults( const QgsRenderedItemResults & ) = delete;
|
||||
//! QgsRenderedItemResults cannot be copied.
|
||||
QgsRenderedItemResults &operator=( const QgsRenderedItemResults &rh ) = delete;
|
||||
|
||||
/**
|
||||
* Returns a list of all rendered items.
|
||||
*/
|
||||
QList< QgsRenderedItemDetails * > renderedItems() const;
|
||||
|
||||
/**
|
||||
* Returns a list with details of the rendered annotation items within the specified \a bounds.
|
||||
*
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
QList<const QgsRenderedAnnotationItemDetails *> renderedAnnotationItemsInBounds( const QgsRectangle &bounds ) const;
|
||||
|
||||
#ifndef SIP_RUN
|
||||
|
||||
/**
|
||||
* Appends rendered item details to the results object.
|
||||
*
|
||||
* Ownership of \a results is transferred to the this object.
|
||||
*
|
||||
* The render \a context argument is used to specify the render context used to render the items. It will be used
|
||||
* to transform the details to the destination map CRS.
|
||||
*
|
||||
* \note Not available in Python bindings.
|
||||
*/
|
||||
void appendResults( const QList< QgsRenderedItemDetails * > &results, const QgsRenderContext &context );
|
||||
#endif
|
||||
|
||||
private:
|
||||
#ifdef SIP_RUN
|
||||
QgsRenderedItemResults( const QgsRenderedItemResults & );
|
||||
#endif
|
||||
|
||||
std::vector< std::unique_ptr< QgsRenderedItemDetails > > mDetails;
|
||||
std::unique_ptr< QgsRenderedItemResultsSpatialIndex > mAnnotationItemsIndex;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSRENDEREDITEMRESULTS_H
|
30
src/core/qgsmaplayerrenderer.cpp
Normal file
30
src/core/qgsmaplayerrenderer.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
/***************************************************************************
|
||||
qgsmaplayerrenderer.cpp
|
||||
--------------------------------------
|
||||
Date : August 2021
|
||||
Copyright : (C) 2021 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgsmaplayerrenderer.h"
|
||||
#include "qgsrendereditemdetails.h"
|
||||
|
||||
QgsMapLayerRenderer::~QgsMapLayerRenderer() = default;
|
||||
|
||||
|
||||
QList<QgsRenderedItemDetails *> QgsMapLayerRenderer::takeRenderedItemDetails()
|
||||
{
|
||||
return std::move( mRenderedItemDetails );
|
||||
}
|
||||
|
||||
void QgsMapLayerRenderer::appendRenderedItemDetails( QgsRenderedItemDetails *details )
|
||||
{
|
||||
mRenderedItemDetails.append( details );
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
|
||||
class QgsFeedback;
|
||||
class QgsRenderContext;
|
||||
class QgsRenderedItemDetails;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -62,7 +63,7 @@ class CORE_EXPORT QgsMapLayerRenderer
|
||||
, mContext( context )
|
||||
{}
|
||||
|
||||
virtual ~QgsMapLayerRenderer() = default;
|
||||
virtual ~QgsMapLayerRenderer();
|
||||
|
||||
/**
|
||||
* Do the rendering (based on data stored in the class).
|
||||
@ -137,6 +138,16 @@ class CORE_EXPORT QgsMapLayerRenderer
|
||||
*/
|
||||
virtual void setLayerRenderingTimeHint( int time ) SIP_SKIP { Q_UNUSED( time ) }
|
||||
|
||||
/**
|
||||
* Takes the list of rendered item details from the renderer.
|
||||
*
|
||||
* Ownership of items is transferred to the caller.
|
||||
*
|
||||
* \see appendRenderedItemDetails()
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
QList< QgsRenderedItemDetails * > takeRenderedItemDetails() SIP_TRANSFERBACK;
|
||||
|
||||
protected:
|
||||
QStringList mErrors;
|
||||
QString mLayerID;
|
||||
@ -169,6 +180,18 @@ class CORE_EXPORT QgsMapLayerRenderer
|
||||
*/
|
||||
static constexpr int MAX_TIME_TO_USE_CACHED_PREVIEW_IMAGE = 3000 SIP_SKIP;
|
||||
|
||||
/**
|
||||
* Appends the \a details of a rendered item to the renderer.
|
||||
*
|
||||
* Rendered item details can be retrieved by calling takeRenderedItemDetails().
|
||||
*
|
||||
* Ownership of \a details is transferred to the renderer.
|
||||
*
|
||||
* \see takeRenderedItemDetails()
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
void appendRenderedItemDetails( QgsRenderedItemDetails *details SIP_TRANSFER );
|
||||
|
||||
private:
|
||||
|
||||
// TODO QGIS 4.0 - make reference instead of pointer!
|
||||
@ -179,6 +202,8 @@ class CORE_EXPORT QgsMapLayerRenderer
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QgsRenderContext *mContext = nullptr;
|
||||
|
||||
QList<QgsRenderedItemDetails *> mRenderedItemDetails;
|
||||
};
|
||||
|
||||
#endif // QGSMAPLAYERRENDERER_H
|
||||
|
19
src/core/qgsrendereditemdetails.cpp
Normal file
19
src/core/qgsrendereditemdetails.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
/***************************************************************************
|
||||
qgsrendereditemdetails.cpp
|
||||
----------------
|
||||
copyright : (C) 2021 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgsrendereditemdetails.h"
|
||||
|
||||
QgsRenderedItemDetails::~QgsRenderedItemDetails() = default;
|
68
src/core/qgsrendereditemdetails.h
Normal file
68
src/core/qgsrendereditemdetails.h
Normal file
@ -0,0 +1,68 @@
|
||||
/***************************************************************************
|
||||
qgsrendereditemdetails.h
|
||||
----------------
|
||||
copyright : (C) 2021 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 QGSRENDEREDITEMDETAILS_H
|
||||
#define QGSRENDEREDITEMDETAILS_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgis_sip.h"
|
||||
#include "qgsrectangle.h"
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \brief Base class for detailed information about a rendered item.
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
class CORE_EXPORT QgsRenderedItemDetails
|
||||
{
|
||||
public:
|
||||
|
||||
#ifdef SIP_RUN
|
||||
SIP_CONVERT_TO_SUBCLASS_CODE
|
||||
if ( dynamic_cast<QgsRenderedAnnotationItemDetails *>( sipCpp ) )
|
||||
sipType = sipType_QgsRenderedAnnotationItemDetails;
|
||||
else
|
||||
sipType = 0;
|
||||
SIP_END
|
||||
#endif
|
||||
|
||||
virtual ~QgsRenderedItemDetails();
|
||||
|
||||
/**
|
||||
* Clones the details.
|
||||
*/
|
||||
virtual QgsRenderedItemDetails *clone() const = 0 SIP_FACTORY;
|
||||
|
||||
/**
|
||||
* Returns the bounding box of the item (in map units).
|
||||
*
|
||||
* \see setBoundingBox()
|
||||
*/
|
||||
QgsRectangle boundingBox() const { return mBounds; }
|
||||
|
||||
/**
|
||||
* Sets the bounding box of the item (in map units).
|
||||
*
|
||||
* \see boundingBox()
|
||||
*/
|
||||
void setBoundingBox( const QgsRectangle &bounds ) { mBounds = bounds; }
|
||||
|
||||
private:
|
||||
|
||||
QgsRectangle mBounds;
|
||||
};
|
||||
|
||||
#endif // QGSRENDEREDITEMDETAILS_H
|
@ -92,6 +92,7 @@ email : sherman at mrcc.com
|
||||
#include "qgslabelingresults.h"
|
||||
#include "qgsmaplayerutils.h"
|
||||
#include "qgssettingsregistrygui.h"
|
||||
#include "qgsrendereditemresults.h"
|
||||
|
||||
/**
|
||||
* \ingroup gui
|
||||
@ -472,6 +473,14 @@ const QgsLabelingResults *QgsMapCanvas::labelingResults( bool allowOutdatedResul
|
||||
return mLabelingResults.get();
|
||||
}
|
||||
|
||||
const QgsRenderedItemResults *QgsMapCanvas::renderedItemResults( bool allowOutdatedResults ) const
|
||||
{
|
||||
if ( !allowOutdatedResults && mRenderedItemResultsOutdated )
|
||||
return nullptr;
|
||||
|
||||
return mRenderedItemResults.get();
|
||||
}
|
||||
|
||||
void QgsMapCanvas::setCachingEnabled( bool enabled )
|
||||
{
|
||||
if ( enabled == isCachingEnabled() )
|
||||
@ -583,6 +592,7 @@ void QgsMapCanvas::refresh()
|
||||
mRefreshTimer->start( 1 );
|
||||
|
||||
mLabelingResultsOutdated = true;
|
||||
mRenderedItemResultsOutdated = true;
|
||||
}
|
||||
|
||||
void QgsMapCanvas::refreshMap()
|
||||
@ -697,6 +707,9 @@ void QgsMapCanvas::rendererJobFinished()
|
||||
}
|
||||
mLabelingResultsOutdated = false;
|
||||
|
||||
mRenderedItemResults.reset( mJob->takeRenderedItemResults() );
|
||||
mRenderedItemResultsOutdated = false;
|
||||
|
||||
QImage img = mJob->renderedImage();
|
||||
|
||||
// emit renderComplete to get our decorations drawn
|
||||
|
@ -70,6 +70,7 @@ class QgsSnappingUtils;
|
||||
class QgsRubberBand;
|
||||
class QgsMapCanvasAnnotationItem;
|
||||
class QgsReferencedRectangle;
|
||||
class QgsRenderedItemResults;
|
||||
|
||||
class QgsTemporalController;
|
||||
|
||||
@ -170,6 +171,17 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView, public QgsExpressionContex
|
||||
*/
|
||||
const QgsLabelingResults *labelingResults( bool allowOutdatedResults = true ) const;
|
||||
|
||||
/**
|
||||
* Gets access to the rendered item results (may be NULLPTR), which includes the results of rendering
|
||||
* annotation items in the canvas map.
|
||||
*
|
||||
* If the \a allowOutdatedResults flag is FALSE then outdated rendered item results (e.g.
|
||||
* as a result of an ongoing canvas render) will not be returned, and instead NULLPTR will be returned.
|
||||
*
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
const QgsRenderedItemResults *renderedItemResults( bool allowOutdatedResults = true ) const;
|
||||
|
||||
/**
|
||||
* Set whether to cache images of rendered layers
|
||||
* \since QGIS 2.4
|
||||
@ -1301,6 +1313,19 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView, public QgsExpressionContex
|
||||
//! TRUE if the labeling results stored in mLabelingResults are outdated (e.g. as a result of an ongoing canvas render)
|
||||
bool mLabelingResultsOutdated = false;
|
||||
|
||||
/**
|
||||
* Rendered results from the recently rendered map.
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
std::unique_ptr< QgsRenderedItemResults > mRenderedItemResults;
|
||||
|
||||
/**
|
||||
* TRUE if the rendered item results stored in mRenderedItemResults are outdated (e.g. as a result of an ongoing canvas render)
|
||||
*
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
bool mRenderedItemResultsOutdated = false;
|
||||
|
||||
//! Whether layers are rendered sequentially or in parallel
|
||||
bool mUseParallelRendering = false;
|
||||
|
||||
|
@ -38,6 +38,9 @@ from qgis.core import (QgsMapSettings,
|
||||
QgsAnnotationMarkerItem,
|
||||
QgsLineSymbol,
|
||||
QgsMarkerSymbol,
|
||||
QgsMapRendererSequentialJob,
|
||||
QgsMapRendererParallelJob,
|
||||
QgsGeometry
|
||||
)
|
||||
from qgis.testing import start_app, unittest
|
||||
|
||||
@ -247,17 +250,17 @@ class TestQgsAnnotationLayer(unittest.TestCase):
|
||||
item.setSymbol(
|
||||
QgsFillSymbol.createSimple({'color': '200,100,100', 'outline_color': 'black', 'outline_width': '2'}))
|
||||
item.setZIndex(3)
|
||||
layer.addItem(item)
|
||||
i1_id = layer.addItem(item)
|
||||
|
||||
item = QgsAnnotationLineItem(QgsLineString([QgsPoint(11, 13), QgsPoint(12, 13), QgsPoint(12, 15)]))
|
||||
item.setSymbol(QgsLineSymbol.createSimple({'color': '#ffff00', 'line_width': '3'}))
|
||||
item.setZIndex(2)
|
||||
layer.addItem(item)
|
||||
i2_id = layer.addItem(item)
|
||||
|
||||
item = QgsAnnotationMarkerItem(QgsPoint(12, 13))
|
||||
item.setSymbol(QgsMarkerSymbol.createSimple({'color': '100,200,200', 'size': '6', 'outline_color': 'black'}))
|
||||
item.setZIndex(1)
|
||||
layer.addItem(item)
|
||||
i3_id = layer.addItem(item)
|
||||
|
||||
settings = QgsMapSettings()
|
||||
settings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
|
||||
@ -282,6 +285,17 @@ class TestQgsAnnotationLayer(unittest.TestCase):
|
||||
|
||||
self.assertTrue(self.imageCheck('layer_render', 'layer_render', image))
|
||||
|
||||
# also check details of rendered items
|
||||
item_details = renderer.takeRenderedItemDetails()
|
||||
self.assertEqual([i.layerId() for i in item_details], [layer.id()] * 3)
|
||||
self.assertCountEqual([i.itemId() for i in item_details], [i1_id, i2_id, i3_id])
|
||||
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i1_id][0],
|
||||
QgsRectangle(12, 13, 14, 15))
|
||||
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i2_id][0],
|
||||
QgsRectangle(11, 13, 12, 15))
|
||||
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i3_id][0],
|
||||
QgsRectangle(12, 13, 12, 13))
|
||||
|
||||
def testRenderWithTransform(self):
|
||||
layer = QgsAnnotationLayer('test', QgsAnnotationLayer.LayerOptions(QgsProject.instance().transformContext()))
|
||||
self.assertTrue(layer.isValid())
|
||||
@ -291,17 +305,17 @@ class TestQgsAnnotationLayer(unittest.TestCase):
|
||||
item.setSymbol(
|
||||
QgsFillSymbol.createSimple({'color': '200,100,100', 'outline_color': 'black', 'outline_width': '2'}))
|
||||
item.setZIndex(1)
|
||||
layer.addItem(item)
|
||||
i1_id = layer.addItem(item)
|
||||
|
||||
item = QgsAnnotationLineItem(QgsLineString([QgsPoint(11, 13), QgsPoint(12, 13), QgsPoint(12, 15)]))
|
||||
item.setSymbol(QgsLineSymbol.createSimple({'color': '#ffff00', 'line_width': '3'}))
|
||||
item.setZIndex(2)
|
||||
layer.addItem(item)
|
||||
i2_id = layer.addItem(item)
|
||||
|
||||
item = QgsAnnotationMarkerItem(QgsPoint(12, 13))
|
||||
item.setSymbol(QgsMarkerSymbol.createSimple({'color': '100,200,200', 'size': '6', 'outline_color': 'black'}))
|
||||
item.setZIndex(3)
|
||||
layer.addItem(item)
|
||||
i3_id = layer.addItem(item)
|
||||
|
||||
layer.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
|
||||
|
||||
@ -331,6 +345,123 @@ class TestQgsAnnotationLayer(unittest.TestCase):
|
||||
|
||||
self.assertTrue(self.imageCheck('layer_render_transform', 'layer_render_transform', image))
|
||||
|
||||
# also check details of rendered items
|
||||
item_details = renderer.takeRenderedItemDetails()
|
||||
self.assertEqual([i.layerId() for i in item_details], [layer.id()] * 3)
|
||||
self.assertCountEqual([i.itemId() for i in item_details], [i1_id, i2_id, i3_id])
|
||||
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i1_id][0],
|
||||
QgsRectangle(11.5, 13, 12, 13.5))
|
||||
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i2_id][0],
|
||||
QgsRectangle(11, 13, 12, 15))
|
||||
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i3_id][0],
|
||||
QgsRectangle(12, 13, 12, 13))
|
||||
|
||||
def test_render_via_job(self):
|
||||
"""
|
||||
Test rendering an annotation layer via a map render job
|
||||
"""
|
||||
layer = QgsAnnotationLayer('test', QgsAnnotationLayer.LayerOptions(QgsProject.instance().transformContext()))
|
||||
self.assertTrue(layer.isValid())
|
||||
|
||||
item = QgsAnnotationPolygonItem(
|
||||
QgsPolygon(QgsLineString([QgsPoint(11.5, 13), QgsPoint(12, 13), QgsPoint(12, 13.5), QgsPoint(11.5, 13)])))
|
||||
item.setSymbol(
|
||||
QgsFillSymbol.createSimple({'color': '200,100,100', 'outline_color': 'black', 'outline_width': '2'}))
|
||||
item.setZIndex(1)
|
||||
i1_id = layer.addItem(item)
|
||||
|
||||
item = QgsAnnotationLineItem(QgsLineString([QgsPoint(11, 13), QgsPoint(12, 13), QgsPoint(12, 15)]))
|
||||
item.setSymbol(QgsLineSymbol.createSimple({'color': '#ffff00', 'line_width': '3'}))
|
||||
item.setZIndex(2)
|
||||
i2_id = layer.addItem(item)
|
||||
|
||||
item = QgsAnnotationMarkerItem(QgsPoint(12, 13))
|
||||
item.setSymbol(QgsMarkerSymbol.createSimple({'color': '100,200,200', 'size': '6', 'outline_color': 'black'}))
|
||||
item.setZIndex(3)
|
||||
i3_id = layer.addItem(item)
|
||||
|
||||
layer.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
|
||||
|
||||
settings = QgsMapSettings()
|
||||
settings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
|
||||
settings.setExtent(QgsRectangle(10, 10, 18, 18))
|
||||
settings.setOutputSize(QSize(200, 200))
|
||||
settings.setLayers([layer])
|
||||
|
||||
job = QgsMapRendererParallelJob(settings)
|
||||
job.start()
|
||||
job.waitForFinished()
|
||||
|
||||
# check rendered item results
|
||||
item_results = job.takeRenderedItemResults()
|
||||
item_details = item_results.renderedItems()
|
||||
self.assertEqual(len(item_details), 3)
|
||||
self.assertEqual([i.layerId() for i in item_details], [layer.id()] * 3)
|
||||
self.assertCountEqual([i.itemId() for i in item_details], [i1_id, i2_id, i3_id])
|
||||
self.assertCountEqual([i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(0, 0, 1, 1))], [])
|
||||
self.assertCountEqual(
|
||||
[i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(10, 10, 11, 18))], [i2_id])
|
||||
self.assertCountEqual(
|
||||
[i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(10, 10, 12, 18))], [i1_id, i2_id, i3_id])
|
||||
|
||||
# bounds should be in map crs
|
||||
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i1_id][0],
|
||||
QgsRectangle(11.5, 13, 12, 13.5))
|
||||
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i2_id][0],
|
||||
QgsRectangle(11, 13, 12, 15))
|
||||
self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i3_id][0],
|
||||
QgsRectangle(12, 13, 12, 13))
|
||||
|
||||
def test_render_via_job_with_transform(self):
|
||||
"""
|
||||
Test rendering an annotation layer via a map render job
|
||||
"""
|
||||
layer = QgsAnnotationLayer('test', QgsAnnotationLayer.LayerOptions(QgsProject.instance().transformContext()))
|
||||
self.assertTrue(layer.isValid())
|
||||
|
||||
item = QgsAnnotationPolygonItem(
|
||||
QgsPolygon(QgsLineString([QgsPoint(11.5, 13), QgsPoint(12, 13), QgsPoint(12, 13.5), QgsPoint(11.5, 13)])))
|
||||
item.setSymbol(
|
||||
QgsFillSymbol.createSimple({'color': '200,100,100', 'outline_color': 'black', 'outline_width': '2'}))
|
||||
item.setZIndex(1)
|
||||
i1_id = layer.addItem(item)
|
||||
|
||||
item = QgsAnnotationLineItem(QgsLineString([QgsPoint(11, 13), QgsPoint(12, 13), QgsPoint(12, 15)]))
|
||||
item.setSymbol(QgsLineSymbol.createSimple({'color': '#ffff00', 'line_width': '3'}))
|
||||
item.setZIndex(2)
|
||||
i2_id = layer.addItem(item)
|
||||
|
||||
item = QgsAnnotationMarkerItem(QgsPoint(12, 13))
|
||||
item.setSymbol(QgsMarkerSymbol.createSimple({'color': '100,200,200', 'size': '6', 'outline_color': 'black'}))
|
||||
item.setZIndex(3)
|
||||
i3_id = layer.addItem(item)
|
||||
|
||||
layer.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
|
||||
|
||||
settings = QgsMapSettings()
|
||||
settings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
|
||||
settings.setExtent(QgsRectangle(1250958, 1386945, 1420709, 1532518))
|
||||
settings.setOutputSize(QSize(200, 200))
|
||||
settings.setLayers([layer])
|
||||
|
||||
job = QgsMapRendererSequentialJob(settings)
|
||||
job.start()
|
||||
job.waitForFinished()
|
||||
|
||||
# check rendered item results
|
||||
item_results = job.takeRenderedItemResults()
|
||||
item_details = item_results.renderedItems()
|
||||
self.assertEqual(len(item_details), 3)
|
||||
self.assertEqual([i.layerId() for i in item_details], [layer.id()] * 3)
|
||||
self.assertCountEqual([i.itemId() for i in item_details], [i1_id, i2_id, i3_id])
|
||||
# bounds should be in map crs
|
||||
self.assertEqual([QgsGeometry.fromRect(i.boundingBox()).asWkt(0) for i in item_details if i.itemId() == i1_id][0],
|
||||
'Polygon ((1280174 1459732, 1335834 1459732, 1335834 1516914, 1280174 1516914, 1280174 1459732))')
|
||||
self.assertEqual([QgsGeometry.fromRect(i.boundingBox()).asWkt(0) for i in item_details if i.itemId() == i2_id][0],
|
||||
'Polygon ((1224514 1459732, 1335834 1459732, 1335834 1689200, 1224514 1689200, 1224514 1459732))')
|
||||
self.assertEqual([QgsGeometry.fromRect(i.boundingBox()).asWkt(0) for i in item_details if i.itemId() == i3_id][0],
|
||||
'Polygon ((1335834 1459732, 1335834 1459732, 1335834 1459732, 1335834 1459732, 1335834 1459732))')
|
||||
|
||||
def imageCheck(self, name, reference_image, image):
|
||||
TestQgsAnnotationLayer.report += "<h2>Render {}</h2>\n".format(name)
|
||||
temp_dir = QDir.tempPath() + '/'
|
||||
|
Loading…
x
Reference in New Issue
Block a user