diff --git a/python/core/auto_generated/qgsapplication.sip.in b/python/core/auto_generated/qgsapplication.sip.in index ee42179d37a..ba69766bdca 100644 --- a/python/core/auto_generated/qgsapplication.sip.in +++ b/python/core/auto_generated/qgsapplication.sip.in @@ -745,6 +745,13 @@ Returns the application's renderer registry, used for managing vector layer rend Returns the application's point cloud renderer registry, used for managing point cloud layer 2D renderers. .. versionadded:: 3.18 +%End + + static QgsTiledSceneRendererRegistry *tiledSceneRendererRegistry() /KeepReference/; +%Docstring +Returns the application's tiled scene renderer registry, used for managing tiled scene layer 2D renderers. + +.. versionadded:: 3.34 %End static QgsDataItemProviderRegistry *dataItemProviderRegistry() /KeepReference/; diff --git a/python/core/auto_generated/tiledscene/qgstiledscenelayer.sip.in b/python/core/auto_generated/tiledscene/qgstiledscenelayer.sip.in index 31d808d72d0..7c61cff2291 100644 --- a/python/core/auto_generated/tiledscene/qgstiledscenelayer.sip.in +++ b/python/core/auto_generated/tiledscene/qgstiledscenelayer.sip.in @@ -9,6 +9,7 @@ + class QgsTiledSceneLayer : QgsMapLayer { %Docstring(signature="appended") @@ -89,6 +90,23 @@ QgsTiledSceneLayer cannot be copied. virtual QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) /Factory/; + QgsTiledSceneRenderer *renderer(); +%Docstring +Returns the 2D renderer for the tiled scene. + +.. seealso:: :py:func:`setRenderer` +%End + + + void setRenderer( QgsTiledSceneRenderer *renderer /Transfer/ ); +%Docstring +Sets the 2D ``renderer`` for the tiled scene. + +Ownership of ``renderer`` is transferred to the layer. + +.. seealso:: :py:func:`renderer` +%End + private: QgsTiledSceneLayer( const QgsTiledSceneLayer &rhs ); }; diff --git a/python/core/auto_generated/tiledscene/qgstiledscenerenderer.sip.in b/python/core/auto_generated/tiledscene/qgstiledscenerenderer.sip.in new file mode 100644 index 00000000000..ecaf045c711 --- /dev/null +++ b/python/core/auto_generated/tiledscene/qgstiledscenerenderer.sip.in @@ -0,0 +1,222 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/tiledscene/qgstiledscenerenderer.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsTiledSceneRenderContext +{ +%Docstring(signature="appended") + +Encapsulates the render context for a 2D tiled scene rendering operation. + +.. versionadded:: 3.34 +%End + +%TypeHeaderCode +#include "qgstiledscenerenderer.h" +%End + public: + + QgsTiledSceneRenderContext( QgsRenderContext &context, QgsFeedback *feedback = 0 ); +%Docstring +Constructor for QgsTiledSceneRenderContext. +%End + + + + QgsRenderContext &renderContext(); +%Docstring +Returns a reference to the context's render context. +%End + + + QgsFeedback *feedback() const; +%Docstring +Returns the feedback object used to cancel rendering + +.. versionadded:: 3.20 +%End + + private: + QgsTiledSceneRenderContext( const QgsTiledSceneRenderContext &rh ); +}; + +class QgsTiledSceneRenderer +{ +%Docstring(signature="appended") + +Abstract base class for 2d tiled scene renderers. + +.. versionadded:: 3.34 +%End + +%TypeHeaderCode +#include "qgstiledscenerenderer.h" +%End +%ConvertToSubClassCode + + const QString type = sipCpp->type(); + + sipType = 0; +%End + public: + + QgsTiledSceneRenderer(); +%Docstring +Constructor for QgsTiledSceneRenderer. +%End + + virtual ~QgsTiledSceneRenderer(); + + virtual QString type() const = 0; +%Docstring +Returns the identifier of the renderer type. +%End + + virtual QgsTiledSceneRenderer *clone() const = 0 /Factory/; +%Docstring +Create a deep copy of this renderer. Should be implemented by all subclasses +and generate a proper subclass. +%End + + + + static QgsTiledSceneRenderer *load( QDomElement &element, const QgsReadWriteContext &context ) /Factory/; +%Docstring +Creates a renderer from an XML ``element``. + +Caller takes ownership of the returned renderer. + +.. seealso:: :py:func:`save` +%End + + virtual QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const = 0; +%Docstring +Saves the renderer configuration to an XML element. + +.. seealso:: :py:func:`load` +%End + + double maximumScreenError() const; +%Docstring +Returns the maximum screen error allowed when rendering the tiled scene. + +Larger values result in a faster render with less detailed features rendered. + +Units are retrieved via :py:func:`~QgsTiledSceneRenderer.maximumScreenErrorUnit`. + +.. seealso:: :py:func:`setMaximumScreenError` + +.. seealso:: :py:func:`maximumScreenErrorUnit` +%End + + void setMaximumScreenError( double error ); +%Docstring +Sets the maximum screen ``error`` allowed when rendering the tiled scene. + +Larger values result in a faster render with less detailed features rendered. + +Units are set via :py:func:`~QgsTiledSceneRenderer.setMaximumScreenErrorUnit`. + +.. seealso:: :py:func:`maximumScreenError` + +.. seealso:: :py:func:`setMaximumScreenErrorUnit` +%End + + Qgis::RenderUnit maximumScreenErrorUnit() const; +%Docstring +Returns the unit for the maximum screen error allowed when rendering the tiled scene. + +.. seealso:: :py:func:`maximumScreenError` + +.. seealso:: :py:func:`setMaximumScreenErrorUnit` +%End + + void setMaximumScreenErrorUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` for the maximum screen error allowed when rendering the tiled scene. + +.. seealso:: :py:func:`setMaximumScreenError` + +.. seealso:: :py:func:`maximumScreenErrorUnit` +%End + + virtual void startRender( QgsTiledSceneRenderContext &context ); +%Docstring +Must be called when a new render cycle is started. A call to :py:func:`~QgsTiledSceneRenderer.startRender` must always +be followed by a corresponding call to :py:func:`~QgsTiledSceneRenderer.stopRender` after all features have been rendered. + +.. seealso:: :py:func:`stopRender` + +.. warning:: + + This method is not thread safe. Before calling :py:func:`~QgsTiledSceneRenderer.startRender` in a non-main thread, + the renderer should instead be cloned and :py:func:`~QgsTiledSceneRenderer.startRender`/:py:func:`~QgsTiledSceneRenderer.stopRender` called on the clone. +%End + + virtual void stopRender( QgsTiledSceneRenderContext &context ); +%Docstring +Must be called when a render cycle has finished, to allow the renderer to clean up. + +Calls to :py:func:`~QgsTiledSceneRenderer.stopRender` must always be preceded by a call to :py:func:`~QgsTiledSceneRenderer.startRender`. + +.. warning:: + + This method is not thread safe. Before calling :py:func:`~QgsTiledSceneRenderer.startRender` in a non-main thread, + the renderer should instead be cloned and :py:func:`~QgsTiledSceneRenderer.startRender`/:py:func:`~QgsTiledSceneRenderer.stopRender` called on the clone. + +.. seealso:: :py:func:`startRender` +%End + + virtual QList createLegendNodes( QgsLayerTreeLayer *nodeLayer ) /Factory/; +%Docstring +Creates a set of legend nodes representing the renderer. +%End + + virtual QStringList legendRuleKeys() const; +%Docstring +Returns a list of all rule keys for legend nodes created by the renderer. +%End + + protected: + + void copyCommonProperties( QgsTiledSceneRenderer *destination ) const; +%Docstring +Copies common tiled scene renderer properties (such as screen error) to the ``destination`` renderer. +%End + + void restoreCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ); +%Docstring +Restores common renderer properties (such as screen error) from the +specified DOM ``element``. + +.. seealso:: :py:func:`saveCommonProperties` +%End + + void saveCommonProperties( QDomElement &element, const QgsReadWriteContext &context ) const; +%Docstring +Saves common renderer properties (such as point size and screen error) to the +specified DOM ``element``. + +.. seealso:: :py:func:`restoreCommonProperties` +%End + + private: + QgsTiledSceneRenderer( const QgsTiledSceneRenderer &other ); +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/tiledscene/qgstiledscenerenderer.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/auto_generated/tiledscene/qgstiledscenerendererregistry.sip.in b/python/core/auto_generated/tiledscene/qgstiledscenerendererregistry.sip.in new file mode 100644 index 00000000000..841370e9d2d --- /dev/null +++ b/python/core/auto_generated/tiledscene/qgstiledscenerendererregistry.sip.in @@ -0,0 +1,173 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/tiledscene/qgstiledscenerendererregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsTiledSceneRendererAbstractMetadata +{ +%Docstring(signature="appended") +Stores metadata about one tiled scene renderer class. + +.. note:: + + It's necessary to implement :py:func:`~createRenderer` function. + In C++ you can use :py:class:`QgsTiledSceneRendererMetadata` convenience class. + +.. versionadded:: 3.34 +%End + +%TypeHeaderCode +#include "qgstiledscenerendererregistry.h" +%End + public: + + QgsTiledSceneRendererAbstractMetadata( const QString &name, const QString &visibleName, const QIcon &icon = QIcon() ); +%Docstring +Constructor for QgsTiledSceneRendererAbstractMetadata, with the specified ``name``. + +The ``visibleName`` argument gives a translated, user friendly string identifying the renderer type. + +The ``icon`` argument can be used to specify an icon representing the renderer. +%End + virtual ~QgsTiledSceneRendererAbstractMetadata(); + + QString name() const; +%Docstring +Returns the unique name of the renderer. This value is not translated. + +.. seealso:: :py:func:`visibleName` +%End + + QString visibleName() const; +%Docstring +Returns a friendly display name of the renderer. This value is translated. + +.. seealso:: :py:func:`name` +%End + + QIcon icon() const; +%Docstring +Returns an icon representing the renderer. + +.. seealso:: :py:func:`setIcon` +%End + + void setIcon( const QIcon &icon ); +%Docstring +Sets an ``icon`` representing the renderer. + +.. seealso:: :py:func:`icon` +%End + + virtual QgsTiledSceneRenderer *createRenderer( QDomElement &elem, const QgsReadWriteContext &context ) = 0 /Factory/; +%Docstring +Returns new instance of the renderer given the DOM element. Returns ``None`` on error. +Pure virtual function: must be implemented in derived classes. +%End + + + protected: +}; + + +class QgsTiledSceneRendererMetadata : QgsTiledSceneRendererAbstractMetadata +{ +%Docstring(signature="appended") +Convenience metadata class that uses static functions to create tiled scene renderer and its widget. + +.. versionadded:: 3.34 +%End + +%TypeHeaderCode +#include "qgstiledscenerendererregistry.h" +%End + public: + + + virtual QgsTiledSceneRenderer *createRenderer( QDomElement &elem, const QgsReadWriteContext &context ) /Factory/; + + + + + protected: + + private: + QgsTiledSceneRendererMetadata(); +}; + + +class QgsTiledSceneRendererRegistry +{ +%Docstring(signature="appended") +Registry of 2D renderers for tiled scenes. + +:py:class:`QgsTiledSceneRendererRegistry` is not usually directly created, but rather accessed through +:py:func:`QgsApplication.tiledSceneRendererRegistry()`. + +.. versionadded:: 3.34 +%End + +%TypeHeaderCode +#include "qgstiledscenerendererregistry.h" +%End + public: + + QgsTiledSceneRendererRegistry(); + ~QgsTiledSceneRendererRegistry(); + + + bool addRenderer( QgsTiledSceneRendererAbstractMetadata *metadata /Transfer/ ); +%Docstring +Adds a renderer to the registry. Takes ownership of the metadata object. + +:param metadata: renderer metadata + +:return: ``True`` if renderer was added successfully, or ``False`` if renderer could not + be added (e.g., a renderer with a duplicate name already exists) +%End + + bool removeRenderer( const QString &rendererName ); +%Docstring +Removes a renderer from registry. + +:param rendererName: name of renderer to remove from registry + +:return: ``True`` if renderer was successfully removed, or ``False`` if matching + renderer could not be found +%End + + QgsTiledSceneRendererAbstractMetadata *rendererMetadata( const QString &rendererName ); +%Docstring +Returns the metadata for a specified renderer. Returns ``None`` if a matching +renderer was not found in the registry. +%End + + QStringList renderersList() const; +%Docstring +Returns a list of available renderers. +%End + + static QgsTiledSceneRenderer *defaultRenderer( const QgsTiledSceneLayer *layer ) /Factory/; +%Docstring +Returns a new default tiled scene renderer for a specified ``layer``. + +Caller takes ownership of the returned renderer. +%End + + private: + QgsTiledSceneRendererRegistry( const QgsTiledSceneRendererRegistry &rh ); +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/tiledscene/qgstiledscenerendererregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 427211c9cd2..0840ff7b592 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -717,6 +717,8 @@ %Include auto_generated/tiledscene/qgstiledscenedataprovider.sip %Include auto_generated/tiledscene/qgstiledsceneindex.sip %Include auto_generated/tiledscene/qgstiledscenelayer.sip +%Include auto_generated/tiledscene/qgstiledscenerenderer.sip +%Include auto_generated/tiledscene/qgstiledscenerendererregistry.sip %Include auto_generated/tiledscene/qgstiledscenerequest.sip %Include auto_generated/tiledscene/qgstiledscenetile.sip %Include auto_generated/sensor/qgssensormodel.sip diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d2618b3646a..1e8f2d770a3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -347,6 +347,8 @@ set(QGIS_CORE_SRCS tiledscene/qgstiledscenelayerrenderer.cpp tiledscene/qgstiledscenenode.cpp tiledscene/qgstiledsceneprovidermetadata.cpp + tiledscene/qgstiledscenerenderer.cpp + tiledscene/qgstiledscenerendererregistry.cpp tiledscene/qgstiledscenerequest.cpp tiledscene/qgstiledscenetile.cpp @@ -1921,6 +1923,8 @@ set(QGIS_CORE_HDRS tiledscene/qgstiledscenelayerrenderer.h tiledscene/qgstiledscenenode.h tiledscene/qgstiledsceneprovidermetadata.h + tiledscene/qgstiledscenerenderer.h + tiledscene/qgstiledscenerendererregistry.h tiledscene/qgstiledscenerequest.h tiledscene/qgstiledscenetile.h diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp index 86c759669ea..1ad1f2e70b2 100644 --- a/src/core/qgsapplication.cpp +++ b/src/core/qgsapplication.cpp @@ -57,6 +57,7 @@ #include "qgssettings.h" #include "qgssettingsregistrycore.h" #include "qgstiledownloadmanager.h" +#include "qgstiledscenerendererregistry.h" #include "qgsunittypes.h" #include "qgsuserprofile.h" #include "qgsuserprofilemanager.h" @@ -2428,6 +2429,11 @@ QgsPointCloudRendererRegistry *QgsApplication::pointCloudRendererRegistry() return members()->mPointCloudRendererRegistry; } +QgsTiledSceneRendererRegistry *QgsApplication::tiledSceneRendererRegistry() +{ + return members()->mTiledSceneRendererRegistry; +} + QgsDataItemProviderRegistry *QgsApplication::dataItemProviderRegistry() { if ( auto *lInstance = instance() ) @@ -2737,6 +2743,11 @@ QgsApplication::ApplicationMembers::ApplicationMembers() mPointCloudRendererRegistry = new QgsPointCloudRendererRegistry(); profiler->end(); } + { + profiler->start( tr( "Setup tiled scene renderer registry" ) ); + mTiledSceneRendererRegistry = new QgsTiledSceneRendererRegistry(); + profiler->end(); + } { profiler->start( tr( "Setup GPS registry" ) ); mGpsConnectionRegistry = new QgsGpsConnectionRegistry(); @@ -2851,6 +2862,7 @@ QgsApplication::ApplicationMembers::~ApplicationMembers() delete mSensorRegistry; delete mLayoutItemRegistry; delete mPointCloudRendererRegistry; + delete mTiledSceneRendererRegistry; delete mRasterRendererRegistry; delete mRendererRegistry; delete mSvgCache; diff --git a/src/core/qgsapplication.h b/src/core/qgsapplication.h index 3aa254c9fcd..01adc6fb327 100644 --- a/src/core/qgsapplication.h +++ b/src/core/qgsapplication.h @@ -70,6 +70,7 @@ class QgsConnectionRegistry; class QgsScaleBarRendererRegistry; class Qgs3DSymbolRegistry; class QgsPointCloudRendererRegistry; +class QgsTiledSceneRendererRegistry; class QgsTileDownloadManager; class QgsCoordinateReferenceSystemRegistry; class QgsRecentStyleHandler; @@ -721,6 +722,12 @@ class CORE_EXPORT QgsApplication : public QApplication */ static QgsPointCloudRendererRegistry *pointCloudRendererRegistry() SIP_KEEPREFERENCE; + /** + * Returns the application's tiled scene renderer registry, used for managing tiled scene layer 2D renderers. + * \since QGIS 3.34 + */ + static QgsTiledSceneRendererRegistry *tiledSceneRendererRegistry() SIP_KEEPREFERENCE; + /** * Returns the application's data item provider registry, which keeps a list of data item * providers that may add items to the browser tree. @@ -1170,6 +1177,7 @@ class CORE_EXPORT QgsApplication : public QApplication QgsRasterRendererRegistry *mRasterRendererRegistry = nullptr; QgsRendererRegistry *mRendererRegistry = nullptr; QgsPointCloudRendererRegistry *mPointCloudRendererRegistry = nullptr; + QgsTiledSceneRendererRegistry *mTiledSceneRendererRegistry = nullptr; QgsSvgCache *mSvgCache = nullptr; QgsImageCache *mImageCache = nullptr; QgsSourceCache *mSourceCache = nullptr; diff --git a/src/core/tiledscene/qgstiledscenelayer.cpp b/src/core/tiledscene/qgstiledscenelayer.cpp index ae0cad2bf78..9e2929c738b 100644 --- a/src/core/tiledscene/qgstiledscenelayer.cpp +++ b/src/core/tiledscene/qgstiledscenelayer.cpp @@ -21,10 +21,12 @@ #include "qgspainting.h" #include "qgsproviderregistry.h" #include "qgslayermetadataformatter.h" +#include "qgstiledscenerenderer.h" #include "qgsxmlutils.h" #include "qgsruntimeprofiler.h" #include "qgsapplication.h" #include "qgstiledscenelayerrenderer.h" +#include "qgstiledscenerendererregistry.h" QgsTiledSceneLayer::QgsTiledSceneLayer( const QString &uri, const QString &baseName, @@ -62,6 +64,9 @@ QgsTiledSceneLayer *QgsTiledSceneLayer::clone() const QgsTiledSceneLayer *layer = new QgsTiledSceneLayer( source(), name(), mProviderKey, mLayerOptions ); QgsMapLayer::clone( layer ); + if ( mRenderer ) + layer->setRenderer( mRenderer->clone() ); + layer->mLayerOptions = mLayerOptions; return layer; @@ -84,6 +89,32 @@ QgsMapLayerRenderer *QgsTiledSceneLayer::createMapRenderer( QgsRenderContext &co return new QgsTiledSceneLayerRenderer( this, context ); } +QgsTiledSceneRenderer *QgsTiledSceneLayer::renderer() +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return mRenderer.get(); +} + +const QgsTiledSceneRenderer *QgsTiledSceneLayer::renderer() const +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return mRenderer.get(); +} + +void QgsTiledSceneLayer::setRenderer( QgsTiledSceneRenderer *renderer ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + if ( renderer == mRenderer.get() ) + return; + + mRenderer.reset( renderer ); + emit rendererChanged(); + emitStyleChanged(); +} + QgsTiledSceneDataProvider *QgsTiledSceneLayer::dataProvider() { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -172,7 +203,7 @@ bool QgsTiledSceneLayer::readSymbology( const QDomNode &node, QString &errorMess return true; } -bool QgsTiledSceneLayer::readStyle( const QDomNode &node, QString &, QgsReadWriteContext &, QgsMapLayer::StyleCategories categories ) +bool QgsTiledSceneLayer::readStyle( const QDomNode &node, QString &, QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -187,6 +218,25 @@ bool QgsTiledSceneLayer::readStyle( const QDomNode &node, QString &, QgsReadWrit const QDomElement e = blendModeNode.toElement(); setBlendMode( QgsPainting::getCompositionMode( static_cast< Qgis::BlendMode >( e.text().toInt() ) ) ); } + + QDomElement rendererElement = node.firstChildElement( QStringLiteral( "renderer" ) ); + if ( !rendererElement.isNull() ) + { + std::unique_ptr< QgsTiledSceneRenderer > r( QgsTiledSceneRenderer::load( rendererElement, context ) ); + if ( r ) + { + setRenderer( r.release() ); + } + else + { + result = false; + } + } + // make sure layer has a renderer - if none exists, fallback to a default renderer + if ( !mRenderer ) + { + setRenderer( QgsTiledSceneRendererRegistry::defaultRenderer( this ) ); + } } // get and set the layer transparency and scale visibility if they exists @@ -231,7 +281,7 @@ bool QgsTiledSceneLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QStr return true; } -bool QgsTiledSceneLayer::writeStyle( QDomNode &node, QDomDocument &doc, QString &, const QgsReadWriteContext &, QgsMapLayer::StyleCategories categories ) const +bool QgsTiledSceneLayer::writeStyle( QDomNode &node, QDomDocument &doc, QString &, const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -250,6 +300,12 @@ bool QgsTiledSceneLayer::writeStyle( QDomNode &node, QDomDocument &doc, QString const QDomText blendModeText = doc.createTextNode( QString::number( static_cast< int >( QgsPainting::getBlendModeEnum( blendMode() ) ) ) ); blendModeElem.appendChild( blendModeText ); node.appendChild( blendModeElem ); + + if ( mRenderer ) + { + const QDomElement rendererElement = mRenderer->save( doc, context ); + node.appendChild( rendererElement ); + } } // add the layer opacity and scale visibility @@ -321,6 +377,32 @@ void QgsTiledSceneLayer::setDataSourcePrivate( const QString &dataSource, const { setExtent( mDataProvider->extent() ); } + + bool loadDefaultStyleFlag = false; + if ( flags & QgsDataProvider::FlagLoadDefaultStyle ) + { + loadDefaultStyleFlag = true; + } + + if ( !mRenderer || loadDefaultStyleFlag ) + { + std::unique_ptr< QgsScopedRuntimeProfile > profile; + if ( QgsApplication::profiler()->groupIsActive( QStringLiteral( "projectload" ) ) ) + profile = std::make_unique< QgsScopedRuntimeProfile >( tr( "Load layer style" ), QStringLiteral( "projectload" ) ); + + bool defaultLoadedFlag = false; + + if ( !defaultLoadedFlag && loadDefaultStyleFlag ) + { + loadDefaultStyle( defaultLoadedFlag ); + } + + if ( !defaultLoadedFlag ) + { + // all else failed, create default renderer + setRenderer( QgsTiledSceneRendererRegistry::defaultRenderer( this ) ); + } + } } QString QgsTiledSceneLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const diff --git a/src/core/tiledscene/qgstiledscenelayer.h b/src/core/tiledscene/qgstiledscenelayer.h index 625ffd6273f..a140d2cdad3 100644 --- a/src/core/tiledscene/qgstiledscenelayer.h +++ b/src/core/tiledscene/qgstiledscenelayer.h @@ -22,6 +22,8 @@ #include "qgsmaplayer.h" #include "qgstiledscenedataprovider.h" +class QgsTiledSceneRenderer; + /** * \ingroup core * @@ -112,6 +114,30 @@ class CORE_EXPORT QgsTiledSceneLayer : public QgsMapLayer QString htmlMetadata() const override; QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) override SIP_FACTORY; + /** + * Returns the 2D renderer for the tiled scene. + * + * \see setRenderer() + */ + QgsTiledSceneRenderer *renderer(); + + /** + * Returns the 2D renderer for the tiled scene. + * \note not available in Python bindings + * + * \see setRenderer() + */ + const QgsTiledSceneRenderer *renderer() const SIP_SKIP; + + /** + * Sets the 2D \a renderer for the tiled scene. + * + * Ownership of \a renderer is transferred to the layer. + * + * \see renderer() + */ + void setRenderer( QgsTiledSceneRenderer *renderer SIP_TRANSFER ); + private slots: void setDataSourcePrivate( const QString &dataSource, const QString &baseName, const QString &provider, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags ) override; @@ -124,6 +150,7 @@ class CORE_EXPORT QgsTiledSceneLayer : public QgsMapLayer #endif std::unique_ptr mDataProvider; + std::unique_ptr mRenderer; LayerOptions mLayerOptions; }; diff --git a/src/core/tiledscene/qgstiledscenerenderer.cpp b/src/core/tiledscene/qgstiledscenerenderer.cpp new file mode 100644 index 00000000000..cf3a61b7d58 --- /dev/null +++ b/src/core/tiledscene/qgstiledscenerenderer.cpp @@ -0,0 +1,124 @@ +/*************************************************************************** + qgstiledscenerenderer.cpp + -------------------- + begin : August 2023 + copyright : (C) 2023 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 "qgstiledscenerenderer.h" +#include "qgsunittypes.h" +#include "qgsapplication.h" +#include "qgstiledscenerendererregistry.h" + +#include + +// +// QgsTiledSceneRenderContext +// + +QgsTiledSceneRenderContext::QgsTiledSceneRenderContext( QgsRenderContext &context, QgsFeedback *feedback ) + : mRenderContext( context ) + , mFeedback( feedback ) +{ + +} + +// +// QgsTiledSceneRenderer +// + +QgsTiledSceneRenderer *QgsTiledSceneRenderer::load( QDomElement &element, const QgsReadWriteContext &context ) +{ + if ( element.isNull() ) + return nullptr; + + // load renderer + const QString rendererType = element.attribute( QStringLiteral( "type" ) ); + + QgsTiledSceneRendererAbstractMetadata *m = QgsApplication::tiledSceneRendererRegistry()->rendererMetadata( rendererType ); + if ( !m ) + return nullptr; + + std::unique_ptr< QgsTiledSceneRenderer > r( m->createRenderer( element, context ) ); + return r.release(); +} + +void QgsTiledSceneRenderer::startRender( QgsTiledSceneRenderContext & ) +{ +#ifdef QGISDEBUG + if ( !mThread ) + { + mThread = QThread::currentThread(); + } + else + { + Q_ASSERT_X( mThread == QThread::currentThread(), "QgsTiledSceneRenderer::startRender", "startRender called in a different thread - use a cloned renderer instead" ); + } +#endif +} + +void QgsTiledSceneRenderer::stopRender( QgsTiledSceneRenderContext & ) +{ +#ifdef QGISDEBUG + Q_ASSERT_X( mThread == QThread::currentThread(), "QgsTiledSceneRenderer::stopRender", "stopRender called in a different thread - use a cloned renderer instead" ); +#endif +} + +double QgsTiledSceneRenderer::maximumScreenError() const +{ + return mMaximumScreenError; +} + +void QgsTiledSceneRenderer::setMaximumScreenError( double error ) +{ + mMaximumScreenError = error; +} + +Qgis::RenderUnit QgsTiledSceneRenderer::maximumScreenErrorUnit() const +{ + return mMaximumScreenErrorUnit; +} + +void QgsTiledSceneRenderer::setMaximumScreenErrorUnit( Qgis::RenderUnit unit ) +{ + mMaximumScreenErrorUnit = unit; +} + +QList QgsTiledSceneRenderer::createLegendNodes( QgsLayerTreeLayer * ) +{ + return QList(); +} + +QStringList QgsTiledSceneRenderer::legendRuleKeys() const +{ + return QStringList(); +} + +void QgsTiledSceneRenderer::copyCommonProperties( QgsTiledSceneRenderer *destination ) const +{ + destination->setMaximumScreenError( mMaximumScreenError ); + destination->setMaximumScreenErrorUnit( mMaximumScreenErrorUnit ); +} + +void QgsTiledSceneRenderer::restoreCommonProperties( const QDomElement &element, const QgsReadWriteContext & ) +{ + mMaximumScreenError = element.attribute( QStringLiteral( "maximumScreenError" ), QStringLiteral( "0.3" ) ).toDouble(); + mMaximumScreenErrorUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "maximumScreenErrorUnit" ), QStringLiteral( "MM" ) ) ); +} + +void QgsTiledSceneRenderer::saveCommonProperties( QDomElement &element, const QgsReadWriteContext & ) const +{ + + element.setAttribute( QStringLiteral( "maximumScreenError" ), qgsDoubleToString( mMaximumScreenError ) ); + element.setAttribute( QStringLiteral( "maximumScreenErrorUnit" ), QgsUnitTypes::encodeUnit( mMaximumScreenErrorUnit ) ); +} diff --git a/src/core/tiledscene/qgstiledscenerenderer.h b/src/core/tiledscene/qgstiledscenerenderer.h new file mode 100644 index 00000000000..77a2600c386 --- /dev/null +++ b/src/core/tiledscene/qgstiledscenerenderer.h @@ -0,0 +1,251 @@ +/*************************************************************************** + qgstiledscenerenderer.h + -------------------- + begin : August 2023 + copyright : (C) 2023 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 QGSTILEDSCENERENDERER_H +#define QGSTILEDSCENERENDERER_H + +#include "qgsrendercontext.h" + +#include "qgis_core.h" +#include "qgis_sip.h" + +class QgsLayerTreeLayer; +class QgsLayerTreeModelLegendNode; + +/** + * \ingroup core + * \class QgsTiledSceneRenderContext + * + * \brief Encapsulates the render context for a 2D tiled scene rendering operation. + * + * \since QGIS 3.34 + */ +class CORE_EXPORT QgsTiledSceneRenderContext +{ + public: + + /** + * Constructor for QgsTiledSceneRenderContext. + */ + QgsTiledSceneRenderContext( QgsRenderContext &context, QgsFeedback *feedback = nullptr ); + + //! QgsTiledSceneRenderContext cannot be copied. + QgsTiledSceneRenderContext( const QgsTiledSceneRenderContext &rh ) = delete; + + //! QgsTiledSceneRenderContext cannot be copied. + QgsTiledSceneRenderContext &operator=( const QgsTiledSceneRenderContext & ) = delete; + + /** + * Returns a reference to the context's render context. + */ + QgsRenderContext &renderContext() { return mRenderContext; } + + /** + * Returns a reference to the context's render context. + * \note Not available in Python bindings. + */ + const QgsRenderContext &renderContext() const { return mRenderContext; } SIP_SKIP + + /** + * Returns the feedback object used to cancel rendering + * + * \since QGIS 3.20 + */ + QgsFeedback *feedback() const { return mFeedback; } + + private: +#ifdef SIP_RUN + QgsTiledSceneRenderContext( const QgsTiledSceneRenderContext &rh ); +#endif + + QgsRenderContext &mRenderContext; + QgsFeedback *mFeedback = nullptr; +}; + +/** + * \ingroup core + * \class QgsTiledSceneRenderer + * + * \brief Abstract base class for 2d tiled scene renderers. + * + * \since QGIS 3.34 + */ +class CORE_EXPORT QgsTiledSceneRenderer +{ + +#ifdef SIP_RUN + SIP_CONVERT_TO_SUBCLASS_CODE + + const QString type = sipCpp->type(); + + sipType = 0; + SIP_END +#endif + + public: + + /** + * Constructor for QgsTiledSceneRenderer. + */ + QgsTiledSceneRenderer() = default; + + virtual ~QgsTiledSceneRenderer() = default; + + /** + * Returns the identifier of the renderer type. + */ + virtual QString type() const = 0; + + /** + * Create a deep copy of this renderer. Should be implemented by all subclasses + * and generate a proper subclass. + */ + virtual QgsTiledSceneRenderer *clone() const = 0 SIP_FACTORY; + + //! QgsTiledSceneRenderer cannot be copied -- use clone() instead + QgsTiledSceneRenderer( const QgsTiledSceneRenderer &other ) = delete; + + //! QgsTiledSceneRenderer cannot be copied -- use clone() instead + QgsTiledSceneRenderer &operator=( const QgsTiledSceneRenderer &other ) = delete; + + /** + * Creates a renderer from an XML \a element. + * + * Caller takes ownership of the returned renderer. + * + * \see save() + */ + static QgsTiledSceneRenderer *load( QDomElement &element, const QgsReadWriteContext &context ) SIP_FACTORY; + + /** + * Saves the renderer configuration to an XML element. + * \see load() + */ + virtual QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const = 0; + + /** + * Returns the maximum screen error allowed when rendering the tiled scene. + * + * Larger values result in a faster render with less detailed features rendered. + * + * Units are retrieved via maximumScreenErrorUnit(). + * + * \see setMaximumScreenError() + * \see maximumScreenErrorUnit() + */ + double maximumScreenError() const; + + /** + * Sets the maximum screen \a error allowed when rendering the tiled scene. + * + * Larger values result in a faster render with less detailed features rendered. + * + * Units are set via setMaximumScreenErrorUnit(). + * + * \see maximumScreenError() + * \see setMaximumScreenErrorUnit() + */ + void setMaximumScreenError( double error ); + + /** + * Returns the unit for the maximum screen error allowed when rendering the tiled scene. + * + * \see maximumScreenError() + * \see setMaximumScreenErrorUnit() + */ + Qgis::RenderUnit maximumScreenErrorUnit() const; + + /** + * Sets the \a unit for the maximum screen error allowed when rendering the tiled scene. + * + * \see setMaximumScreenError() + * \see maximumScreenErrorUnit() + */ + void setMaximumScreenErrorUnit( Qgis::RenderUnit unit ); + + /** + * Must be called when a new render cycle is started. A call to startRender() must always + * be followed by a corresponding call to stopRender() after all features have been rendered. + * + * \see stopRender() + * + * \warning This method is not thread safe. Before calling startRender() in a non-main thread, + * the renderer should instead be cloned and startRender()/stopRender() called on the clone. + */ + virtual void startRender( QgsTiledSceneRenderContext &context ); + + /** + * Must be called when a render cycle has finished, to allow the renderer to clean up. + * + * Calls to stopRender() must always be preceded by a call to startRender(). + * + * \warning This method is not thread safe. Before calling startRender() in a non-main thread, + * the renderer should instead be cloned and startRender()/stopRender() called on the clone. + * + * \see startRender() + */ + virtual void stopRender( QgsTiledSceneRenderContext &context ); + + /** + * Creates a set of legend nodes representing the renderer. + */ + virtual QList createLegendNodes( QgsLayerTreeLayer *nodeLayer ) SIP_FACTORY; + + /** + * Returns a list of all rule keys for legend nodes created by the renderer. + */ + virtual QStringList legendRuleKeys() const; + + protected: + + /** + * Copies common tiled scene renderer properties (such as screen error) to the \a destination renderer. + */ + void copyCommonProperties( QgsTiledSceneRenderer *destination ) const; + + /** + * Restores common renderer properties (such as screen error) from the + * specified DOM \a element. + * + * \see saveCommonProperties() + */ + void restoreCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ); + + /** + * Saves common renderer properties (such as point size and screen error) to the + * specified DOM \a element. + * + * \see restoreCommonProperties() + */ + void saveCommonProperties( QDomElement &element, const QgsReadWriteContext &context ) const; + + private: +#ifdef SIP_RUN + QgsTiledSceneRenderer( const QgsTiledSceneRenderer &other ); +#endif + +#ifdef QGISDEBUG + //! Pointer to thread in which startRender was first called + QThread *mThread = nullptr; +#endif + + double mMaximumScreenError = 0.3; + Qgis::RenderUnit mMaximumScreenErrorUnit = Qgis::RenderUnit::Millimeters; + +}; + +#endif // QGSTILEDSCENERENDERER_H diff --git a/src/core/tiledscene/qgstiledscenerendererregistry.cpp b/src/core/tiledscene/qgstiledscenerendererregistry.cpp new file mode 100644 index 00000000000..c270c35b56e --- /dev/null +++ b/src/core/tiledscene/qgstiledscenerendererregistry.cpp @@ -0,0 +1,70 @@ +/*************************************************************************** + qgstiledscenerendererregistry.cpp + --------------------- + begin : August 2023 + copyright : (C) 2023 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 "qgstiledscenerendererregistry.h" +#include "qgstiledscenerenderer.h" + +QgsTiledSceneRendererRegistry::QgsTiledSceneRendererRegistry() +{ + // add default renderers +} + +QgsTiledSceneRendererRegistry::~QgsTiledSceneRendererRegistry() +{ + qDeleteAll( mRenderers ); +} + +bool QgsTiledSceneRendererRegistry::addRenderer( QgsTiledSceneRendererAbstractMetadata *metadata ) +{ + if ( !metadata || mRenderers.contains( metadata->name() ) ) + return false; + + mRenderers[metadata->name()] = metadata; + mRenderersOrder << metadata->name(); + return true; +} + +bool QgsTiledSceneRendererRegistry::removeRenderer( const QString &rendererName ) +{ + if ( !mRenderers.contains( rendererName ) ) + return false; + + delete mRenderers[rendererName]; + mRenderers.remove( rendererName ); + mRenderersOrder.removeAll( rendererName ); + return true; +} + +QgsTiledSceneRendererAbstractMetadata *QgsTiledSceneRendererRegistry::rendererMetadata( const QString &rendererName ) +{ + return mRenderers.value( rendererName ); +} + +QStringList QgsTiledSceneRendererRegistry::renderersList() const +{ + QStringList renderers; + for ( const QString &renderer : mRenderersOrder ) + { + QgsTiledSceneRendererAbstractMetadata *r = mRenderers.value( renderer ); + if ( r ) + renderers << renderer; + } + return renderers; +} + +QgsTiledSceneRenderer *QgsTiledSceneRendererRegistry::defaultRenderer( const QgsTiledSceneLayer * ) +{ + return nullptr; +} + diff --git a/src/core/tiledscene/qgstiledscenerendererregistry.h b/src/core/tiledscene/qgstiledscenerendererregistry.h new file mode 100644 index 00000000000..04724054f1e --- /dev/null +++ b/src/core/tiledscene/qgstiledscenerendererregistry.h @@ -0,0 +1,239 @@ +/*************************************************************************** + qgstiledscenerendererregistry.h + --------------------- + begin : August 2023 + copyright : (C) 2023 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 QGSTILEDSCENERENDERERREGISTRY_H +#define QGSTILEDSCENERENDERERREGISTRY_H + +#include "qgis_core.h" +#include "qgis_sip.h" +#include +#include +#include +#include + +class QgsTiledSceneRenderer; +class QgsReadWriteContext; +class QgsTiledSceneLayer; +class QgsStyle; +#ifndef SIP_RUN +class QgsTiledSceneRendererWidget SIP_EXTERNAL; +#endif + + +/** + * \ingroup core + * \brief Stores metadata about one tiled scene renderer class. + * + * \note It's necessary to implement createRenderer() function. + * In C++ you can use QgsTiledSceneRendererMetadata convenience class. + * + * \since QGIS 3.34 + */ +class CORE_EXPORT QgsTiledSceneRendererAbstractMetadata +{ + public: + + /** + * Constructor for QgsTiledSceneRendererAbstractMetadata, with the specified \a name. + * + * The \a visibleName argument gives a translated, user friendly string identifying the renderer type. + * + * The \a icon argument can be used to specify an icon representing the renderer. + */ + QgsTiledSceneRendererAbstractMetadata( const QString &name, const QString &visibleName, const QIcon &icon = QIcon() ) + : mName( name ) + , mVisibleName( visibleName ) + , mIcon( icon ) + {} + virtual ~QgsTiledSceneRendererAbstractMetadata() = default; + + /** + * Returns the unique name of the renderer. This value is not translated. + * \see visibleName() + */ + QString name() const { return mName; } + + /** + * Returns a friendly display name of the renderer. This value is translated. + * \see name() + */ + QString visibleName() const { return mVisibleName; } + + /** + * Returns an icon representing the renderer. + * \see setIcon() + */ + QIcon icon() const { return mIcon; } + + /** + * Sets an \a icon representing the renderer. + * \see icon() + */ + void setIcon( const QIcon &icon ) { mIcon = icon; } + + /** + * Returns new instance of the renderer given the DOM element. Returns NULLPTR on error. + * Pure virtual function: must be implemented in derived classes. + */ + virtual QgsTiledSceneRenderer *createRenderer( QDomElement &elem, const QgsReadWriteContext &context ) = 0 SIP_FACTORY; + +#ifndef SIP_RUN + + /** + * Returns new instance of settings widget for the renderer. Returns NULLPTR on error. + * + * The \a oldRenderer argument may refer to previously used renderer (or it is NULLPTR). + * If not NULLPTR, it may be used to initialize GUI of the widget from the previous settings. + * The old renderer does not have to be of the same type as returned by createRenderer(). + * + * \note Not available in Python bindings + */ + virtual QgsTiledSceneRendererWidget *createRendererWidget( QgsTiledSceneLayer *layer, QgsStyle *style, QgsTiledSceneRenderer *oldRenderer ) SIP_FACTORY + { Q_UNUSED( layer ) Q_UNUSED( style ); Q_UNUSED( oldRenderer ); return nullptr; } +#endif + + protected: + //! name used within QGIS for identification (the same what renderer's type() returns) + QString mName; + //! name visible for users (translatable) + QString mVisibleName; + //! icon to be shown in the renderer properties dialog + QIcon mIcon; +}; + +typedef QgsTiledSceneRenderer *( *QgsTiledSceneRendererCreateFunc )( QDomElement &, const QgsReadWriteContext & ) SIP_SKIP; +typedef QgsTiledSceneRendererWidget *( *QgsTiledSceneRendererWidgetFunc )( QgsTiledSceneLayer *, QgsStyle *, QgsTiledSceneRenderer * ) SIP_SKIP; + +/** + * \ingroup core + * \brief Convenience metadata class that uses static functions to create tiled scene renderer and its widget. + * \since QGIS 3.34 + */ +class CORE_EXPORT QgsTiledSceneRendererMetadata : public QgsTiledSceneRendererAbstractMetadata +{ + public: + + /** + * Construct metadata + * \note not available in Python bindings + */ + QgsTiledSceneRendererMetadata( const QString &name, + const QString &visibleName, + QgsTiledSceneRendererCreateFunc pfCreate, + const QIcon &icon = QIcon(), + QgsTiledSceneRendererWidgetFunc pfWidget = nullptr ) SIP_SKIP + : QgsTiledSceneRendererAbstractMetadata( name, visibleName, icon ) + , mCreateFunc( pfCreate ) + , mWidgetFunc( pfWidget ) + {} + + QgsTiledSceneRenderer *createRenderer( QDomElement &elem, const QgsReadWriteContext &context ) override SIP_FACTORY + { return mCreateFunc ? mCreateFunc( elem, context ) : nullptr; } + +#ifndef SIP_RUN + QgsTiledSceneRendererWidget *createRendererWidget( QgsTiledSceneLayer *layer, QgsStyle *style, QgsTiledSceneRenderer *renderer ) override SIP_FACTORY + { return mWidgetFunc ? mWidgetFunc( layer, style, renderer ) : nullptr; } +#endif + + //! \note not available in Python bindings + QgsTiledSceneRendererCreateFunc createFunction() const { return mCreateFunc; } SIP_SKIP + //! \note not available in Python bindings + QgsTiledSceneRendererWidgetFunc widgetFunction() const { return mWidgetFunc; } SIP_SKIP + + //! \note not available in Python bindings + void setWidgetFunction( QgsTiledSceneRendererWidgetFunc f ) { mWidgetFunc = f; } SIP_SKIP + + protected: + //! pointer to function that creates an instance of the renderer when loading project / style + QgsTiledSceneRendererCreateFunc mCreateFunc; + //! pointer to function that creates a widget for configuration of renderer's params + QgsTiledSceneRendererWidgetFunc mWidgetFunc; + + private: +#ifdef SIP_RUN + QgsTiledSceneRendererMetadata(); +#endif + +}; + + +/** + * \ingroup core + * \class QgsTiledSceneRendererRegistry + * \brief Registry of 2D renderers for tiled scenes. + * + * QgsTiledSceneRendererRegistry is not usually directly created, but rather accessed through + * QgsApplication::tiledSceneRendererRegistry(). + * + * \since QGIS 3.34 + */ +class CORE_EXPORT QgsTiledSceneRendererRegistry +{ + public: + + QgsTiledSceneRendererRegistry(); + ~QgsTiledSceneRendererRegistry(); + + //! QgsTiledSceneRendererRegistry cannot be copied. + QgsTiledSceneRendererRegistry( const QgsTiledSceneRendererRegistry &rh ) = delete; + //! QgsTiledSceneRendererRegistry cannot be copied. + QgsTiledSceneRendererRegistry &operator=( const QgsTiledSceneRendererRegistry &rh ) = delete; + + /** + * Adds a renderer to the registry. Takes ownership of the metadata object. + * \param metadata renderer metadata + * \returns TRUE if renderer was added successfully, or FALSE if renderer could not + * be added (e.g., a renderer with a duplicate name already exists) + */ + bool addRenderer( QgsTiledSceneRendererAbstractMetadata *metadata SIP_TRANSFER ); + + /** + * Removes a renderer from registry. + * \param rendererName name of renderer to remove from registry + * \returns TRUE if renderer was successfully removed, or FALSE if matching + * renderer could not be found + */ + bool removeRenderer( const QString &rendererName ); + + /** + * Returns the metadata for a specified renderer. Returns NULLPTR if a matching + * renderer was not found in the registry. + */ + QgsTiledSceneRendererAbstractMetadata *rendererMetadata( const QString &rendererName ); + + /** + * Returns a list of available renderers. + */ + QStringList renderersList() const; + + /** + * Returns a new default tiled scene renderer for a specified \a layer. + * + * Caller takes ownership of the returned renderer. + */ + static QgsTiledSceneRenderer *defaultRenderer( const QgsTiledSceneLayer *layer ) SIP_FACTORY; + + private: +#ifdef SIP_RUN + QgsTiledSceneRendererRegistry( const QgsTiledSceneRendererRegistry &rh ); +#endif + + //! Map of name to renderer + QMap mRenderers; + + //! List of renderers, maintained in the order that they have been added + QStringList mRenderersOrder; +}; + +#endif // QGSTILEDSCENERENDERERREGISTRY_H diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index 6c84425284f..3848a7320d8 100644 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -194,6 +194,7 @@ set(TESTS testqgstemporalproperty.cpp testqgstemporalrangeobject.cpp testqgstiledsceneconnection.cpp + testqgstiledscenerendererregistry.cpp testqgstiledownloadmanager.cpp testqgstracer.cpp testqgstranslateproject.cpp diff --git a/tests/src/core/testqgstiledscenerendererregistry.cpp b/tests/src/core/testqgstiledscenerendererregistry.cpp new file mode 100644 index 00000000000..ae8f21807f8 --- /dev/null +++ b/tests/src/core/testqgstiledscenerendererregistry.cpp @@ -0,0 +1,139 @@ +/*************************************************************************** + testqgstiledscenerendererregistry.cpp + ----------------------- + begin : August 2023 + copyright : (C) 2023 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 "qgstiledscenerendererregistry.h" +#include "qgstiledscenerenderer.h" +#include "qgsreadwritecontext.h" + +#include +#include "qgstest.h" + +//dummy renderer for testing +class DummyRenderer : public QgsTiledSceneRenderer +{ + public: + DummyRenderer() = default; + QString type() const override { return QStringLiteral( "dummy" ); } + QgsTiledSceneRenderer *clone() const override { return new DummyRenderer(); } + static QgsTiledSceneRenderer *create( QDomElement &, const QgsReadWriteContext & ) { return new DummyRenderer(); } + QDomElement save( QDomDocument &doc, const QgsReadWriteContext & ) const override { return doc.createElement( QStringLiteral( "test" ) ); } + +}; + +class TestQgsTiledSceneRendererRegistry : public QObject +{ + Q_OBJECT + + private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + void metadata(); + void createInstance(); + void instanceHasDefaultRenderers(); + void addRenderer(); + void fetchTypes(); + + private: + +}; + +void TestQgsTiledSceneRendererRegistry::initTestCase() +{ + QgsApplication::init(); // init paths for CRS lookup + QgsApplication::initQgis(); +} + +void TestQgsTiledSceneRendererRegistry::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgsTiledSceneRendererRegistry::init() +{ + +} + +void TestQgsTiledSceneRendererRegistry::cleanup() +{ + +} + +void TestQgsTiledSceneRendererRegistry::metadata() +{ + QgsTiledSceneRendererMetadata metadata = QgsTiledSceneRendererMetadata( QStringLiteral( "name" ), QStringLiteral( "display name" ), DummyRenderer::create, QIcon() ); + QCOMPARE( metadata.name(), QString( "name" ) ); + QCOMPARE( metadata.visibleName(), QString( "display name" ) ); + + //test creating renderer from metadata + QDomElement elem; + const std::unique_ptr< QgsTiledSceneRenderer > renderer( metadata.createRenderer( elem, QgsReadWriteContext() ) ); + QVERIFY( renderer ); + DummyRenderer *dummyRenderer = dynamic_cast( renderer.get() ); + QVERIFY( dummyRenderer ); +} + +void TestQgsTiledSceneRendererRegistry::createInstance() +{ + QgsTiledSceneRendererRegistry *registry = QgsApplication::tiledSceneRendererRegistry(); + QVERIFY( registry ); +} + +void TestQgsTiledSceneRendererRegistry::instanceHasDefaultRenderers() +{ + //check that callout registry is initially populated with some renderers + //(assumes that there is some default renderers) + QgsTiledSceneRendererRegistry *registry = QgsApplication::tiledSceneRendererRegistry(); + QVERIFY( registry->renderersList().length() > 0 ); +} + +void TestQgsTiledSceneRendererRegistry::addRenderer() +{ + QgsTiledSceneRendererRegistry *registry = QgsApplication::tiledSceneRendererRegistry(); + const int previousCount = registry->renderersList().length(); + + registry->addRenderer( new QgsTiledSceneRendererMetadata( QStringLiteral( "Dummy" ), QStringLiteral( "Dummy renderer" ), DummyRenderer::create, QIcon() ) ); + QCOMPARE( registry->renderersList().length(), previousCount + 1 ); + //try adding again, should have no effect + QgsTiledSceneRendererMetadata *dupe = new QgsTiledSceneRendererMetadata( QStringLiteral( "Dummy" ), QStringLiteral( "Dummy callout" ), DummyRenderer::create, QIcon() ); + QVERIFY( ! registry->addRenderer( dupe ) ); + QCOMPARE( registry->renderersList().length(), previousCount + 1 ); + delete dupe; + + //try adding empty metadata + registry->addRenderer( nullptr ); + QCOMPARE( registry->renderersList().length(), previousCount + 1 ); +} + +void TestQgsTiledSceneRendererRegistry::fetchTypes() +{ + QgsTiledSceneRendererRegistry *registry = QgsApplication::tiledSceneRendererRegistry(); + const QStringList types = registry->renderersList(); + + QVERIFY( types.contains( "Dummy" ) ); + + QgsTiledSceneRendererAbstractMetadata *metadata = registry->rendererMetadata( QStringLiteral( "Dummy" ) ); + QCOMPARE( metadata->name(), QString( "Dummy" ) ); + + //metadata for bad renderer + metadata = registry->rendererMetadata( QStringLiteral( "bad renderer" ) ); + QVERIFY( !metadata ); +} + +QGSTEST_MAIN( TestQgsTiledSceneRendererRegistry ) +#include "testqgstiledscenerendererregistry.moc"