Skip to content

Commit

Permalink
Expose choice of units for geometry generator symbol layers
Browse files Browse the repository at this point in the history
Usually a geometry generator expression must return a geometry
in the associated layer's CRS. But this doesn't work in situations
where a geometry generator symbol is NOT associated with a layer,
e.g. when used in a layout item.

So add a new option to allow users to specify which unit the expression
will return geometries in, with choices of map units (the default,
previous behavior), millimeters, pixels, inches and points.

When millimeters, points, inches or pixels is selected then
the @map_geometry variable will be available for the expression,
and contain the feature's geometry in the specified units (relative
to the map frame). (The $geometry variable remains available and
still in layer CRS, in case an expression needs to calculate the
original area, perimeter, etc in real-world units while returning
results in a different unit!)

A step toward fixing qgis#39159
  • Loading branch information
nyalldawson committed Sep 17, 2021
1 parent 73ced5d commit fa2302d
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@ Set the expression to generate this geometry.
QString geometryExpression() const;
%Docstring
Gets the expression to generate this geometry.
%End

QgsUnitTypes::RenderUnit units() const;
%Docstring
Returns the unit for the geometry expression.

By default this is :py:class:`QgsUnitTypes`.MapUnits, which means that the :py:func:`~QgsGeometryGeneratorSymbolLayer.geometryExpression`
will return geometries in the associated layer's CRS.

.. seealso:: :py:func:`setUnits`

.. versionadded:: 3.22
%End

void setUnits( QgsUnitTypes::RenderUnit units );
%Docstring
Sets the ``units`` for the geometry expression.

By default this is :py:class:`QgsUnitTypes`.MapUnits, which means that the :py:func:`~QgsGeometryGeneratorSymbolLayer.geometryExpression`
will return geometries in the associated layer's CRS.

.. seealso:: :py:func:`units`

.. versionadded:: 3.22
%End

virtual QgsSymbol *subSymbol();
Expand Down Expand Up @@ -105,6 +129,7 @@ context which is available to the evaluated expression.
virtual void setColor( const QColor &color );



private:
QgsGeometryGeneratorSymbolLayer( const QgsGeometryGeneratorSymbolLayer &copy );
};
Expand Down
18 changes: 18 additions & 0 deletions python/gui/auto_generated/qgsunitselectionwidget.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,24 @@ map scale from the canvas.
:param canvas: map canvas

.. versionadded:: 2.12
%End

bool showMapScaleButton() const;
%Docstring
Returns ``True`` if the widget can show the map scale button when the Map Units option is selected.

.. seealso:: :py:func:`setShowMapScaleButton`

.. versionadded:: 3.22
%End

void setShowMapScaleButton( bool show );
%Docstring
Sets whether the widget can show the map scale button when the Map Units option is selected.

.. seealso:: :py:func:`showMapScaleButton`

.. versionadded:: 3.22
%End

signals:
Expand Down
55 changes: 52 additions & 3 deletions src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ QgsSymbolLayer *QgsGeometryGeneratorSymbolLayer::create( const QVariantMap &prop
{
symbolLayer->setSubSymbol( QgsFillSymbol::createSimple( properties ) );
}
symbolLayer->setUnits( QgsUnitTypes::decodeRenderUnit( properties.value( QStringLiteral( "units" ), QStringLiteral( "mapunits" ) ).toString() ) );

symbolLayer->restoreOldDataDefinedProperties( properties );

return symbolLayer;
Expand Down Expand Up @@ -133,6 +135,7 @@ QgsSymbolLayer *QgsGeometryGeneratorSymbolLayer::clone() const
clone->mMarkerSymbol.reset( mMarkerSymbol->clone() );

clone->setSymbolType( mSymbolType );
clone->setUnits( mUnits );

copyDataDefinedProperties( clone );
copyPaintEffect( clone );
Expand All @@ -156,6 +159,8 @@ QVariantMap QgsGeometryGeneratorSymbolLayer::properties() const
props.insert( QStringLiteral( "SymbolType" ), QStringLiteral( "Fill" ) );
break;
}
props.insert( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( mUnits ) );

return props;
}

Expand Down Expand Up @@ -222,11 +227,55 @@ void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context )

if ( context.feature() )
{
const QgsExpressionContext &expressionContext = context.renderContext().expressionContext();
QgsExpressionContext &expressionContext = context.renderContext().expressionContext();

QgsFeature f = expressionContext.feature();
const QgsGeometry geom = mExpression->evaluate( &expressionContext ).value<QgsGeometry>();
f.setGeometry( geom );

switch ( mUnits )
{
case QgsUnitTypes::RenderMapUnits:
case QgsUnitTypes::RenderUnknownUnit: // unsupported, not exposed as an option
case QgsUnitTypes::RenderMetersInMapUnits: // unsupported, not exposed as an option
case QgsUnitTypes::RenderPercentage: // unsupported, not exposed as an option
{
QgsGeometry geom = mExpression->evaluate( &expressionContext ).value<QgsGeometry>();
f.setGeometry( geom );
break;
}

case QgsUnitTypes::RenderMillimeters:
case QgsUnitTypes::RenderPixels:
case QgsUnitTypes::RenderPoints:
case QgsUnitTypes::RenderInches:
{
QgsExpressionContextScope *generatorScope = new QgsExpressionContextScope();
expressionContext.appendScope( generatorScope );

QgsGeometry transformed = f.geometry();
transformed.transform( context.renderContext().coordinateTransform() );
QTransform mapToPixel = context.renderContext().mapToPixel().transform();

// scale transform to target units
const double scale = 1 / context.renderContext().convertToPainterUnits( 1, mUnits );
mapToPixel.scale( scale, scale );

if ( mExpression->referencedVariables().contains( QStringLiteral( "map_geometry" ) ) )
{
transformed.transform( mapToPixel );
generatorScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "map_geometry" ), transformed ) );
}

QgsGeometry geom = mExpression->evaluate( &expressionContext ).value<QgsGeometry>();

geom.transform( mapToPixel.inverted() );
geom.transform( context.renderContext().coordinateTransform(), QgsCoordinateTransform::ReverseTransform );

f.setGeometry( geom );

delete expressionContext.popScope();
break;
}
}

QgsExpressionContextScope *subSymbolExpressionContextScope = mSymbol->symbolRenderContext()->expressionContextScope();

Expand Down
25 changes: 25 additions & 0 deletions src/core/symbology/qgsgeometrygeneratorsymbollayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,28 @@ class CORE_EXPORT QgsGeometryGeneratorSymbolLayer : public QgsSymbolLayer
*/
QString geometryExpression() const { return mExpression->expression(); }

/**
* Returns the unit for the geometry expression.
*
* By default this is QgsUnitTypes::MapUnits, which means that the geometryExpression()
* will return geometries in the associated layer's CRS.
*
* \see setUnits()
* \since QGIS 3.22
*/
QgsUnitTypes::RenderUnit units() const { return mUnits; }

/**
* Sets the \a units for the geometry expression.
*
* By default this is QgsUnitTypes::MapUnits, which means that the geometryExpression()
* will return geometries in the associated layer's CRS.
*
* \see units()
* \since QGIS 3.22
*/
void setUnits( QgsUnitTypes::RenderUnit units ) { mUnits = units;}

QgsSymbol *subSymbol() override { return mSymbol; }

bool setSubSymbol( QgsSymbol *symbol SIP_TRANSFER ) override;
Expand Down Expand Up @@ -104,6 +126,7 @@ class CORE_EXPORT QgsGeometryGeneratorSymbolLayer : public QgsSymbolLayer

void setColor( const QColor &color ) override;


private:
QgsGeometryGeneratorSymbolLayer( const QString &expression );

Expand All @@ -122,6 +145,8 @@ class CORE_EXPORT QgsGeometryGeneratorSymbolLayer : public QgsSymbolLayer
*/
Qgis::SymbolType mSymbolType;

QgsUnitTypes::RenderUnit mUnits = QgsUnitTypes::RenderMapUnits;

bool mRenderingFeature = false;
bool mHasRenderedFeature = false;
};
Expand Down
16 changes: 14 additions & 2 deletions src/gui/qgsunitselectionwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,11 @@ void QgsUnitSelectionWidget::toggleUnitRangeButton()
{
if ( unit() != QgsUnitTypes::RenderUnknownUnit )
{
mMapScaleButton->setVisible( unit() == QgsUnitTypes::RenderMapUnits );
mMapScaleButton->setVisible( mShowMapScaleButton && unit() == QgsUnitTypes::RenderMapUnits );
}
else
{
mMapScaleButton->setVisible( mMapUnitIdx != -1 && mUnitCombo->currentIndex() == mMapUnitIdx );
mMapScaleButton->setVisible( mShowMapScaleButton && mMapUnitIdx != -1 && mUnitCombo->currentIndex() == mMapUnitIdx );
}
}

Expand All @@ -266,6 +266,18 @@ void QgsUnitSelectionWidget::widgetChanged( const QgsMapUnitScale &scale )
emit changed();
}

bool QgsUnitSelectionWidget::showMapScaleButton() const
{
return mShowMapScaleButton;
}

void QgsUnitSelectionWidget::setShowMapScaleButton( bool show )
{
mShowMapScaleButton = show;
if ( !show )
mMapScaleButton->hide();
}


QgsMapUnitScaleDialog::QgsMapUnitScaleDialog( QWidget *parent )
: QDialog( parent )
Expand Down
17 changes: 17 additions & 0 deletions src/gui/qgsunitselectionwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,22 @@ class GUI_EXPORT QgsUnitSelectionWidget : public QWidget, private Ui::QgsUnitSel
*/
void setMapCanvas( QgsMapCanvas *canvas );

/**
* Returns TRUE if the widget can show the map scale button when the Map Units option is selected.
*
* \see setShowMapScaleButton()
* \since QGIS 3.22
*/
bool showMapScaleButton() const;

/**
* Sets whether the widget can show the map scale button when the Map Units option is selected.
*
* \see showMapScaleButton()
* \since QGIS 3.22
*/
void setShowMapScaleButton( bool show );

signals:
void changed();

Expand All @@ -237,6 +253,7 @@ class GUI_EXPORT QgsUnitSelectionWidget : public QWidget, private Ui::QgsUnitSel
QgsMapUnitScale mMapUnitScale;
int mMapUnitIdx;
QgsMapCanvas *mCanvas = nullptr;
bool mShowMapScaleButton = true;

};

Expand Down
20 changes: 20 additions & 0 deletions src/gui/symbology/qgssymbollayerwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4288,15 +4288,35 @@ QgsGeometryGeneratorSymbolLayerWidget::QgsGeometryGeneratorSymbolLayerWidget( Qg
cbxGeometryType->addItem( QgsIconUtils::iconPolygon(), tr( "Polygon / MultiPolygon" ), static_cast< int >( Qgis::SymbolType::Fill ) );
cbxGeometryType->addItem( QgsIconUtils::iconLine(), tr( "LineString / MultiLineString" ), static_cast< int >( Qgis::SymbolType::Line ) );
cbxGeometryType->addItem( QgsIconUtils::iconPoint(), tr( "Point / MultiPoint" ), static_cast< int >( Qgis::SymbolType::Marker ) );

mUnitWidget->setUnits( {QgsUnitTypes::RenderMillimeters,
QgsUnitTypes::RenderPoints,
QgsUnitTypes::RenderPixels,
QgsUnitTypes::RenderInches,
QgsUnitTypes::RenderMapUnits
} );
mUnitWidget->setShowMapScaleButton( false );

connect( modificationExpressionSelector, &QgsExpressionLineEdit::expressionChanged, this, &QgsGeometryGeneratorSymbolLayerWidget::updateExpression );
connect( cbxGeometryType, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsGeometryGeneratorSymbolLayerWidget::updateSymbolType );
connect( mUnitWidget, &QgsUnitSelectionWidget::changed, this, [ = ]
{
if ( !mBlockSignals )
{
mLayer->setUnits( mUnitWidget->unit() );
emit symbolChanged();
}
} );
}

void QgsGeometryGeneratorSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *l )
{
mBlockSignals++;
mLayer = static_cast<QgsGeometryGeneratorSymbolLayer *>( l );
modificationExpressionSelector->setExpression( mLayer->geometryExpression() );
cbxGeometryType->setCurrentIndex( cbxGeometryType->findData( static_cast< int >( mLayer->symbolType() ) ) );
mUnitWidget->setUnit( mLayer->units() );
mBlockSignals--;
}

QgsSymbolLayer *QgsGeometryGeneratorSymbolLayerWidget::symbolLayer()
Expand Down
1 change: 1 addition & 0 deletions src/gui/symbology/qgssymbollayerwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ class GUI_EXPORT QgsGeometryGeneratorSymbolLayerWidget : public QgsSymbolLayerWi

private:
QgsGeometryGeneratorSymbolLayer *mLayer = nullptr;
int mBlockSignals = 0;

private slots:
void updateExpression( const QString &string );
Expand Down
34 changes: 30 additions & 4 deletions src/ui/symbollayer/qgsgeometrygeneratorwidgetbase.ui
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,1">
<item row="2" column="0" colspan="2">
<widget class="QgsExpressionLineEdit" name="modificationExpressionSelector" native="true"/>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="cbxGeometryType"/>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Units</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
Expand All @@ -33,6 +37,22 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QgsUnitSelectionWidget" name="mUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="cbxGeometryType"/>
</item>
</layout>
</widget>
<customwidgets>
Expand All @@ -42,6 +62,12 @@
<header>qgsexpressionlineedit.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsUnitSelectionWidget</class>
<extends>QWidget</extends>
<header>qgsunitselectionwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
Expand Down

0 comments on commit fa2302d

Please sign in to comment.