Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FEATURE] Merge pull request #9460 from m-kuhn/labeling_geometry_gene…
…rator

➿ Geometry generators for labeling ➿
  • Loading branch information
m-kuhn committed Mar 12, 2019
2 parents f60e0b6 + de541d4 commit 857fe53
Show file tree
Hide file tree
Showing 15 changed files with 827 additions and 434 deletions.
20 changes: 20 additions & 0 deletions python/core/auto_generated/qgspallabeling.sip.in
Expand Up @@ -261,6 +261,20 @@ class QgsPalLayerSettings
AlwaysShow
};


bool prepare( const QgsRenderContext &context, QSet<QString> &attributeNames /In,Out/, const QgsFields &fields, const QgsMapSettings &mapSettings, const QgsCoordinateReferenceSystem &crs );
%Docstring
Prepare for registration of features.
The ``context``, ``mapSettings`` and ``fields`` parameters give more
information about the rendering environment.
If target ``crs`` is not specified, the targetCrs from ``mapSettings``
will be taken.
The parameter ``attributeNames`` should be updated to contain all the field
names which the labeling requires for the rendering.

.. versionadded:: 3.8
%End

static const QgsPropertiesDefinition &propertyDefinitions();
%Docstring
Returns the labeling property definitions.
Expand Down Expand Up @@ -390,6 +404,12 @@ Returns the QgsExpression for this label settings. May be ``None`` if isExpressi

double zIndex;

QString geometryGenerator;

QgsWkbTypes::GeometryType geometryGeneratorType;

bool geometryGeneratorEnabled;

void calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f = 0, QgsRenderContext *context = 0 );
%Docstring
Calculates the space required to render the provided ``text`` in map units.
Expand Down
203 changes: 160 additions & 43 deletions src/app/qgslabelinggui.cpp
Expand Up @@ -24,6 +24,7 @@
#include "qgsauxiliarystorage.h"
#include "qgsnewauxiliarylayerdialog.h"
#include "qgsexpressioncontextutils.h"
#include "qgsexpressionbuilderdialog.h"

#include <QButtonGroup>

Expand Down Expand Up @@ -78,6 +79,12 @@ QgsLabelingGui::QgsLabelingGui( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas,
connect( mFormatNumChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
connect( mScaleBasedVisibilityChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
connect( mFontLimitPixelChkBox, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
connect( mGeometryGeneratorGroupBox, &QGroupBox::toggled, this, &QgsLabelingGui::updateGeometryTypeBasedWidgets );
connect( mGeometryGeneratorType, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsLabelingGui::updateGeometryTypeBasedWidgets );
connect( mGeometryGeneratorExpressionButton, &QToolButton::clicked, this, &QgsLabelingGui::showGeometryGeneratorExpressionBuilder );
connect( mGeometryGeneratorGroupBox, &QGroupBox::toggled, this, &QgsLabelingGui::validateGeometryGeneratorExpression );
connect( mGeometryGenerator, &QgsCodeEditorExpression::textChanged, this, &QgsLabelingGui::validateGeometryGeneratorExpression );
connect( mGeometryGeneratorType, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsLabelingGui::validateGeometryGeneratorExpression );

mFieldExpressionWidget->registerExpressionContextGenerator( this );

Expand All @@ -86,71 +93,43 @@ QgsLabelingGui::QgsLabelingGui( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas,
mMaxScaleWidget->setMapCanvas( mapCanvas );
mMaxScaleWidget->setShowCurrentScaleButton( true );

mGeometryGeneratorWarningLabel->setStyleSheet( QStringLiteral( "color: #FFC107;" ) );
mGeometryGeneratorWarningLabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
connect( mGeometryGeneratorWarningLabel, &QLabel::linkActivated, this, [this]( const QString & link )
{
if ( link == QLatin1String( "#determineGeometryGeneratorType" ) )
determineGeometryGeneratorType();
} );

setLayer( layer );
}

void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer )
{
mPreviewFeature = QgsFeature();

if ( !mapLayer || mapLayer->type() != QgsMapLayer::VectorLayer )
{
setEnabled( false );
return;
}
else
{
setEnabled( true );
}

QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
setEnabled( true );

QgsVectorLayer *layer = static_cast<QgsVectorLayer *>( mapLayer );
mLayer = layer;

// load labeling settings from layer
const QgsPalLayerSettings &lyr = mSettings;

// show/hide options based upon geometry type
chkMergeLines->setVisible( mLayer->geometryType() == QgsWkbTypes::LineGeometry );
mDirectSymbolsFrame->setVisible( mLayer->geometryType() == QgsWkbTypes::LineGeometry );
mMinSizeFrame->setVisible( mLayer->geometryType() != QgsWkbTypes::PointGeometry );
mPolygonObstacleTypeFrame->setVisible( mLayer->geometryType() == QgsWkbTypes::PolygonGeometry );
mPolygonFeatureOptionsFrame->setVisible( mLayer->geometryType() == QgsWkbTypes::PolygonGeometry );
updateGeometryTypeBasedWidgets();

mFieldExpressionWidget->setLayer( mLayer );
QgsDistanceArea da;
da.setSourceCrs( mLayer->crs(), QgsProject::instance()->transformContext() );
da.setEllipsoid( QgsProject::instance()->ellipsoid() );
mFieldExpressionWidget->setGeomCalculator( da );

// set placement methods page based on geometry type
switch ( mLayer->geometryType() )
{
case QgsWkbTypes::PointGeometry:
stackedPlacement->setCurrentWidget( pagePoint );
break;
case QgsWkbTypes::LineGeometry:
stackedPlacement->setCurrentWidget( pageLine );
break;
case QgsWkbTypes::PolygonGeometry:
stackedPlacement->setCurrentWidget( pagePolygon );
break;
case QgsWkbTypes::NullGeometry:
break;
case QgsWkbTypes::UnknownGeometry:
qFatal( "unknown geometry type unexpected" );
}

if ( mLayer->geometryType() == QgsWkbTypes::PointGeometry )
{
// follow placement alignment is only valid for point layers
if ( mFontMultiLineAlignComboBox->findText( tr( "Follow label placement" ) ) == -1 )
mFontMultiLineAlignComboBox->addItem( tr( "Follow label placement" ) );
}
else
{
int idx = mFontMultiLineAlignComboBox->findText( tr( "Follow label placement" ) );
if ( idx >= 0 )
mFontMultiLineAlignComboBox->removeItem( idx );
}

mFieldExpressionWidget->setEnabled( mMode == Labels );
mLabelingFrame->setEnabled( mMode == Labels );

Expand Down Expand Up @@ -250,7 +229,7 @@ void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer )
wrapCharacterEdit->setText( lyr.wrapChar );
mAutoWrapLengthSpinBox->setValue( lyr.autoWrapLength );
mAutoWrapTypeComboBox->setCurrentIndex( lyr.useMaxLineLengthForAutoWrap ? 0 : 1 );
mFontMultiLineAlignComboBox->setCurrentIndex( ( unsigned int ) lyr.multilineAlign );
mFontMultiLineAlignComboBox->setCurrentIndex( lyr.multilineAlign );
chkPreserveRotation->setChecked( lyr.preserveRotation );

mPreviewBackgroundBtn->setColor( lyr.previewBkgrdColor );
Expand All @@ -274,6 +253,10 @@ void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer )

mZIndexSpinBox->setValue( lyr.zIndex );

mGeometryGenerator->setText( lyr.geometryGenerator );
mGeometryGeneratorGroupBox->setChecked( lyr.geometryGeneratorEnabled );
mGeometryGeneratorType->setCurrentIndex( mGeometryGeneratorType->findData( lyr.geometryGeneratorType ) );

mDataDefinedProperties = lyr.dataDefinedProperties();

updatePlacementWidgets();
Expand Down Expand Up @@ -442,6 +425,9 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
lyr.useMaxLineLengthForAutoWrap = mAutoWrapTypeComboBox->currentIndex() == 0;
lyr.multilineAlign = ( QgsPalLayerSettings::MultiLineAlign ) mFontMultiLineAlignComboBox->currentIndex();
lyr.preserveRotation = chkPreserveRotation->isChecked();
lyr.geometryGenerator = mGeometryGenerator->text();
lyr.geometryGeneratorType = mGeometryGeneratorType->currentData().value<QgsWkbTypes::GeometryType>();
lyr.geometryGeneratorEnabled = mGeometryGeneratorGroupBox->isChecked();

lyr.zIndex = mZIndexSpinBox->value();

Expand Down Expand Up @@ -668,3 +654,134 @@ void QgsLabelingGui::deactivateField( QgsPalLayerSettings::Property key )
mDataDefinedProperties.setProperty( key, p );
}
}

void QgsLabelingGui::updateGeometryTypeBasedWidgets()
{
QgsWkbTypes::GeometryType geometryType;

if ( mGeometryGeneratorGroupBox->isChecked() )
geometryType = mGeometryGeneratorType->currentData().value<QgsWkbTypes::GeometryType>();
else
geometryType = mLayer->geometryType();

// show/hide options based upon geometry type
chkMergeLines->setVisible( geometryType == QgsWkbTypes::LineGeometry );
mDirectSymbolsFrame->setVisible( geometryType == QgsWkbTypes::LineGeometry );
mMinSizeFrame->setVisible( geometryType != QgsWkbTypes::PointGeometry );
mPolygonObstacleTypeFrame->setVisible( geometryType == QgsWkbTypes::PolygonGeometry );
mPolygonFeatureOptionsFrame->setVisible( geometryType == QgsWkbTypes::PolygonGeometry );


// set placement methods page based on geometry type
switch ( geometryType )
{
case QgsWkbTypes::PointGeometry:
stackedPlacement->setCurrentWidget( pagePoint );
break;
case QgsWkbTypes::LineGeometry:
stackedPlacement->setCurrentWidget( pageLine );
break;
case QgsWkbTypes::PolygonGeometry:
stackedPlacement->setCurrentWidget( pagePolygon );
break;
case QgsWkbTypes::NullGeometry:
break;
case QgsWkbTypes::UnknownGeometry:
qFatal( "unknown geometry type unexpected" );
}

if ( geometryType == QgsWkbTypes::PointGeometry )
{
// follow placement alignment is only valid for point layers
if ( mFontMultiLineAlignComboBox->findText( tr( "Follow label placement" ) ) == -1 )
mFontMultiLineAlignComboBox->addItem( tr( "Follow label placement" ) );
}
else
{
int idx = mFontMultiLineAlignComboBox->findText( tr( "Follow label placement" ) );
if ( idx >= 0 )
mFontMultiLineAlignComboBox->removeItem( idx );
}

updatePlacementWidgets();
updateLinePlacementOptions();
}

void QgsLabelingGui::showGeometryGeneratorExpressionBuilder()
{
QgsExpressionBuilderDialog expressionBuilder( mLayer );

expressionBuilder.setExpressionText( mGeometryGenerator->text() );
expressionBuilder.setExpressionContext( createExpressionContext() );

if ( expressionBuilder.exec() )
{
mGeometryGenerator->setText( expressionBuilder.expressionText() );
}
}

void QgsLabelingGui::validateGeometryGeneratorExpression()
{
bool valid = true;

if ( mGeometryGeneratorGroupBox->isChecked() )
{
if ( !mPreviewFeature.isValid() && mLayer )
mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( mPreviewFeature );

QgsExpression expression( mGeometryGenerator->text() );
QgsExpressionContext context = createExpressionContext();
context.setFeature( mPreviewFeature );

expression.prepare( &context );

if ( expression.hasParserError() )
{
mGeometryGeneratorWarningLabel->setText( expression.parserErrorString() );
valid = false;
}
else
{
const QVariant result = expression.evaluate( &context );
const QgsGeometry geometry = result.value<QgsGeometry>();
QgsWkbTypes::GeometryType configuredGeometryType = mGeometryGeneratorType->currentData().value<QgsWkbTypes::GeometryType>();
if ( geometry.isNull() )
{
mGeometryGeneratorWarningLabel->setText( tr( "Result of the expression is not a geometry" ) );
valid = false;
}
else if ( geometry.type() != configuredGeometryType )
{
mGeometryGeneratorWarningLabel->setText( QStringLiteral( "<p>%1</p><p><a href=\"#determineGeometryGeneratorType\">%2</a></p>" ).arg(
tr( "Result of the expression does not match configured geometry type." ),
tr( "Change to %1" ).arg( QgsWkbTypes::geometryDisplayString( geometry.type() ) ) ) );
valid = false;
}
}
}

// The collapsible groupbox internally changes the visibility of this
// Work around by setting the visibility deferred in the next event loop cycle.
QTimer *timer = new QTimer();
connect( timer, &QTimer::timeout, this, [this, valid]()
{
mGeometryGeneratorWarningLabel->setVisible( !valid );
} );
connect( timer, &QTimer::timeout, timer, &QTimer::deleteLater );
timer->start( 0 );
}

void QgsLabelingGui::determineGeometryGeneratorType()
{
if ( !mPreviewFeature.isValid() && mLayer )
mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( mPreviewFeature );

QgsExpression expression( mGeometryGenerator->text() );
QgsExpressionContext context = createExpressionContext();
context.setFeature( mPreviewFeature );

expression.prepare( &context );
const QgsGeometry geometry = expression.evaluate( &context ).value<QgsGeometry>();

mGeometryGeneratorType->setCurrentIndex( mGeometryGeneratorType->findData( geometry.type() ) );
}
13 changes: 13 additions & 0 deletions src/app/qgslabelinggui.h
Expand Up @@ -67,11 +67,24 @@ class APP_EXPORT QgsLabelingGui : public QgsTextFormatWidget, private QgsExpress
void blockInitSignals( bool block );
void syncDefinedCheckboxFrame( QgsPropertyOverrideButton *ddBtn, QCheckBox *chkBx, QFrame *f );

private slots:

/**
* Called when the geometry type is changed and
* configuration options which only work with a specific
* geometry type should be updated.
*/
void updateGeometryTypeBasedWidgets();
void showGeometryGeneratorExpressionBuilder();
void validateGeometryGeneratorExpression();
void determineGeometryGeneratorType();

private:
QgsVectorLayer *mLayer = nullptr;
const QgsPalLayerSettings &mSettings;
QgsPropertyCollection mDataDefinedProperties;
LabelMode mMode;
QgsFeature mPreviewFeature;

QgsExpressionContext createExpressionContext() const override;

Expand Down
4 changes: 2 additions & 2 deletions src/app/qgsvectorlayerproperties.cpp
Expand Up @@ -233,7 +233,7 @@ QgsVectorLayerProperties::QgsVectorLayerProperties(

if ( mLayer->dataProvider() )//enable spatial index button group if supported by provider
{
int capabilities = mLayer->dataProvider()->capabilities();
QgsVectorDataProvider::Capabilities capabilities = mLayer->dataProvider()->capabilities();
if ( !( capabilities & QgsVectorDataProvider::CreateSpatialIndex ) )
{
pbnIndex->setEnabled( false );
Expand Down Expand Up @@ -1669,7 +1669,7 @@ void QgsVectorLayerProperties::updateAuxiliaryStoragePage()
mAuxiliaryStorageKeyLineEdit->setText( alayer->joinInfo().targetFieldName() );

// update feature count
int features = alayer->featureCount();
long features = alayer->featureCount();
mAuxiliaryStorageFeaturesLineEdit->setText( QString::number( features ) );

// update actions
Expand Down
7 changes: 4 additions & 3 deletions src/core/qgsmaprendererjob.cpp
Expand Up @@ -87,7 +87,8 @@ bool QgsMapRendererJob::prepareLabelCache() const

// calculate which layers will be labeled
QSet< QgsMapLayer * > labeledLayers;
Q_FOREACH ( const QgsMapLayer *ml, mSettings.layers() )
const QList<QgsMapLayer *> layers = mSettings.layers();
for ( const QgsMapLayer *ml : layers )
{
QgsVectorLayer *vl = const_cast< QgsVectorLayer * >( qobject_cast<const QgsVectorLayer *>( ml ) );
if ( vl && QgsPalLabeling::staticWillUseLayer( vl ) )
Expand Down Expand Up @@ -314,7 +315,7 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEn
job.cached = true;
job.imageInitialized = true;
job.img = new QImage( mCache->cacheImage( ml->id() ) );
job.img->setDevicePixelRatio( mSettings.devicePixelRatio() );
job.img->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
job.renderer = nullptr;
job.context.setPainter( nullptr );
continue;
Expand All @@ -328,7 +329,7 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEn
// Flattened image for drawing when a blending mode is set
QImage *mypFlattenedImage = new QImage( mSettings.deviceOutputSize(),
mSettings.outputImageFormat() );
mypFlattenedImage->setDevicePixelRatio( mSettings.devicePixelRatio() );
mypFlattenedImage->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
if ( mypFlattenedImage->isNull() )
{
mErrors.append( Error( ml->id(), tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
Expand Down

0 comments on commit 857fe53

Please sign in to comment.