initial support for mesh labeling

This commit is contained in:
Alexander Bruy 2023-11-14 11:57:39 +02:00 committed by Martin Dobias
parent 452c344b1e
commit 9ded559386
14 changed files with 1149 additions and 0 deletions

View File

@ -877,6 +877,57 @@ Returns the edges count of the mesh frame
.. versionadded:: 3.22
%End
bool labelsEnabled() const;
%Docstring
Returns whether the layer contains labels which are enabled and should be drawn.
:return: ``True`` if layer contains enabled labels
.. seealso:: :py:func:`setLabelsEnabled`
.. seealso:: :py:func:`labeling`
.. versionadded:: 3.36
%End
void setLabelsEnabled( bool enabled );
%Docstring
Sets whether labels should be ``enabled`` for the layer.
.. note::
Labels will only be rendered if :py:func:`~QgsMeshLayer.labelsEnabled` is ``True`` and a labeling
object is returned by :py:func:`~QgsMeshLayer.labeling`.
.. seealso:: :py:func:`labelsEnabled`
.. seealso:: :py:func:`labeling`
.. versionadded:: 3.36
%End
QgsAbstractMeshLayerLabeling *labeling();
%Docstring
Access to labeling configuration. May be ``None`` if labeling is not used.
.. note::
Labels will only be rendered if :py:func:`~QgsMeshLayer.labelsEnabled` returns ``True``.
.. seealso:: :py:func:`labelsEnabled`
.. versionadded:: 3.26
%End
void setLabeling( QgsAbstractMeshLayerLabeling *labeling /Transfer/ );
%Docstring
Sets labeling configuration. Takes ownership of the object.
.. versionadded:: 3.36
%End
public slots:
virtual void setTransformContext( const QgsCoordinateTransformContext &transformContext );

View File

@ -0,0 +1,172 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/mesh/qgsmeshlayerlabeling.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsAbstractMeshLayerLabeling
{
%Docstring(signature="appended")
Abstract base class - its implementations define different approaches to the labeling of a mesh layer.
.. versionadded:: 3.38
%End
%TypeHeaderCode
#include "qgsmeshlayerlabeling.h"
%End
public:
QgsAbstractMeshLayerLabeling();
%Docstring
Default constructor
%End
virtual ~QgsAbstractMeshLayerLabeling();
virtual QString type() const = 0;
%Docstring
Unique type string of the labeling configuration implementation
%End
virtual QgsAbstractMeshLayerLabeling *clone() const = 0 /Factory/;
%Docstring
Returns a new copy of the object
%End
virtual QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const = 0;
%Docstring
Returns labeling configuration as XML element
%End
virtual QStringList subProviders() const;
%Docstring
Gets list of sub-providers within the layer's labeling.
%End
virtual QgsPalLayerSettings settings( const QString &providerId = QString() ) const = 0;
%Docstring
Gets associated label settings. In case of multiple sub-providers with different settings,
they are identified by their ID.
%End
virtual void setSettings( QgsPalLayerSettings *settings /Transfer/, const QString &providerId = QString() ) = 0;
%Docstring
Set pal settings for a specific provider (takes ownership).
:param settings: Pal layer settings
:param providerId: The id of the provider
.. versionadded:: 3.0
%End
virtual bool requiresAdvancedEffects() const = 0;
%Docstring
Returns ``True`` if drawing labels requires advanced effects like composition
modes, which could prevent it being used as an isolated cached image
or exported to a vector format.
%End
virtual void multiplyOpacity( double opacityFactor );
%Docstring
Multiply opacity by ``opacityFactor``.
This method multiplies the opacity of the labeling elements (text, shadow, buffer etc.)
by ``opacity`` effectively changing the opacity of the whole labeling elements.
.. versionadded:: 3.32
%End
static QgsAbstractMeshLayerLabeling *create( const QDomElement &element, const QgsReadWriteContext &context ) /Factory/;
%Docstring
Try to create instance of an implementation based on the XML data
%End
virtual void toSld( QDomNode &parent, const QVariantMap &props ) const;
%Docstring
Writes the SE 1.1 TextSymbolizer element based on the current layer labeling settings
%End
virtual bool accept( QgsStyleEntityVisitorInterface *visitor ) const;
%Docstring
Accepts the specified symbology ``visitor``, causing it to visit all symbols associated
with the labeling.
Returns ``True`` if the visitor should continue visiting other objects, or ``False`` if visiting
should be canceled.
%End
static QgsPalLayerSettings defaultSettingsForLayer( const QgsMeshLayer *layer );
%Docstring
Returns the default layer settings to use for the specified mesh ``layer``.
%End
private:
QgsAbstractMeshLayerLabeling( const QgsAbstractMeshLayerLabeling &rhs );
};
class QgsMeshLayerSimpleLabeling : QgsAbstractMeshLayerLabeling
{
%Docstring(signature="appended")
Basic implementation of the labeling interface for mesh layer.
.. versionadded:: 3.38
%End
%TypeHeaderCode
#include "qgsmeshlayerlabeling.h"
%End
public:
explicit QgsMeshLayerSimpleLabeling( const QgsPalLayerSettings &settings );
%Docstring
Constructs simple labeling configuration with given initial settings
%End
virtual QString type() const;
virtual QgsMeshLayerSimpleLabeling *clone() const /Factory/;
virtual QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const;
virtual QgsPalLayerSettings settings( const QString &providerId = QString() ) const;
virtual bool accept( QgsStyleEntityVisitorInterface *visitor ) const;
virtual void setSettings( QgsPalLayerSettings *settings /Transfer/, const QString &providerId = QString() );
%Docstring
Set pal settings (takes ownership).
:param settings: Pal layer settings
:param providerId: Unused parameter
%End
virtual bool requiresAdvancedEffects() const;
virtual void multiplyOpacity( double opacityFactor );
static QgsMeshLayerSimpleLabeling *create( const QDomElement &element, const QgsReadWriteContext &context );
%Docstring
Create the instance from a DOM element with saved configuration
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/mesh/qgsmeshlayerlabeling.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -477,6 +477,7 @@
%Include auto_generated/mesh/qgsmeshlayer.sip
%Include auto_generated/mesh/qgsmeshlayerelevationproperties.sip
%Include auto_generated/mesh/qgsmeshlayerinterpolator.sip
%Include auto_generated/mesh/qgsmeshlayerlabeling.sip
%Include auto_generated/mesh/qgsmeshlayertemporalproperties.sip
%Include auto_generated/mesh/qgsmeshrenderersettings.sip
%Include auto_generated/mesh/qgsmeshspatialindex.sip

View File

@ -0,0 +1,24 @@
{
"name": "mesh_contour",
"type": "function",
"groups": ["Meshes"],
"description": "Returns the mesh scalar value at a given point for a dataset group used for contour rendering.",
"arguments": [{
"arg": "point",
"description": "point geometry (for multipart geometries having more than one part, a first part will be used)."
}, {
"arg": "timestamp",
"description": "timestamp (defaults to current time)."
}, {
"arg": "layer",
"description": "mesh layer (defaults to current layer)."
}],
"examples": [{
"expression": "mesh_contour(make_point(1,1))",
"returns": "2.5"
}, {
"expression": "mesh_contour(make_point(1,1), make_datetime(2020,5,4,13,45,30.5))",
"returns": "2.5"
}],
"tags": ["mesh", "contour", "point"]
}

View File

@ -0,0 +1,27 @@
{
"name": "mesh_data",
"type": "function",
"groups": ["Meshes"],
"description": "Returns the mesh value at a given point for a given dataset group",
"arguments": [{
"arg": "point",
"description": "point geometry (for multipart geometries having more than one part, a first part will be used)."
}, {
"arg": "dataset_group",
"description": "name of the mesh dataset group."
}, {
"arg": "timestamp",
"description": "timestamp (defaults to current time)."
}, {
"arg": "layer",
"description": "mesh layer (defaults to current layer)."
}],
"examples": [{
"expression": "mesh_data(make_point(1,1), 'Bed Elevation')",
"returns": "2.5"
}, {
"expression": "mesh_contour(make_point(1,1), 'Bed Elevation' make_datetime(2020,5,4,13,45,30.5))",
"returns": "2.5"
}],
"tags": ["mesh", "dataset", "point"]
}

View File

@ -795,6 +795,8 @@ set(QGIS_CORE_SRCS
mesh/qgstopologicalmesh.cpp
mesh/qgsmeshadvancedediting.cpp
mesh/qgsmeshforcebypolylines.cpp
mesh/qgsmeshlayerlabeling.cpp
mesh/qgsmeshlayerlabelprovider.cpp
pointcloud/qgspointcloudattribute.cpp
pointcloud/qgspointcloudattributebyramprenderer.cpp
@ -1618,6 +1620,8 @@ set(QGIS_CORE_HDRS
mesh/qgstopologicalmesh.h
mesh/qgsmeshadvancedediting.h
mesh/qgsmeshforcebypolylines.h
mesh/qgsmeshlayerlabeling.h
mesh/qgsmeshlayerlabelprovider.h
pal/costcalculator.h
pal/feature.h

View File

@ -64,6 +64,8 @@
#include "qgsunittypes.h"
#include "qgsspatialindex.h"
#include "qgscolorrampimpl.h"
#include "qgsmeshlayer.h"
#include "qgsmeshdataset.h"
#include <QMimeDatabase>
#include <QProcessEnvironment>
@ -1865,6 +1867,173 @@ static QVariant fcnRasterAttributes( const QVariantList &values, const QgsExpres
}
}
static QVariant fcnMeshContour( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );
QDateTime datetime;
if ( values.size() < 2 || QgsVariantUtils::isNull( values.at( 1 ) ) )
{
datetime = QDateTime::currentDateTimeUtc();
}
else
{
datetime = QgsExpressionUtils::getDateTimeValue( values.at( 1 ), parent );
}
QVariant layer;
if ( values.size() < 3 || QgsVariantUtils::isNull( values.at( 2 ) ) )
{
layer = context->variable( QStringLiteral( "layer" ) );
}
else
{
layer = values.at( 2 );
}
bool foundLayer = false;
const QVariant res = QgsExpressionUtils::runMapLayerFunctionThreadSafe( layer, context, parent, [geom, parent]( QgsMapLayer * mapLayer ) -> QVariant
{
QgsMeshLayer *layer = qobject_cast< QgsMeshLayer * >( mapLayer );
if ( !layer )
{
return QVariant();
}
if ( geom.isNull() || geom.type() != Qgis::GeometryType::Point )
{
parent->setEvalErrorString( QObject::tr( "Function `mesh_contour` requires a valid point geometry." ) );
return QVariant();
}
QgsPointXY point = geom.asPoint();
if ( geom.isMultipart() )
{
QgsMultiPointXY multiPoint = geom.asMultiPoint();
if ( multiPoint.count() == 1 )
{
point = multiPoint[0];
}
else
{
return QVariant();
}
}
QgsMeshDatasetIndex index = layer->staticScalarDatasetIndex();
if ( !index.isValid() )
{
return QVariant();
}
const QgsMeshDatasetValue scalarValue = layer->datasetValue( index, point );
return scalarValue.scalar();
}, foundLayer );
if ( !foundLayer )
{
return QVariant();
}
else
{
return res;
}
}
static QVariant fcnMeshData( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
const QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent );
QString datasetGroupName = values.at( 1 ).toString();
if ( datasetGroupName.isEmpty() )
{
parent->setEvalErrorString( QObject::tr( "Mesh dataset group name can not be empty." ) );
return QVariant();
}
QDateTime datetime;
if ( values.size() < 3 || QgsVariantUtils::isNull( values.at( 2 ) ) )
{
datetime = QDateTime::currentDateTimeUtc();
}
else
{
datetime = QgsExpressionUtils::getDateTimeValue( values.at( 2 ), parent );
}
QVariant layer;
if ( values.size() < 4 || QgsVariantUtils::isNull( values.at( 3 ) ) )
{
layer = context->variable( QStringLiteral( "layer" ) );
}
else
{
layer = values.at( 3 );
}
bool foundLayer = false;
const QVariant res = QgsExpressionUtils::runMapLayerFunctionThreadSafe( layer, context, parent, [geom, datasetGroupName, parent]( QgsMapLayer * mapLayer ) -> QVariant
{
QgsMeshLayer *layer = qobject_cast< QgsMeshLayer * >( mapLayer );
if ( !layer )
{
return QVariant();
}
if ( geom.isNull() || geom.type() != Qgis::GeometryType::Point )
{
parent->setEvalErrorString( QObject::tr( "Function `mesh_data` requires a valid point geometry." ) );
return QVariant();
}
QgsPointXY point = geom.asPoint();
if ( geom.isMultipart() )
{
QgsMultiPointXY multiPoint = geom.asMultiPoint();
if ( multiPoint.count() == 1 )
{
point = multiPoint[0];
}
else
{
return QVariant();
}
}
QgsMeshDatasetGroupTreeItem *root = layer->datasetGroupTreeRootItem();
QList<QgsMeshDatasetIndex> datasetIndexList;
const QList<int> allGroup = layer->enabledDatasetGroupsIndexes();
for ( int groupIndex : allGroup )
{
QgsMeshDatasetGroupTreeItem *group = root->childFromDatasetGroupIndex( groupIndex );
if ( group->name() == datasetGroupName )
{
datasetIndexList.append( QgsMeshDatasetIndex( groupIndex, 0 ) );
break;
}
}
if ( datasetIndexList.size() == 0 )
{
parent->setEvalErrorString( QObject::tr( "Dataset group '%1' not found." ).arg( datasetGroupName ) );
return QVariant();
}
const QgsMeshDatasetValue scalarValue = layer->datasetValue( datasetIndexList.at( 0 ), point );
return scalarValue.scalar();
}, foundLayer );
if ( !foundLayer )
{
return QVariant();
}
else
{
return res;
}
}
static QVariant fcnFeature( const QVariantList &, const QgsExpressionContext *context, QgsExpression *, const QgsExpressionNodeFunction * )
{
if ( !context )
@ -9195,6 +9364,10 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "raster_value" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "layer" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "band" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "point" ) ), fcnRasterValue, QStringLiteral( "Rasters" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "raster_attributes" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "layer" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "band" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "point" ) ), fcnRasterAttributes, QStringLiteral( "Rasters" ) )
// mesh
<< new QgsStaticExpressionFunction( QStringLiteral( "mesh_contour" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "point" ), false ) << QgsExpressionFunction::Parameter( QStringLiteral( "timestamp" ), true ) << QgsExpressionFunction::Parameter( QStringLiteral( "layer" ), true ), fcnMeshContour, QStringLiteral( "Meshes" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "mesh_data" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "point" ), false ) << QgsExpressionFunction::Parameter( QStringLiteral( "dataset_group" ), false ) << QgsExpressionFunction::Parameter( QStringLiteral( "timestamp" ), true ) << QgsExpressionFunction::Parameter( QStringLiteral( "layer" ), true ), fcnMeshData, QStringLiteral( "Meshes" ) )
// functions for arrays
<< new QgsArrayForeachExpressionFunction()
<< new QgsArrayFilterExpressionFunction()

View File

@ -47,6 +47,7 @@
#include "qgsthreadingutils.h"
#include "qgsapplication.h"
#include "qgsruntimeprofiler.h"
#include "qgsmeshlayerlabeling.h"
QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath,
const QString &baseName,
@ -106,6 +107,7 @@ bool QgsMeshLayer::hasSimplifiedMeshes() const
QgsMeshLayer::~QgsMeshLayer()
{
delete mLabeling;
delete mDataProvider;
}
@ -1773,6 +1775,16 @@ bool QgsMeshLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString &e
blendModeElement.appendChild( blendModeText );
node.appendChild( blendModeElement );
if ( categories.testFlag( Labeling ) )
{
if ( mLabeling )
{
QDomElement labelingElement = mLabeling->save( doc, context );
elem.appendChild( labelingElement );
}
elem.setAttribute( QStringLiteral( "labelsEnabled" ), mLabelsEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
}
// add the layer opacity
if ( categories.testFlag( Rendering ) )
{
@ -2148,3 +2160,28 @@ QgsMapLayerElevationProperties *QgsMeshLayer::elevationProperties()
return mElevationProperties;
}
bool QgsMeshLayer::labelsEnabled() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mLabelsEnabled && static_cast< bool >( mLabeling );
}
void QgsMeshLayer::setLabelsEnabled( bool enabled )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mLabelsEnabled = enabled;
}
void QgsMeshLayer::setLabeling( QgsAbstractMeshLayerLabeling *labeling )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mLabeling == labeling )
return;
delete mLabeling;
mLabeling = labeling;
}

View File

@ -42,6 +42,7 @@ class QgsMeshDatasetGroupStore;
class QgsMeshEditor;
class QgsMeshEditingError;
class QgsMeshLayerElevationProperties;
class QgsAbstractMeshLayerLabeling;
/**
* \ingroup core
@ -881,6 +882,53 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer, public QgsAbstractProfileSo
*/
int meshEdgeCount() const;
/**
* Returns whether the layer contains labels which are enabled and should be drawn.
* \returns TRUE if layer contains enabled labels
*
* \see setLabelsEnabled()
* \see labeling()
* \since QGIS 3.36
*/
bool labelsEnabled() const;
/**
* Sets whether labels should be \a enabled for the layer.
*
* \note Labels will only be rendered if labelsEnabled() is TRUE and a labeling
* object is returned by labeling().
*
* \see labelsEnabled()
* \see labeling()
* \since QGIS 3.36
*/
void setLabelsEnabled( bool enabled );
/**
* Access to const labeling configuration. May be NULLPTR if labeling is not used.
* \note Labels will only be rendered if labelsEnabled() returns TRUE.
*
* \see labelsEnabled()
* \see setLabelsEnabled()
* \since QGIS 3.36
*/
const QgsAbstractMeshLayerLabeling *labeling() const SIP_SKIP { return mLabeling; }
/**
* Access to labeling configuration. May be NULLPTR if labeling is not used.
* \note Labels will only be rendered if labelsEnabled() returns TRUE.
* \see labelsEnabled()
* \since QGIS 3.26
*/
QgsAbstractMeshLayerLabeling *labeling() { return mLabeling; }
/**
* Sets labeling configuration. Takes ownership of the object.
* \since QGIS 3.36
*/
void setLabeling( QgsAbstractMeshLayerLabeling *labeling SIP_TRANSFER );
public slots:
/**
@ -991,6 +1039,12 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer, public QgsAbstractProfileSo
QgsMeshEditor *mMeshEditor = nullptr;
//! True if labels are enabled
bool mLabelsEnabled = false;
//! Labeling configuration
QgsAbstractMeshLayerLabeling *mLabeling = nullptr;
int closestEdge( const QgsPointXY &point, double searchRadius, QgsPointXY &projectedPoint ) const;
//! Returns the exact position in map coordinates of the closest vertex in the search area

View File

@ -0,0 +1,132 @@
/***************************************************************************
qgsmeshlayerlabeling.cpp
---------------------
begin : November 2023
copyright : (C) 2023 by LutraConsulting
email : info at lutraconsulting dot co dot uk
***************************************************************************/
/***************************************************************************
* *
* 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 "qgsmeshlayerlabeling.h"
#include "qgspallabeling.h"
#include "qgsmeshlayer.h"
#include "qgis.h"
#include "qgsstyleentityvisitor.h"
#include "qgsmeshlayerlabelprovider.h"
QgsAbstractMeshLayerLabeling *QgsAbstractMeshLayerLabeling::create( const QDomElement &element, const QgsReadWriteContext &context )
{
const QString type = element.attribute( QStringLiteral( "type" ) );
if ( type == QLatin1String( "simple" ) )
{
return QgsMeshLayerSimpleLabeling::create( element, context );
}
else
{
return nullptr;
}
}
bool QgsAbstractMeshLayerLabeling::accept( QgsStyleEntityVisitorInterface * ) const
{
return true;
}
QgsPalLayerSettings QgsAbstractMeshLayerLabeling::defaultSettingsForLayer( const QgsMeshLayer *layer )
{
QgsPalLayerSettings settings;
settings.setFormat( QgsStyle::defaultTextFormatForProject( layer->project() ) );
return settings;
}
///
QgsMeshLayerSimpleLabeling::QgsMeshLayerSimpleLabeling( const QgsPalLayerSettings &settings )
: mSettings( new QgsPalLayerSettings( settings ) )
{
}
QString QgsMeshLayerSimpleLabeling::type() const
{
return QStringLiteral( "simple" );
}
QgsMeshLayerSimpleLabeling *QgsMeshLayerSimpleLabeling::clone() const
{
return new QgsMeshLayerSimpleLabeling( *mSettings );
}
QgsMeshLayerLabelProvider *QgsMeshLayerSimpleLabeling::provider( QgsMeshLayer *layer ) const
{
return new QgsMeshLayerLabelProvider( layer, QString(), mSettings.get() );
}
QDomElement QgsMeshLayerSimpleLabeling::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
{
QDomElement elem = doc.createElement( QStringLiteral( "labeling" ) );
elem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "simple" ) );
elem.appendChild( mSettings->writeXml( doc, context ) );
return elem;
}
QgsPalLayerSettings QgsMeshLayerSimpleLabeling::settings( const QString &providerId ) const
{
Q_UNUSED( providerId )
return *mSettings;
}
bool QgsMeshLayerSimpleLabeling::accept( QgsStyleEntityVisitorInterface *visitor ) const
{
if ( mSettings )
{
QgsStyleLabelSettingsEntity entity( *mSettings );
if ( !visitor->visit( &entity ) )
return false;
}
return true;
}
bool QgsMeshLayerSimpleLabeling::requiresAdvancedEffects() const
{
return mSettings->containsAdvancedEffects();
}
QgsMeshLayerSimpleLabeling *QgsMeshLayerSimpleLabeling::create( const QDomElement &element, const QgsReadWriteContext &context )
{
const QDomElement settingsElem = element.firstChildElement( QStringLiteral( "settings" ) );
if ( !settingsElem.isNull() )
{
QgsPalLayerSettings settings;
settings.readXml( settingsElem, context );
return new QgsMeshLayerSimpleLabeling( settings );
}
return new QgsMeshLayerSimpleLabeling( QgsPalLayerSettings() );
}
void QgsMeshLayerSimpleLabeling::multiplyOpacity( double opacityFactor )
{
QgsTextFormat format { mSettings->format() };
format.multiplyOpacity( opacityFactor );
mSettings->setFormat( format );
}
void QgsMeshLayerSimpleLabeling::setSettings( QgsPalLayerSettings *settings, const QString &providerId )
{
Q_UNUSED( providerId )
if ( mSettings.get() == settings )
return;
mSettings.reset( settings );
}

View File

@ -0,0 +1,180 @@
/***************************************************************************
qgsmeshlayerlabeling.h
---------------------
begin : November 2023
copyright : (C) 2023 by LutraConsulting
email : info at lutraconsulting dot co dot uk
***************************************************************************/
/***************************************************************************
* *
* 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 QGSMESHLAYERLABELING_H
#define QGSMESHLAYERLABELING_H
#include <memory>
#include <QString>
#include <QStringList>
#include <QDomNode>
#include "qgis.h"
class QDomDocument;
class QDomElement;
class QgsPalLayerSettings;
class QgsReadWriteContext;
class QgsMeshLayer;
class QgsMeshLayerLabelProvider;
class QgsStyleEntityVisitorInterface;
/**
* \ingroup core
* \brief Abstract base class - its implementations define different approaches to the labeling of a mesh layer.
*
* \since QGIS 3.38
*/
class CORE_EXPORT QgsAbstractMeshLayerLabeling
{
public:
//! Default constructor
QgsAbstractMeshLayerLabeling() = default;
virtual ~QgsAbstractMeshLayerLabeling() = default;
//! Unique type string of the labeling configuration implementation
virtual QString type() const = 0;
//! Returns a new copy of the object
virtual QgsAbstractMeshLayerLabeling *clone() const = 0 SIP_FACTORY;
/**
* Factory for label provider implementation
* \note not available in Python bindings
*/
virtual QgsMeshLayerLabelProvider *provider( QgsMeshLayer *layer ) const SIP_SKIP { Q_UNUSED( layer ) return nullptr; }
//! Returns labeling configuration as XML element
virtual QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const = 0;
//! Gets list of sub-providers within the layer's labeling.
virtual QStringList subProviders() const { return QStringList( QString() ); }
/**
* Gets associated label settings. In case of multiple sub-providers with different settings,
* they are identified by their ID.
*/
virtual QgsPalLayerSettings settings( const QString &providerId = QString() ) const = 0;
/**
* Set pal settings for a specific provider (takes ownership).
*
* \param settings Pal layer settings
* \param providerId The id of the provider
*
* \since QGIS 3.0
*/
virtual void setSettings( QgsPalLayerSettings *settings SIP_TRANSFER, const QString &providerId = QString() ) = 0;
/**
* Returns TRUE if drawing labels requires advanced effects like composition
* modes, which could prevent it being used as an isolated cached image
* or exported to a vector format.
*/
virtual bool requiresAdvancedEffects() const = 0;
/**
* Multiply opacity by \a opacityFactor.
*
* This method multiplies the opacity of the labeling elements (text, shadow, buffer etc.)
* by \a opacity effectively changing the opacity of the whole labeling elements.
*
* \since QGIS 3.32
*/
virtual void multiplyOpacity( double opacityFactor ) { Q_UNUSED( opacityFactor ); };
// static stuff
//! Try to create instance of an implementation based on the XML data
static QgsAbstractMeshLayerLabeling *create( const QDomElement &element, const QgsReadWriteContext &context ) SIP_FACTORY;
/**
* Writes the SE 1.1 TextSymbolizer element based on the current layer labeling settings
*/
virtual void toSld( QDomNode &parent, const QVariantMap &props ) const
{
Q_UNUSED( parent )
Q_UNUSED( props )
QDomDocument doc = parent.ownerDocument();
parent.appendChild( doc.createComment( QStringLiteral( "SE Export for %1 not implemented yet" ).arg( type() ) ) );
}
/**
* Accepts the specified symbology \a visitor, causing it to visit all symbols associated
* with the labeling.
*
* Returns TRUE if the visitor should continue visiting other objects, or FALSE if visiting
* should be canceled.
*/
virtual bool accept( QgsStyleEntityVisitorInterface *visitor ) const;
/**
* Returns the default layer settings to use for the specified mesh \a layer.
*/
static QgsPalLayerSettings defaultSettingsForLayer( const QgsMeshLayer *layer );
private:
Q_DISABLE_COPY( QgsAbstractMeshLayerLabeling )
#ifdef SIP_RUN
QgsAbstractMeshLayerLabeling( const QgsAbstractMeshLayerLabeling &rhs );
#endif
};
/**
* \ingroup core
* \brief Basic implementation of the labeling interface for mesh layer.
*
* \since QGIS 3.38
*/
class CORE_EXPORT QgsMeshLayerSimpleLabeling : public QgsAbstractMeshLayerLabeling
{
public:
//! Constructs simple labeling configuration with given initial settings
explicit QgsMeshLayerSimpleLabeling( const QgsPalLayerSettings &settings );
QString type() const override;
QgsMeshLayerSimpleLabeling *clone() const override SIP_FACTORY;
//! \note not available in Python bindings
QgsMeshLayerLabelProvider *provider( QgsMeshLayer *layer ) const override SIP_SKIP;
QDomElement save( QDomDocument &doc, const QgsReadWriteContext &context ) const override;
QgsPalLayerSettings settings( const QString &providerId = QString() ) const override;
bool accept( QgsStyleEntityVisitorInterface *visitor ) const override;
/**
* Set pal settings (takes ownership).
*
* \param settings Pal layer settings
* \param providerId Unused parameter
*/
void setSettings( QgsPalLayerSettings *settings SIP_TRANSFER, const QString &providerId = QString() ) override;
bool requiresAdvancedEffects() const override;
void multiplyOpacity( double opacityFactor ) override;
//! Create the instance from a DOM element with saved configuration
static QgsMeshLayerSimpleLabeling *create( const QDomElement &element, const QgsReadWriteContext &context );
private:
std::unique_ptr<QgsPalLayerSettings> mSettings;
};
#endif // QGSMESHLAYERLABELING_H

View File

@ -0,0 +1,173 @@
/***************************************************************************
qgsmeshlayerlabelprovider.cpp
---------------------
begin : November 2023
copyright : (C) 2023 by LutraConsulting
email : info at lutraconsulting dot co dot uk
***************************************************************************/
/***************************************************************************
* *
* 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 "qgsmeshlayerlabelprovider.h"
#include "qgsgeometry.h"
#include "qgslabelsearchtree.h"
#include "qgspallabeling.h"
#include "qgstextlabelfeature.h"
#include "qgsmeshlayer.h"
#include "qgsvectorlayer.h"
#include "qgsrenderer.h"
#include "qgspolygon.h"
#include "qgslinestring.h"
#include "qgsmultipolygon.h"
#include "qgslogger.h"
#include "qgsexpressioncontextutils.h"
#include "qgsmaskidprovider.h"
#include "qgstextcharacterformat.h"
#include "qgstextfragment.h"
#include "qgslabelingresults.h"
#include "qgstextrenderer.h"
#include "qgstriangularmesh.h"
#include "feature.h"
#include "labelposition.h"
#include "qgssymbol.h"
#include "qgsmarkersymbol.h"
#include "pal/layer.h"
#include <QPicture>
#include <QTextDocument>
#include <QTextFragment>
using namespace pal;
QgsMeshLayerLabelProvider::QgsMeshLayerLabelProvider( QgsMeshLayer *layer, const QString &providerId, const QgsPalLayerSettings *settings, const QString &layerName )
: QgsAbstractLabelProvider( layer, providerId )
, mSettings( settings ? * settings : QgsPalLayerSettings() )
, mCrs( layer->crs() )
{
mName = layerName.isEmpty() ? layer->id() : layerName;
init();
}
void QgsMeshLayerLabelProvider::init()
{
mPlacement = mSettings.placement;
mFlags = Flags();
if ( mSettings.drawLabels )
mFlags |= DrawLabels;
if ( mSettings.lineSettings().mergeLines() && !mSettings.lineSettings().addDirectionSymbol() )
mFlags |= MergeConnectedLines;
if ( mSettings.centroidInside )
mFlags |= CentroidMustBeInside;
mPriority = 1 - mSettings.priority / 10.0; // convert 0..10 --> 1..0
if ( mLabelFaces )
{
mVectorLayer = std::make_unique<QgsVectorLayer>( QStringLiteral( "Polygon?crs=%1" ).arg( mCrs.authid() ), QStringLiteral( "faces" ), QStringLiteral( "memory" ) );
QgsMesh *mesh = qobject_cast<QgsMeshLayer *>( layer() )->nativeMesh();
int faceCount = mesh->faceCount();
QList<QgsFeature> features;
for ( int i = 0; i < faceCount; i++ )
{
QgsFeature f;
QgsGeometry geom = QgsMeshUtils::toGeometry( mesh->face( i ), mesh->vertices );
f.setGeometry( geom );
features << f;
}
mVectorLayer->dataProvider()->addFeatures( features );
}
else
{
mVectorLayer = std::make_unique<QgsVectorLayer>( QStringLiteral( "Polygon?crs=%1" ).arg( mCrs.authid() ), QStringLiteral( "faces" ), QStringLiteral( "memory" ) );
QgsMesh *mesh = qobject_cast<QgsMeshLayer *>( layer() )->nativeMesh();
int vertexCount = mesh->vertexCount();
QList<QgsFeature> features;
for ( int i = 0; i < vertexCount; i++ )
{
QgsFeature f;
QgsGeometry geom = QgsGeometry( new QgsPoint( mesh->vertex( i ) ) );
f.setGeometry( geom );
features << f;
}
mVectorLayer->dataProvider()->addFeatures( features );
}
mVectorLabelProvider = std::make_unique<QgsVectorLayerLabelProvider>( mVectorLayer.get(), QStringLiteral(), false, &mSettings );
if ( mLabelFaces )
{
//override obstacle type to treat any intersection of a label with the point symbol as a high cost conflict
mObstacleType = QgsLabelObstacleSettings::PolygonWhole;
}
else
{
mObstacleType = mSettings.obstacleSettings().type();
}
mUpsidedownLabels = mSettings.upsidedownLabels;
}
QgsMeshLayerLabelProvider::~QgsMeshLayerLabelProvider()
{
qDeleteAll( mLabels );
}
bool QgsMeshLayerLabelProvider::prepare( QgsRenderContext &context, QSet<QString> &attributeNames )
{
const QgsMapSettings &mapSettings = mEngine->mapSettings();
return mSettings.prepare( context, attributeNames, QgsFields(), mapSettings, mCrs );
}
void QgsMeshLayerLabelProvider::startRender( QgsRenderContext &context )
{
QgsAbstractLabelProvider::startRender( context );
mSettings.startRender( context );
}
void QgsMeshLayerLabelProvider::stopRender( QgsRenderContext &context )
{
QgsAbstractLabelProvider::stopRender( context );
mSettings.stopRender( context );
}
QList<QgsLabelFeature *> QgsMeshLayerLabelProvider::labelFeatures( QgsRenderContext &ctx )
{
Q_UNUSED( ctx );
return mLabels;
}
QList< QgsLabelFeature * > QgsMeshLayerLabelProvider::registerFeature( const QgsFeature &feature, QgsRenderContext &context, const QgsGeometry &obstacleGeometry, const QgsSymbol *symbol )
{
std::unique_ptr< QgsLabelFeature > label = mSettings.registerFeatureWithDetails( feature, context, obstacleGeometry, symbol );
QList< QgsLabelFeature * > res;
if ( label )
{
res << label.get();
mLabels << label.release();
}
return res;
}
const QgsPalLayerSettings &QgsMeshLayerLabelProvider::settings() const
{
return mSettings;
}
void QgsMeshLayerLabelProvider::drawLabel( QgsRenderContext &context, pal::LabelPosition *label ) const
{
mVectorLabelProvider->drawLabel( context, label );
}

View File

@ -0,0 +1,114 @@
/***************************************************************************
qgsmeshlayerlabelprovider.h
---------------------
begin : November 2023
copyright : (C) 2023 by LutraConsulting
email : info at lutraconsulting dot co dot uk
***************************************************************************/
/***************************************************************************
* *
* 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 QGSMESHLAYERLABELPROVIDER_H
#define QGSMESHLAYERLABELPROVIDER_H
#define SIP_NO_FILE
#include "qgis_core.h"
#include "qgslabelingengine.h"
#include "qgsrenderer.h"
#include "qgspallabeling.h"
class QgsMeshLayer;
/**
* \ingroup core
* \brief The QgsMeshLayerLabelProvider class implements a label provider
* for mesh layers. Parameters for the labeling are taken from the layer's
* custom properties or from the given settings.
*
* \note this class is not a part of public API yet. See notes in QgsLabelingEngine
* \note not available in Python bindings
* \since QGIS 2.12
*/
class CORE_EXPORT QgsMeshLayerLabelProvider : public QgsAbstractLabelProvider
{
public:
//! Convenience constructor to initialize the provider from given mesh layer
explicit QgsMeshLayerLabelProvider( QgsMeshLayer *layer,
const QString &providerId,
const QgsPalLayerSettings *settings,
const QString &layerName = QString() );
~QgsMeshLayerLabelProvider() override;
QList<QgsLabelFeature *> labelFeatures( QgsRenderContext &context ) override;
//void drawLabelBackground( QgsRenderContext &context, pal::LabelPosition *label ) const override;
void drawLabel( QgsRenderContext &context, pal::LabelPosition *label ) const override;
//void drawUnplacedLabel( QgsRenderContext &context, pal::LabelPosition *label ) const override;
void startRender( QgsRenderContext &context ) override;
void stopRender( QgsRenderContext &context ) override;
/**
* Prepare for registration of features. Must be called after provider has been added to engine (uses its map settings)
* \param context render context.
* \param attributeNames list of attribute names to which additional required attributes shall be added
* \returns Whether the preparation was successful - if not, the provider shall not be used
*/
virtual bool prepare( QgsRenderContext &context, QSet<QString> &attributeNames );
/**
* Register a feature for labeling as one or more QgsLabelFeature objects stored into mLabels
*
* \param feature feature to label
* \param context render context. The QgsExpressionContext contained within the render context
* must have already had the feature and fields sets prior to calling this method.
* \param obstacleGeometry optional obstacle geometry, if a different geometry to the feature's geometry
* should be used as an obstacle for labels (e.g., if the feature has been rendered with an offset point
* symbol, the obstacle geometry should represent the bounds of the offset symbol). If not set,
* the feature's original geometry will be used as an obstacle for labels.
* \param symbol feature symbol to label (ownership is not transferred - the symbol must exist until after labeling is complete)
* \returns a list of the newly generated label features. Ownership of these label features is not transferred
* (it has already been assigned to the label provider).
*/
virtual QList< QgsLabelFeature * > registerFeature( const QgsFeature &feature, QgsRenderContext &context, const QgsGeometry &obstacleGeometry = QgsGeometry(), const QgsSymbol *symbol = nullptr );
/**
* Returns the layer's settings.
*/
const QgsPalLayerSettings &settings() const;
protected:
//! initialization method - called from constructors
void init();
//! Internal label drawing method
void drawLabelPrivate( pal::LabelPosition *label, QgsRenderContext &context, QgsPalLayerSettings &tmpLyr, Qgis::TextComponent drawType, double dpiRatio = 1.0 ) const;
protected:
//! Layer's labeling configuration
QgsPalLayerSettings mSettings;
bool mLabelFaces = false;
//! Layer's CRS
QgsCoordinateReferenceSystem mCrs;
//! List of generated
QList<QgsLabelFeature *> mLabels;
private:
std::unique_ptr<QgsVectorLayerLabelProvider> mVectorLabelProvider;
std::unique_ptr<QgsVectorLayer> mVectorLayer;
friend class TestQgsLabelingEngine;
};
#endif // QGSMESHLAYERLABELPROVIDER_H

View File

@ -2016,6 +2016,13 @@ class TestQgsExpression: public QObject
QTest::newRow( "raster_attributes wrong band 2" ) << QStringLiteral( "raster_attributes('%1',2,243)" ).arg( mRasterLayerWithAttributeTable->name() ) << true << QVariant();
QTest::newRow( "raster_attributes no attributes" ) << QStringLiteral( "raster_attributes('%1',1,1)" ).arg( mRasterLayerWithAttributeTable->name() ) << false << QVariant();
// mesh_contour tests
QTest::newRow( "mesh_contour invalid geometry" ) << QStringLiteral( "mesh_contour('invalid geom', make_datetime(2023,11,29,10,34,52), '%1')" ).arg( mMeshLayer->name() ) << true << QVariant();
QTest::newRow( "mesh_contour valid" ) << QStringLiteral( "mesh_contour(make_point(2000,3000), make_datetime(2023,11,29,10,34,52), '%1')" ).arg( mMeshLayer->name() ) << false << QVariant( 200.0 );
QTest::newRow( "mesh_data invalid geometry" ) << QStringLiteral( "mesh_data('invalid geom', 'Bed Elevation', make_datetime(2023,11,29,10,34,52), '%1')" ).arg( mMeshLayer->name() ) << true << QVariant();
QTest::newRow( "mesh_data invalid group" ) << QStringLiteral( "mesh_data(make_point(2000,3000), 'Bad elevation', make_datetime(2023,11,29,10,34,52), '%1')" ).arg( mMeshLayer->name() ) << true << QVariant();
QTest::newRow( "mesh_data valid" ) << QStringLiteral( "mesh_data(make_point(2000,3000), 'Bed Elevation', make_datetime(2023,11,29,10,34,52), '%1')" ).arg( mMeshLayer->name() ) << false << QVariant( 200.0 );
//test conversions to bool
QTest::newRow( "feature to bool false" ) << QStringLiteral( "case when get_feature('none','none',499) then true else false end" ) << false << QVariant( false );
QTest::newRow( "feature to bool true" ) << QStringLiteral( "case when get_feature('test','col1',10) then true else false end" ) << false << QVariant( true );