Virtual point clouds - Tile Labels (#59726)

* Add label rendering into VPC tiles

* Move tile labels rendering into QgsPointCloudExtentRenderer

* Rework showLabels flag once more & connect it to widget

* Add widget for label TextFormat WIP

* Rework and fix text format widget issues

* Improve default text format used in VPC labels

* Fix banned keywords issue

* Fix QgsTextFormat review issue

* Fix labels drawing efficiency issue

* Fix formating issues

* Fix review issues
This commit is contained in:
Matej Bagar 2024-12-11 09:11:44 +00:00 committed by GitHub
parent 62034dfe30
commit 22fe235648
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 504 additions and 288 deletions

View File

@ -77,6 +77,20 @@ Sets the ``symbol`` used to render the cloud's extent.
Ownership of ``symbol`` is transferred to the renderer.
.. seealso:: :py:func:`fillSymbol`
%End
void renderLabel( const QRectF &extent, const QString &text, QgsPointCloudRenderContext &context ) const;
%Docstring
Renders a label inside the specified extent rectangle.
The label will be rendered centered horizontally and vertically inside ``extent``. If the label is too large to fit
inside this rectangle, it will not be rendered.
:param extent: rectangle (in painter coordinates) inside which the label will be rendered
:param text: label text to render
:param context: point cloud rendering context
.. versionadded:: 3.42
%End
};

View File

@ -547,6 +547,35 @@ Creates a set of legend nodes representing the renderer.
virtual QStringList legendRuleKeys() const;
%Docstring
Returns a list of all rule keys for legend nodes created by the renderer.
%End
void setShowLabels( const bool show );
%Docstring
Set whether the renderer should also render file labels inside extent
.. versionadded:: 3.42
%End
bool showLabels() const;
%Docstring
Returns whether the renderer shows file labels inside the extent
rectangle
.. versionadded:: 3.42
%End
void setLabelTextFormat( const QgsTextFormat &textFormat );
%Docstring
Sets the text format renderers should use for rendering labels
.. versionadded:: 3.42
%End
QgsTextFormat labelTextFormat() const;
%Docstring
Returns the text format renderer is using for rendering labels
.. versionadded:: 3.42
%End
protected:

View File

@ -77,6 +77,20 @@ Sets the ``symbol`` used to render the cloud's extent.
Ownership of ``symbol`` is transferred to the renderer.
.. seealso:: :py:func:`fillSymbol`
%End
void renderLabel( const QRectF &extent, const QString &text, QgsPointCloudRenderContext &context ) const;
%Docstring
Renders a label inside the specified extent rectangle.
The label will be rendered centered horizontally and vertically inside ``extent``. If the label is too large to fit
inside this rectangle, it will not be rendered.
:param extent: rectangle (in painter coordinates) inside which the label will be rendered
:param text: label text to render
:param context: point cloud rendering context
.. versionadded:: 3.42
%End
};

View File

@ -547,6 +547,35 @@ Creates a set of legend nodes representing the renderer.
virtual QStringList legendRuleKeys() const;
%Docstring
Returns a list of all rule keys for legend nodes created by the renderer.
%End
void setShowLabels( const bool show );
%Docstring
Set whether the renderer should also render file labels inside extent
.. versionadded:: 3.42
%End
bool showLabels() const;
%Docstring
Returns whether the renderer shows file labels inside the extent
rectangle
.. versionadded:: 3.42
%End
void setLabelTextFormat( const QgsTextFormat &textFormat );
%Docstring
Sets the text format renderers should use for rendering labels
.. versionadded:: 3.42
%End
QgsTextFormat labelTextFormat() const;
%Docstring
Returns the text format renderer is using for rendering labels
.. versionadded:: 3.42
%End
protected:

View File

@ -16,14 +16,18 @@
***************************************************************************/
#include "qgspointcloudextentrenderer.h"
#include "qgspointcloudblock.h"
#include "qgssymbollayerutils.h"
#include "qgssymbol.h"
#include "qgspolygon.h"
#include "qgscurve.h"
#include "qgslinesymbollayer.h"
#include "qgslayertreemodellegendnode.h"
#include "qgsfillsymbol.h"
#include "qgslayertreemodellegendnode.h"
#include "qgslinesymbollayer.h"
#include "qgspointcloudblock.h"
#include "qgspolygon.h"
#include "qgsstyle.h"
#include "qgssymbol.h"
#include "qgssymbollayerutils.h"
#include "qgstextdocument.h"
#include "qgstextdocumentmetrics.h"
#include "qgstextrenderer.h"
QgsPointCloudExtentRenderer::QgsPointCloudExtentRenderer( QgsFillSymbol *symbol )
: mFillSymbol( symbol ? symbol : defaultFillSymbol() )
@ -134,6 +138,16 @@ void QgsPointCloudExtentRenderer::setFillSymbol( QgsFillSymbol *symbol )
{
mFillSymbol.reset( symbol );
}
void QgsPointCloudExtentRenderer::renderLabel( const QRectF &extent, const QString &text, QgsPointCloudRenderContext &context ) const
{
const QgsTextDocument doc = QgsTextDocument::fromTextAndFormat( {text}, labelTextFormat() );
const QgsTextDocumentMetrics metrics = QgsTextDocumentMetrics::calculateMetrics( doc, labelTextFormat(), context.renderContext() );
const QSizeF textSize = metrics.documentSize( Qgis::TextLayoutMode::Rectangle, labelTextFormat().orientation() );
if ( textSize.width() < extent.width() && textSize.height() < extent.height() )
{
QgsTextRenderer::drawDocument( extent, labelTextFormat(), metrics.document(), metrics, context.renderContext(), Qgis::TextHorizontalAlignment::Center, Qgis::TextVerticalAlignment::VerticalCenter );
}
}
QDomElement QgsPointCloudExtentRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
{

View File

@ -83,6 +83,19 @@ class CORE_EXPORT QgsPointCloudExtentRenderer : public QgsPointCloudRenderer
*/
void setFillSymbol( QgsFillSymbol *symbol SIP_TRANSFER );
/**
* Renders a label inside the specified extent rectangle.
*
* The label will be rendered centered horizontally and vertically inside \a extent. If the label is too large to fit
* inside this rectangle, it will not be rendered.
*
* \param extent rectangle (in painter coordinates) inside which the label will be rendered
* \param text label text to render
* \param context point cloud rendering context
* \since QGIS 3.42
*/
void renderLabel( const QRectF &extent, const QString &text, QgsPointCloudRenderContext &context ) const;
private:
std::unique_ptr< QgsFillSymbol > mFillSymbol;

View File

@ -18,24 +18,24 @@
#include <QElapsedTimer>
#include <QPointer>
#include "qgspointcloudlayerrenderer.h"
#include "qgspointcloudlayer.h"
#include "qgsrendercontext.h"
#include "qgspointcloudindex.h"
#include "qgsapplication.h"
#include "qgscolorramp.h"
#include "qgselevationmap.h"
#include "qgsmeshlayerutils.h"
#include "qgspointcloudrequest.h"
#include "qgspointcloudattribute.h"
#include "qgspointcloudrenderer.h"
#include "qgspointcloudextentrenderer.h"
#include "qgslogger.h"
#include "qgspointcloudlayerelevationproperties.h"
#include "qgsmessagelog.h"
#include "qgsmapclippingutils.h"
#include "qgsmeshlayerutils.h"
#include "qgsmessagelog.h"
#include "qgspointcloudattribute.h"
#include "qgspointcloudblockrequest.h"
#include "qgspointcloudextentrenderer.h"
#include "qgspointcloudindex.h"
#include "qgspointcloudlayer.h"
#include "qgspointcloudlayerelevationproperties.h"
#include "qgspointcloudlayerrenderer.h"
#include "qgspointcloudrenderer.h"
#include "qgspointcloudrequest.h"
#include "qgsrendercontext.h"
#include "qgsruntimeprofiler.h"
#include "qgsapplication.h"
#include <delaunator.hpp>
@ -59,7 +59,11 @@ QgsPointCloudLayerRenderer::QgsPointCloudLayerRenderer( QgsPointCloudLayer *laye
mRenderer.reset( mLayer->renderer()->clone() );
if ( !mSubIndexes.isEmpty() )
{
mSubIndexExtentRenderer.reset( new QgsPointCloudExtentRenderer() );
mSubIndexExtentRenderer->setShowLabels( mRenderer->showLabels() );
mSubIndexExtentRenderer->setLabelTextFormat( mRenderer->labelTextFormat() );
}
if ( mLayer->dataProvider()->index() )
{
@ -211,6 +215,14 @@ bool QgsPointCloudLayerRenderer::render()
// when dealing with virtual point clouds, we want to render the individual extents when zoomed out
// and only use the selected renderer when zoomed in
mSubIndexExtentRenderer->renderExtent( si.polygonBounds(), context );
// render the label of point cloud tile
if ( mSubIndexExtentRenderer->showLabels() )
{
mSubIndexExtentRenderer->renderLabel(
context.renderContext().mapToPixel().transformBounds( si.extent().toRectF() ),
si.uri().section( "/", -1 ).section( ".", 0, 0 ),
context );
}
}
else
{

View File

@ -61,6 +61,16 @@ void QgsPointCloudRenderContext::setAttributes( const QgsPointCloudAttributeColl
attributes.find( QStringLiteral( "Z" ), mZOffset );
}
QgsPointCloudRenderer::QgsPointCloudRenderer()
{
QgsTextFormat textFormat = QgsStyle::defaultStyle()->defaultTextFormat();
QgsTextBufferSettings settings;
settings.setEnabled( true );
settings.setSize( 1 );
textFormat.setBuffer( settings );
mLabelTextFormat = ( textFormat );
}
QgsPointCloudRenderer *QgsPointCloudRenderer::load( QDomElement &element, const QgsReadWriteContext &context )
{
if ( element.isNull() )
@ -205,9 +215,12 @@ void QgsPointCloudRenderer::copyCommonProperties( QgsPointCloudRenderer *destina
destination->setHorizontalTriangleFilter( mHorizontalTriangleFilter );
destination->setHorizontalTriangleFilterThreshold( mHorizontalTriangleFilterThreshold );
destination->setHorizontalTriangleFilterUnit( mHorizontalTriangleFilterUnit );
destination->setShowLabels( mShowLabels );
destination->setLabelTextFormat( mLabelTextFormat );
}
void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element, const QgsReadWriteContext & )
void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element, const QgsReadWriteContext &context )
{
mPointSize = element.attribute( QStringLiteral( "pointSize" ), QStringLiteral( "1" ) ).toDouble();
mPointSizeUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "pointSizeUnit" ), QStringLiteral( "MM" ) ) );
@ -222,9 +235,16 @@ void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element,
mHorizontalTriangleFilter = element.attribute( QStringLiteral( "horizontalTriangleFilter" ), QStringLiteral( "0" ) ).toInt();
mHorizontalTriangleFilterThreshold = element.attribute( QStringLiteral( "horizontalTriangleFilterThreshold" ), QStringLiteral( "5" ) ).toDouble();
mHorizontalTriangleFilterUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "horizontalTriangleFilterUnit" ), QStringLiteral( "MM" ) ) );
mShowLabels = element.attribute( QStringLiteral( "showLabels" ), QStringLiteral( "0" ) ).toInt();
if ( !element.firstChildElement( QStringLiteral( "text-style" ) ).isNull() )
{
mLabelTextFormat = QgsTextFormat();
mLabelTextFormat.readXml( element.firstChildElement( QStringLiteral( "text-style" ) ), context );
}
}
void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const QgsReadWriteContext & ) const
void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const QgsReadWriteContext &context ) const
{
element.setAttribute( QStringLiteral( "pointSize" ), qgsDoubleToString( mPointSize ) );
element.setAttribute( QStringLiteral( "pointSizeUnit" ), QgsUnitTypes::encodeUnit( mPointSizeUnit ) );
@ -239,6 +259,13 @@ void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const Qg
element.setAttribute( QStringLiteral( "horizontalTriangleFilter" ), QString::number( static_cast< int >( mHorizontalTriangleFilter ) ) );
element.setAttribute( QStringLiteral( "horizontalTriangleFilterThreshold" ), qgsDoubleToString( mHorizontalTriangleFilterThreshold ) );
element.setAttribute( QStringLiteral( "horizontalTriangleFilterUnit" ), QgsUnitTypes::encodeUnit( mHorizontalTriangleFilterUnit ) );
element.setAttribute( QStringLiteral( "showLabels" ), QString::number( mShowLabels ) );
if ( mLabelTextFormat.isValid() )
{
QDomDocument doc = element.ownerDocument();
element.appendChild( mLabelTextFormat.writeXml( doc, context ) );
}
}
Qgis::PointCloudSymbol QgsPointCloudRenderer::pointSymbol() const

View File

@ -24,6 +24,7 @@
#include "qgis_sip.h"
#include "qgsvector3d.h"
#include "qgspointcloudattribute.h"
#include "qgsstyle.h"
class QgsPointCloudBlock;
class QgsLayerTreeLayer;
@ -340,7 +341,7 @@ class CORE_EXPORT QgsPointCloudRenderer
public:
QgsPointCloudRenderer() = default;
QgsPointCloudRenderer();
virtual ~QgsPointCloudRenderer() = default;
@ -675,6 +676,31 @@ class CORE_EXPORT QgsPointCloudRenderer
*/
virtual QStringList legendRuleKeys() const;
/**
* Set whether the renderer should also render file labels inside extent
* \since QGIS 3.42
*/
void setShowLabels( const bool show ) { mShowLabels = show; }
/**
* Returns whether the renderer shows file labels inside the extent
* rectangle
* \since QGIS 3.42
*/
bool showLabels() const { return mShowLabels; }
/**
* Sets the text format renderers should use for rendering labels
* \since QGIS 3.42
*/
void setLabelTextFormat( const QgsTextFormat &textFormat ) { mLabelTextFormat = textFormat; }
/**
* Returns the text format renderer is using for rendering labels
* \since QGIS 3.42
*/
QgsTextFormat labelTextFormat() const { return mLabelTextFormat; }
protected:
/**
@ -813,6 +839,9 @@ class CORE_EXPORT QgsPointCloudRenderer
bool mHorizontalTriangleFilter = false;
double mHorizontalTriangleFilterThreshold = 5.0;
Qgis::RenderUnit mHorizontalTriangleFilterUnit = Qgis::RenderUnit::Millimeters;
bool mShowLabels = false;
QgsTextFormat mLabelTextFormat;
};
#endif // QGSPOINTCLOUDRENDERER_H

View File

@ -16,19 +16,23 @@
#include "moc_qgspointcloudrendererpropertieswidget.cpp"
#include "qgis.h"
#include "qgspointcloudrendererregistry.h"
#include "qgsapplication.h"
#include "qgssymbolwidgetcontext.h"
#include "qgspointcloudrendererwidget.h"
#include "qgspointcloudlayer.h"
#include "qgspointcloudrenderer.h"
#include "qgspointcloudrgbrendererwidget.h"
#include "qgsfontbutton.h"
#include "qgslogger.h"
#include "qgspointcloudattributebyramprendererwidget.h"
#include "qgspointcloudclassifiedrendererwidget.h"
#include "qgspointcloudextentrenderer.h"
#include "qgspointcloudextentrendererwidget.h"
#include "qgslogger.h"
#include "qgspointcloudlayer.h"
#include "qgspointcloudrenderer.h"
#include "qgspointcloudrendererregistry.h"
#include "qgspointcloudrendererwidget.h"
#include "qgspointcloudrgbrendererwidget.h"
#include "qgsproject.h"
#include "qgsprojectutils.h"
#include "qgsstyle.h"
#include "qgssymbolwidgetcontext.h"
#include "qgstextformatwidget.h"
static bool initPointCloudRenderer( const QString &name, QgsPointCloudRendererWidgetFunc f, const QString &iconName = QString() )
{
@ -120,6 +124,14 @@ QgsPointCloudRendererPropertiesWidget::QgsPointCloudRendererPropertiesWidget( Qg
connect( mHorizontalTriangleThresholdSpinBox, qOverload<double>( &QgsDoubleSpinBox::valueChanged ), this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged );
connect( mHorizontalTriangleUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged );
// show label options only for virtual point clouds
bool showLabelOptions = !mLayer->dataProvider()->subIndexes().isEmpty();
mLabels->setVisible( showLabelOptions );
mLabelOptions->setVisible( showLabelOptions );
mLabelOptions->setDialogTitle( tr( "Customize label text" ) );
connect( mLabels, &QCheckBox::stateChanged, this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged );
connect( mLabelOptions, &QgsFontButton::changed, this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged );
syncToLayer( layer );
}
@ -174,6 +186,12 @@ void QgsPointCloudRendererPropertiesWidget::syncToLayer( QgsMapLayer *layer )
mHorizontalTriangleCheckBox->setChecked( mLayer->renderer()->horizontalTriangleFilter() );
mHorizontalTriangleThresholdSpinBox->setValue( mLayer->renderer()->horizontalTriangleFilterThreshold() );
mHorizontalTriangleUnitWidget->setUnit( mLayer->renderer()->horizontalTriangleFilterUnit() );
if ( !mLayer->dataProvider()->subIndexes().isEmpty() )
{
mLabels->setChecked( mLayer->renderer()->showLabels() );
mLabelOptions->setTextFormat( mLayer->renderer()->labelTextFormat() );
}
}
mBlockChangedSignal = false;
@ -213,6 +231,9 @@ void QgsPointCloudRendererPropertiesWidget::apply()
mLayer->renderer()->setHorizontalTriangleFilter( mHorizontalTriangleCheckBox->isChecked() );
mLayer->renderer()->setHorizontalTriangleFilterThreshold( mHorizontalTriangleThresholdSpinBox->value() );
mLayer->renderer()->setHorizontalTriangleFilterUnit( mHorizontalTriangleUnitWidget->unit() );
mLayer->renderer()->setShowLabels( mLabels->isChecked() );
mLayer->renderer()->setLabelTextFormat( mLabelOptions->textFormat() );
}
void QgsPointCloudRendererPropertiesWidget::rendererChanged()

View File

@ -13,37 +13,11 @@
<property name="windowTitle">
<string>Renderer Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QStackedWidget" name="mainStack">
<widget class="QWidget" name="page">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
@ -297,6 +271,41 @@
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="labelLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<item>
<widget class="QCheckBox" name="mLabels">
<property name="text">
<string>Show tile labels</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QgsFontButton" name="mLabelOptions">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@ -340,6 +349,11 @@
<header>qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsFontButton</class>
<extends>QToolButton</extends>
<header>qgsfontbutton.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>cboRenderers</tabstop>