[25d] Improve convertability to other layers

* Move height and angle expressions for 2.5D renderer to layer
 * Apply color based on main symbol color

This makes the transition to other renderers easy.

Fixes #14132
This commit is contained in:
Matthias Kuhn 2016-01-20 08:21:32 +01:00
parent aca49992a9
commit 8d72f13a57
25 changed files with 287 additions and 97 deletions

View File

@ -391,6 +391,8 @@ class QgsExpressionContext
static const QString EXPR_FIELDS; static const QString EXPR_FIELDS;
static const QString EXPR_FEATURE; static const QString EXPR_FEATURE;
static const QString EXPR_ORIGINAL_VALUE; static const QString EXPR_ORIGINAL_VALUE;
static const QString EXPR_SYMBOL_COLOR;
static const QString EXPR_SYMBOL_ANGLE;
}; };
/** \ingroup core /** \ingroup core
@ -488,6 +490,16 @@ class QgsExpressionContextUtils
*/ */
static QgsExpressionContextScope* mapSettingsScope( const QgsMapSettings &mapSettings ) /Factory/; static QgsExpressionContextScope* mapSettingsScope( const QgsMapSettings &mapSettings ) /Factory/;
/**
* Updates a symbol scope related to a QgsSymbolV2 to an expression context. If there is no existing scope
* provided, a new one will be generated and returned.
* @param symbol symbol to extract properties from
* @param symbolScope optional pointer to an existing scope to update
* @note added in QGIS 2.14
*/
static QgsExpressionContextScope* updateSymbolScope( const QgsSymbolV2* symbol, QgsExpressionContextScope* symbolScope = nullptr ) /Factory/;
/** Creates a new scope which contains variables and functions relating to a QgsComposition. /** Creates a new scope which contains variables and functions relating to a QgsComposition.
* For instance, number of pages and page sizes. * For instance, number of pages and page sizes.
* @param composition source composition * @param composition source composition

View File

@ -39,24 +39,6 @@ class Qgs25DRenderer : QgsFeatureRendererV2
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature, QgsRenderContext& context ); virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature, QgsRenderContext& context );
virtual QgsSymbolV2List symbols( QgsRenderContext& context ); virtual QgsSymbolV2List symbols( QgsRenderContext& context );
/**
* Get the field or expression used to determine the height of extrusion
*/
QgsDataDefined height() const;
/**
* Set the field or expression used to determine the height of extrusion
*/
void setHeight( const QgsDataDefined& height );
/**
* Get the angle for the extrusion effect
*/
int angle() const;
/**
* Set the angle for the extrusion effect
*/
void setAngle( int angle );
/** /**
* Get the roof color * Get the roof color
*/ */
@ -77,6 +59,16 @@ class Qgs25DRenderer : QgsFeatureRendererV2
*/ */
void setWallColor( const QColor& wallColor ); void setWallColor( const QColor& wallColor );
/**
* Set wall shading enabled
*/
void setWallShadingEnabled( bool enabled );
/**
* Get wall shading enabled
*/
bool wallShadingEnabled();
/** /**
* Get the shadow's color * Get the shadow's color
*/ */

View File

@ -35,4 +35,8 @@ class Qgs25DRendererWidget : QgsRendererV2Widget
Qgs25DRendererWidget( QgsVectorLayer* layer, QgsStyleV2* style, QgsFeatureRendererV2* renderer ); Qgs25DRendererWidget( QgsVectorLayer* layer, QgsStyleV2* style, QgsFeatureRendererV2* renderer );
QgsFeatureRendererV2* renderer(); QgsFeatureRendererV2* renderer();
private:
void apply();
}; };

View File

@ -15,6 +15,14 @@ class QgsRendererV2PropertiesDialog : QDialog
*/ */
void setMapCanvas( QgsMapCanvas* canvas ); void setMapCanvas( QgsMapCanvas* canvas );
signals:
/**
* Emitted when expression context variables on the associated
* vector layers have been changed. Will request the parent dialog
* to re-synchronize with the variables.
*/
void layerVariablesChanged();
public slots: public slots:
//! called when user changes renderer type //! called when user changes renderer type
void rendererChanged(); void rendererChanged();

View File

@ -34,6 +34,19 @@ class QgsRendererV2Widget : QWidget
*/ */
const QgsVectorLayer* vectorLayer() const; const QgsVectorLayer* vectorLayer() const;
/**
* This method should be called whenever the renderer is actually set on the layer.
*/
void applyChanges();
signals:
/**
* Emitted when expression context variables on the associated
* vector layers have been changed. Will request the parent dialog
* to re-synchronize with the variables.
*/
void layerVariablesChanged();
protected: protected:
/** Subclasses may provide the capability of changing multiple symbols at once by implementing the following two methods /** Subclasses may provide the capability of changing multiple symbols at once by implementing the following two methods
and by connecting the slot contextMenuViewCategories(const QPoint&)*/ and by connecting the slot contextMenuViewCategories(const QPoint&)*/
@ -58,6 +71,13 @@ class QgsRendererV2Widget : QWidget
virtual void copy(); virtual void copy();
virtual void paste(); virtual void paste();
private:
/**
* This will be called whenever the renderer is set on a layer.
* This can be overwritten in subclasses.
*/
virtual void apply();
}; };

View File

@ -50,9 +50,11 @@ static QgsExpressionContext _getExpressionContext( const void* context )
if ( layer ) if ( layer )
expContext << QgsExpressionContextUtils::layerScope( layer ); expContext << QgsExpressionContextUtils::layerScope( layer );
expContext << QgsExpressionContextUtils::updateSymbolScope( nullptr );
//TODO - show actual value //TODO - show actual value
expContext.setOriginalValueVariable( QVariant() ); expContext.setOriginalValueVariable( QVariant() );
expContext.setHighlightedVariables( QStringList() << QgsExpressionContext::EXPR_ORIGINAL_VALUE ); expContext.setHighlightedVariables( QStringList() << QgsExpressionContext::EXPR_ORIGINAL_VALUE << QgsExpressionContext::EXPR_SYMBOL_COLOR );
return expContext; return expContext;
} }

View File

@ -1263,10 +1263,12 @@ void QgsVectorLayerProperties::updateSymbologyPage()
mRendererDialog = new QgsRendererV2PropertiesDialog( layer, QgsStyleV2::defaultStyle(), true ); mRendererDialog = new QgsRendererV2PropertiesDialog( layer, QgsStyleV2::defaultStyle(), true );
mRendererDialog->setMapCanvas( QgisApp::instance()->mapCanvas() ); mRendererDialog->setMapCanvas( QgisApp::instance()->mapCanvas() );
connect( mRendererDialog, SIGNAL( layerVariablesChanged() ), this, SLOT( updateVariableEditor() ) );
// display the menu to choose the output format (fix #5136) // display the menu to choose the output format (fix #5136)
mActionSaveStyleAs->setText( tr( "Save Style" ) ); mActionSaveStyleAs->setText( tr( "Save Style" ) );
mActionSaveStyleAs->setMenu( mSaveAsMenu ); mActionSaveStyleAs->setMenu( mSaveAsMenu );
QObject::disconnect( mActionSaveStyleAs, SIGNAL( triggered() ), this, SLOT( saveStyleAs_clicked() ) ); disconnect( mActionSaveStyleAs, SIGNAL( triggered() ), this, SLOT( saveStyleAs_clicked() ) );
} }
else else
{ {

View File

@ -4546,6 +4546,9 @@ void QgsExpression::initVariableHelp()
//symbol variables //symbol variables
gVariableHelpTexts.insert( "geometry_part_count", QCoreApplication::translate( "variable_help", "Number of parts in rendered feature's geometry." ) ); gVariableHelpTexts.insert( "geometry_part_count", QCoreApplication::translate( "variable_help", "Number of parts in rendered feature's geometry." ) );
gVariableHelpTexts.insert( "geometry_part_num", QCoreApplication::translate( "variable_help", "Current geometry part number for feature being rendered." ) ); gVariableHelpTexts.insert( "geometry_part_num", QCoreApplication::translate( "variable_help", "Current geometry part number for feature being rendered." ) );
gVariableHelpTexts.insert( "symbol_color", QCoreApplication::translate( "symbol_color", "Color of symbol used to render the feature." ) );
gVariableHelpTexts.insert( "symbol_angle", QCoreApplication::translate( "symbol_angle", "Angle of symbol used to render the feature (valid for marker symbols only)." ) );
} }
QString QgsExpression::variableHelpText( const QString &variableName, bool showValue, const QVariant &value ) QString QgsExpression::variableHelpText( const QString &variableName, bool showValue, const QVariant &value )

View File

@ -33,6 +33,8 @@
const QString QgsExpressionContext::EXPR_FIELDS( "_fields_" ); const QString QgsExpressionContext::EXPR_FIELDS( "_fields_" );
const QString QgsExpressionContext::EXPR_FEATURE( "_feature_" ); const QString QgsExpressionContext::EXPR_FEATURE( "_feature_" );
const QString QgsExpressionContext::EXPR_ORIGINAL_VALUE( "value" ); const QString QgsExpressionContext::EXPR_ORIGINAL_VALUE( "value" );
const QString QgsExpressionContext::EXPR_SYMBOL_COLOR( "symbol_color" );
const QString QgsExpressionContext::EXPR_SYMBOL_ANGLE( "symbol_angle" );
// //
// QgsExpressionContextScope // QgsExpressionContextScope
@ -716,6 +718,24 @@ QgsExpressionContextScope* QgsExpressionContextUtils::mapSettingsScope( const Qg
return scope; return scope;
} }
QgsExpressionContextScope* QgsExpressionContextUtils::updateSymbolScope( const QgsSymbolV2* symbol, QgsExpressionContextScope* symbolScope )
{
if ( !symbolScope )
symbolScope = new QgsExpressionContextScope( QObject::tr( "Symbol Scope" ) );
symbolScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_SYMBOL_COLOR, symbol ? symbol->color() : QColor(), true ) );
double angle = 0.0;
const QgsMarkerSymbolV2* markerSymbol = dynamic_cast< const QgsMarkerSymbolV2* >( symbol );
if ( markerSymbol )
{
angle = markerSymbol->angle();
}
symbolScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_SYMBOL_ANGLE, angle, true ) );
return symbolScope;
}
QgsExpressionContextScope *QgsExpressionContextUtils::compositionScope( const QgsComposition *composition ) QgsExpressionContextScope *QgsExpressionContextUtils::compositionScope( const QgsComposition *composition )
{ {
QgsExpressionContextScope* scope = new QgsExpressionContextScope( QObject::tr( "Composition" ) ); QgsExpressionContextScope* scope = new QgsExpressionContextScope( QObject::tr( "Composition" ) );

View File

@ -28,6 +28,7 @@ class QgsComposition;
class QgsComposerItem; class QgsComposerItem;
class QgsAtlasComposition; class QgsAtlasComposition;
class QgsMapSettings; class QgsMapSettings;
class QgsSymbolV2;
/** \ingroup core /** \ingroup core
* \class QgsScopedExpressionFunction * \class QgsScopedExpressionFunction
@ -425,6 +426,8 @@ class CORE_EXPORT QgsExpressionContext
static const QString EXPR_FIELDS; static const QString EXPR_FIELDS;
static const QString EXPR_FEATURE; static const QString EXPR_FEATURE;
static const QString EXPR_ORIGINAL_VALUE; static const QString EXPR_ORIGINAL_VALUE;
static const QString EXPR_SYMBOL_COLOR;
static const QString EXPR_SYMBOL_ANGLE;
private: private:
@ -519,6 +522,16 @@ class CORE_EXPORT QgsExpressionContextUtils
*/ */
static QgsExpressionContextScope* mapSettingsScope( const QgsMapSettings &mapSettings ); static QgsExpressionContextScope* mapSettingsScope( const QgsMapSettings &mapSettings );
/**
* Updates a symbol scope related to a QgsSymbolV2 to an expression context. If there is no existing scope
* provided, a new one will be generated and returned.
* @param symbol symbol to extract properties from
* @param symbolScope optional pointer to an existing scope to update
* @note added in QGIS 2.14
*/
static QgsExpressionContextScope* updateSymbolScope( const QgsSymbolV2* symbol, QgsExpressionContextScope* symbolScope = nullptr );
/** Creates a new scope which contains variables and functions relating to a QgsComposition. /** Creates a new scope which contains variables and functions relating to a QgsComposition.
* For instance, number of pages and page sizes. * For instance, number of pages and page sizes.
* @param composition source composition * @param composition source composition

View File

@ -281,19 +281,33 @@ QList<QgsLabelFeature*> QgsVectorLayerLabelProvider::labelFeatures( QgsRenderCon
request.setSubsetOfAttributes( attrNames, mFields ); request.setSubsetOfAttributes( attrNames, mFields );
QgsFeatureIterator fit = mSource->getFeatures( request ); QgsFeatureIterator fit = mSource->getFeatures( request );
QgsExpressionContextScope* symbolScope = nullptr;
QgsFeature fet; QgsFeature fet;
while ( fit.nextFeature( fet ) ) while ( fit.nextFeature( fet ) )
{ {
QScopedPointer<QgsGeometry> obstacleGeometry; QScopedPointer<QgsGeometry> obstacleGeometry;
if ( fet.constGeometry()->type() == QGis::Point ) if ( mRenderer )
{ {
//point feature, use symbol bounds as obstacle QgsSymbolV2List symbols = mRenderer->originalSymbolsForFeature( fet, ctx );
obstacleGeometry.reset( getPointObstacleGeometry( fet, ctx, mRenderer ) ); if ( !symbols.isEmpty() && fet.constGeometry()->type() == QGis::Point )
{
//point feature, use symbol bounds as obstacle
obstacleGeometry.reset( QgsVectorLayerLabelProvider::getPointObstacleGeometry( fet, ctx, symbols ) );
}
if ( !symbols.isEmpty() )
{
symbolScope = QgsExpressionContextUtils::updateSymbolScope( symbols.at( 0 ), symbolScope );
if ( !ctx.expressionContext().scopes().contains( symbolScope ) )
ctx.expressionContext().appendScope( symbolScope );
}
} }
ctx.expressionContext().setFeature( fet ); ctx.expressionContext().setFeature( fet );
registerFeature( fet, ctx, obstacleGeometry.data() ); registerFeature( fet, ctx, obstacleGeometry.data() );
} }
if ( ctx.expressionContext().lastScope() == symbolScope )
ctx.expressionContext().popScope();
if ( mRenderer ) if ( mRenderer )
mRenderer->stopRender( ctx ); mRenderer->stopRender( ctx );
@ -308,14 +322,11 @@ void QgsVectorLayerLabelProvider::registerFeature( QgsFeature& feature, QgsRende
mLabels << label; mLabels << label;
} }
QgsGeometry* QgsVectorLayerLabelProvider::getPointObstacleGeometry( QgsFeature& fet, QgsRenderContext& context, QgsFeatureRendererV2* renderer ) QgsGeometry* QgsVectorLayerLabelProvider::getPointObstacleGeometry( QgsFeature& fet, QgsRenderContext& context, const QgsSymbolV2List& symbols )
{ {
if ( !fet.constGeometry() || fet.constGeometry()->isEmpty() || fet.constGeometry()->type() != QGis::Point || !renderer ) if ( !fet.constGeometry() || fet.constGeometry()->isEmpty() || fet.constGeometry()->type() != QGis::Point )
return nullptr; return nullptr;
//calculate bounds for symbols for feature
QgsSymbolV2List symbols = renderer->originalSymbolsForFeature( fet, context );
bool isMultiPoint = fet.constGeometry()->geometry()->nCoordinates() > 1; bool isMultiPoint = fet.constGeometry()->geometry()->nCoordinates() > 1;
QgsAbstractGeometryV2* obstacleGeom = nullptr; QgsAbstractGeometryV2* obstacleGeom = nullptr;
if ( isMultiPoint ) if ( isMultiPoint )

View File

@ -17,9 +17,11 @@
#define QGSVECTORLAYERLABELPROVIDER_H #define QGSVECTORLAYERLABELPROVIDER_H
#include "qgslabelingenginev2.h" #include "qgslabelingenginev2.h"
#include "qgsrendererv2.h"
class QgsAbstractFeatureSource; class QgsAbstractFeatureSource;
class QgsFeatureRendererV2; class QgsFeatureRendererV2;
class QgsSymbolV2;
/** /**
* @brief The QgsVectorLayerLabelProvider class implements a label provider * @brief The QgsVectorLayerLabelProvider class implements a label provider
@ -80,10 +82,10 @@ class CORE_EXPORT QgsVectorLayerLabelProvider : public QgsAbstractLabelProvider
* point, and ensures that labels will not overlap large or offset points. * point, and ensures that labels will not overlap large or offset points.
* @param fet point feature * @param fet point feature
* @param context render context * @param context render context
* @param renderer renderer used for layer, required to determine symbols rendered for point feature * @param symbols symbols rendered for point feature
* @note added in QGIS 2.14 * @note added in QGIS 2.14
*/ */
static QgsGeometry* getPointObstacleGeometry( QgsFeature& fet, QgsRenderContext& context, QgsFeatureRendererV2* renderer ); static QgsGeometry* getPointObstacleGeometry( QgsFeature& fet, QgsRenderContext& context, const QgsSymbolV2List& symbols );
protected: protected:
//! initialization method - called from constructors //! initialization method - called from constructors

View File

@ -286,6 +286,9 @@ void QgsVectorLayerRenderer::setGeometryCachePointer( QgsGeometryCache* cache )
void QgsVectorLayerRenderer::drawRendererV2( QgsFeatureIterator& fit ) void QgsVectorLayerRenderer::drawRendererV2( QgsFeatureIterator& fit )
{ {
QgsExpressionContextScope* symbolScope = QgsExpressionContextUtils::updateSymbolScope( nullptr );
mContext.expressionContext().appendScope( symbolScope );
QgsFeature fet; QgsFeature fet;
while ( fit.nextFeature( fet ) ) while ( fit.nextFeature( fet ) )
{ {
@ -332,10 +335,18 @@ void QgsVectorLayerRenderer::drawRendererV2( QgsFeatureIterator& fit )
if ( mContext.labelingEngineV2() ) if ( mContext.labelingEngineV2() )
{ {
QScopedPointer<QgsGeometry> obstacleGeometry; QScopedPointer<QgsGeometry> obstacleGeometry;
if ( fet.constGeometry()->type() == QGis::Point ) QgsSymbolV2List symbols = mRendererV2->originalSymbolsForFeature( fet, mContext );
if ( !symbols.isEmpty() && fet.constGeometry()->type() == QGis::Point )
{ {
obstacleGeometry.reset( QgsVectorLayerLabelProvider::getPointObstacleGeometry( fet, mContext, mRendererV2 ) ); obstacleGeometry.reset( QgsVectorLayerLabelProvider::getPointObstacleGeometry( fet, mContext, symbols ) );
} }
if ( !symbols.isEmpty() )
{
QgsExpressionContextUtils::updateSymbolScope( symbols.at( 0 ), symbolScope );
}
if ( mLabelProvider ) if ( mLabelProvider )
{ {
mLabelProvider->registerFeature( fet, mContext, obstacleGeometry.data() ); mLabelProvider->registerFeature( fet, mContext, obstacleGeometry.data() );
@ -355,6 +366,8 @@ void QgsVectorLayerRenderer::drawRendererV2( QgsFeatureIterator& fit )
} }
} }
mContext.expressionContext().popScope();
stopRendererV2( nullptr ); stopRendererV2( nullptr );
} }
@ -371,6 +384,9 @@ void QgsVectorLayerRenderer::drawRendererV2Levels( QgsFeatureIterator& fit )
selRenderer->startRender( mContext, mFields ); selRenderer->startRender( mContext, mFields );
} }
QgsExpressionContextScope* symbolScope = QgsExpressionContextUtils::updateSymbolScope( nullptr );
mContext.expressionContext().appendScope( symbolScope );
// 1. fetch features // 1. fetch features
QgsFeature fet; QgsFeature fet;
while ( fit.nextFeature( fet ) ) while ( fit.nextFeature( fet ) )
@ -382,6 +398,7 @@ void QgsVectorLayerRenderer::drawRendererV2Levels( QgsFeatureIterator& fit )
{ {
qDebug( "rendering stop!" ); qDebug( "rendering stop!" );
stopRendererV2( selRenderer ); stopRendererV2( selRenderer );
mContext.expressionContext().popScope();
return; return;
} }
@ -420,10 +437,18 @@ void QgsVectorLayerRenderer::drawRendererV2Levels( QgsFeatureIterator& fit )
if ( mContext.labelingEngineV2() ) if ( mContext.labelingEngineV2() )
{ {
QScopedPointer<QgsGeometry> obstacleGeometry; QScopedPointer<QgsGeometry> obstacleGeometry;
if ( fet.constGeometry()->type() == QGis::Point ) QgsSymbolV2List symbols = mRendererV2->originalSymbolsForFeature( fet, mContext );
if ( !symbols.isEmpty() && fet.constGeometry()->type() == QGis::Point )
{ {
obstacleGeometry.reset( QgsVectorLayerLabelProvider::getPointObstacleGeometry( fet, mContext, mRendererV2 ) ); obstacleGeometry.reset( QgsVectorLayerLabelProvider::getPointObstacleGeometry( fet, mContext, symbols ) );
} }
if ( !symbols.isEmpty() )
{
QgsExpressionContextUtils::updateSymbolScope( symbols.at( 0 ), symbolScope );
}
if ( mLabelProvider ) if ( mLabelProvider )
{ {
mLabelProvider->registerFeature( fet, mContext, obstacleGeometry.data() ); mLabelProvider->registerFeature( fet, mContext, obstacleGeometry.data() );
@ -435,6 +460,8 @@ void QgsVectorLayerRenderer::drawRendererV2Levels( QgsFeatureIterator& fit )
} }
} }
mContext.expressionContext().popScope();
// find out the order // find out the order
QgsSymbolV2LevelOrder levels; QgsSymbolV2LevelOrder levels;
QgsSymbolV2List symbols = mRendererV2->symbols( mContext ); QgsSymbolV2List symbols = mRendererV2->symbols( mContext );

View File

@ -48,6 +48,16 @@
" )" \ " )" \
")" ")"
#define WALL_SHADING_EXPRESSION \
"set_color_part( " \
" @symbol_color," \
" 'value'," \
" 40 + 20 * abs( $pi - azimuth( " \
" point_n( geometry_n($geometry, @geometry_part_num) , 1 ), " \
" point_n( geometry_n($geometry, @geometry_part_num) , 2 )" \
" ) ) " \
")"
Qgs25DRenderer::Qgs25DRenderer() Qgs25DRenderer::Qgs25DRenderer()
: QgsFeatureRendererV2( "25dRenderer" ) : QgsFeatureRendererV2( "25dRenderer" )
{ {
@ -67,6 +77,8 @@ Qgs25DRenderer::Qgs25DRenderer()
roofProperties.insert( "symbolType", "Fill" ); roofProperties.insert( "symbolType", "Fill" );
QgsSymbolLayerV2* roof = QgsGeometryGeneratorSymbolLayerV2::create( roofProperties ); QgsSymbolLayerV2* roof = QgsGeometryGeneratorSymbolLayerV2::create( roofProperties );
floor->setLocked( true );
mSymbol->appendSymbolLayer( floor ); mSymbol->appendSymbolLayer( floor );
mSymbol->appendSymbolLayer( walls ); mSymbol->appendSymbolLayer( walls );
mSymbol->appendSymbolLayer( roof ); mSymbol->appendSymbolLayer( roof );
@ -81,12 +93,11 @@ Qgs25DRenderer::Qgs25DRenderer()
setRoofColor( QColor( "#fdbf6f" ) ); setRoofColor( QColor( "#fdbf6f" ) );
setWallColor( QColor( "#777777" ) ); setWallColor( QColor( "#777777" ) );
wallLayer()->setDataDefinedProperty( "color", new QgsDataDefined( QString( WALL_SHADING_EXPRESSION ) ) );
setShadowSpread( 4 ); setShadowSpread( 4 );
setShadowColor( QColor( "#1111111" ) ); setShadowColor( QColor( "#1111111" ) );
setHeight( QString( "20" ) );
setAngle( 40 );
QgsFeatureRequest::OrderBy orderBy; QgsFeatureRequest::OrderBy orderBy;
orderBy << QgsFeatureRequest::OrderByClause( orderBy << QgsFeatureRequest::OrderByClause(
ORDER_BY_EXPRESSION, ORDER_BY_EXPRESSION,
@ -100,8 +111,6 @@ QDomElement Qgs25DRenderer::save( QDomDocument& doc )
QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME ); QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
rendererElem.setAttribute( "type", "25dRenderer" ); rendererElem.setAttribute( "type", "25dRenderer" );
rendererElem.setAttribute( "height", mHeight.expressionOrField() );
rendererElem.setAttribute( "angle", mAngle );
QDomElement symbolElem = QgsSymbolLayerV2Utils::saveSymbol( "symbol", mSymbol.data(), doc ); QDomElement symbolElem = QgsSymbolLayerV2Utils::saveSymbol( "symbol", mSymbol.data(), doc );
@ -112,19 +121,14 @@ QDomElement Qgs25DRenderer::save( QDomDocument& doc )
QgsFeatureRendererV2* Qgs25DRenderer::create( QDomElement& element ) QgsFeatureRendererV2* Qgs25DRenderer::create( QDomElement& element )
{ {
Q_UNUSED( element )
Qgs25DRenderer* renderer = new Qgs25DRenderer(); Qgs25DRenderer* renderer = new Qgs25DRenderer();
renderer->mHeight.setField( element.attribute( "height" ) );
renderer->mAngle = element.attribute( "angle", "45" ).toInt();
return renderer; return renderer;
} }
void Qgs25DRenderer::startRender( QgsRenderContext& context, const QgsFields& fields ) void Qgs25DRenderer::startRender( QgsRenderContext& context, const QgsFields& fields )
{ {
QgsExpressionContextScope* scope = new QgsExpressionContextScope( "2.5D Renderer" );
scope->setVariable( "qgis_25d_height", mHeight.expressionOrField() );
scope->setVariable( "qgis_25d_angle", mAngle );
context.expressionContext().appendScope( scope );
mSymbol->startRender( context, &fields ); mSymbol->startRender( context, &fields );
} }
@ -142,8 +146,6 @@ QgsFeatureRendererV2* Qgs25DRenderer::clone() const
{ {
Qgs25DRenderer* c = new Qgs25DRenderer(); Qgs25DRenderer* c = new Qgs25DRenderer();
c->mSymbol.reset( mSymbol->clone() ); c->mSymbol.reset( mSymbol->clone() );
c->mAngle = mAngle;
c->mHeight = mHeight;
return c; return c;
} }
@ -162,26 +164,6 @@ QgsSymbolV2List Qgs25DRenderer::symbols( QgsRenderContext& context )
return lst; return lst;
} }
QgsDataDefined Qgs25DRenderer::height() const
{
return mHeight;
}
void Qgs25DRenderer::setHeight( const QgsDataDefined& height )
{
mHeight = height;
}
int Qgs25DRenderer::angle() const
{
return mAngle;
}
void Qgs25DRenderer::setAngle( int angle )
{
mAngle = angle;
}
QgsFillSymbolLayerV2* Qgs25DRenderer::roofLayer() const QgsFillSymbolLayerV2* Qgs25DRenderer::roofLayer() const
{ {
return static_cast<QgsFillSymbolLayerV2*>( mSymbol->symbolLayer( 2 )->subSymbol()->symbolLayer( 0 ) ); return static_cast<QgsFillSymbolLayerV2*>( mSymbol->symbolLayer( 2 )->subSymbol()->symbolLayer( 0 ) );
@ -238,6 +220,16 @@ void Qgs25DRenderer::setWallColor( const QColor& wallColor )
wallLayer()->setOutlineColor( wallColor ); wallLayer()->setOutlineColor( wallColor );
} }
void Qgs25DRenderer::setWallShadingEnabled( bool enabled )
{
wallLayer()->getDataDefinedProperty( "color" )->setActive( enabled );
}
bool Qgs25DRenderer::wallShadingEnabled()
{
return wallLayer()->getDataDefinedProperty( "color" )->isActive();
}
QColor Qgs25DRenderer::roofColor() const QColor Qgs25DRenderer::roofColor() const
{ {
return roofLayer()->fillColor(); return roofLayer()->fillColor();

View File

@ -43,24 +43,6 @@ class CORE_EXPORT Qgs25DRenderer : public QgsFeatureRendererV2
virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature, QgsRenderContext& context ) override; virtual QgsSymbolV2* symbolForFeature( QgsFeature& feature, QgsRenderContext& context ) override;
virtual QgsSymbolV2List symbols( QgsRenderContext& context ) override; virtual QgsSymbolV2List symbols( QgsRenderContext& context ) override;
/**
* Get the field or expression used to determine the height of extrusion
*/
QgsDataDefined height() const;
/**
* Set the field or expression used to determine the height of extrusion
*/
void setHeight( const QgsDataDefined& height );
/**
* Get the angle for the extrusion effect
*/
int angle() const;
/**
* Set the angle for the extrusion effect
*/
void setAngle( int angle );
/** /**
* Get the roof color * Get the roof color
*/ */
@ -81,6 +63,16 @@ class CORE_EXPORT Qgs25DRenderer : public QgsFeatureRendererV2
*/ */
void setWallColor( const QColor& wallColor ); void setWallColor( const QColor& wallColor );
/**
* Set wall shading enabled
*/
void setWallShadingEnabled( bool enabled );
/**
* Get wall shading enabled
*/
bool wallShadingEnabled();
/** /**
* Get the shadow's color * Get the shadow's color
*/ */
@ -123,9 +115,6 @@ class CORE_EXPORT Qgs25DRenderer : public QgsFeatureRendererV2
QgsOuterGlowEffect* glowEffect() const; QgsOuterGlowEffect* glowEffect() const;
QScopedPointer<QgsSymbolV2> mSymbol; QScopedPointer<QgsSymbolV2> mSymbol;
QgsDataDefined mHeight;
int mAngle;
}; };
#endif // QGS25DRENDERER_H #endif // QGS25DRENDERER_H

View File

@ -441,7 +441,7 @@ void QgsSymbolV2::startRender( QgsRenderContext& context, const QgsFields* field
QgsSymbolV2RenderContext symbolContext( context, outputUnit(), mAlpha, false, mRenderHints, nullptr, fields, mapUnitScale() ); QgsSymbolV2RenderContext symbolContext( context, outputUnit(), mAlpha, false, mRenderHints, nullptr, fields, mapUnitScale() );
QgsExpressionContextScope* scope = new QgsExpressionContextScope( QApplication::translate( "QgsSymbolV2", "Symbol Scope" ) ); QgsExpressionContextScope* scope = QgsExpressionContextUtils::updateSymbolScope( this );
mSymbolRenderContext->setExpressionContextScope( scope ); mSymbolRenderContext->setExpressionContextScope( scope );
@ -714,6 +714,7 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
} }
context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() ); context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() );
QgsExpressionContextUtils::updateSymbolScope( this, mSymbolRenderContext->expressionContextScope() );
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_count", segmentizedGeometry->geometry()->partCount() ); mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_count", segmentizedGeometry->geometry()->partCount() );
mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_num", 1 ); mSymbolRenderContext->expressionContextScope()->setVariable( "geometry_part_num", 1 );

View File

@ -15,6 +15,8 @@
***************************************************************************/ ***************************************************************************/
#include "qgs25drendererwidget.h" #include "qgs25drendererwidget.h"
#include "qgsmaplayerstylemanager.h"
Qgs25DRendererWidget::Qgs25DRendererWidget( QgsVectorLayer* layer, QgsStyleV2* style, QgsFeatureRendererV2* renderer ) Qgs25DRendererWidget::Qgs25DRendererWidget( QgsVectorLayer* layer, QgsStyleV2* style, QgsFeatureRendererV2* renderer )
: QgsRendererV2Widget( layer, style ) : QgsRendererV2Widget( layer, style )
, mRenderer( nullptr ) , mRenderer( nullptr )
@ -42,13 +44,19 @@ Qgs25DRendererWidget::Qgs25DRendererWidget( QgsVectorLayer* layer, QgsStyleV2* s
} }
mHeightWidget->setLayer( layer ); mHeightWidget->setLayer( layer );
mHeightWidget->setField( mRenderer->height().expressionOrField() );
mAngleWidget->setValue( mRenderer->angle() ); QgsExpressionContextScope* scope = QgsExpressionContextUtils::layerScope( mLayer );
QVariant height = scope->variable( "qgis_25d_height" );
QVariant angle = scope->variable( "qgis_25d_angle" );
mHeightWidget->setField( height.isNull() ? "10" : height.toString() );
mAngleWidget->setValue( angle.isNull() ? 70 : angle.toDouble() );
mWallColorButton->setColor( mRenderer->wallColor() ); mWallColorButton->setColor( mRenderer->wallColor() );
mRoofColorButton->setColor( mRenderer->roofColor() ); mRoofColorButton->setColor( mRenderer->roofColor() );
mShadowColorButton->setColor( mRenderer->shadowColor() ); mShadowColorButton->setColor( mRenderer->shadowColor() );
mShadowEnabledWidget->setEnabled( mRenderer->shadowEnabled() ); mShadowEnabledWidget->setEnabled( mRenderer->shadowEnabled() );
mShadowSizeWidget->setValue( mRenderer->shadowSpread() ); mShadowSizeWidget->setValue( mRenderer->shadowSpread() );
mWallExpositionShading->setChecked( mRenderer->wallShadingEnabled() );
connect( mAngleWidget, SIGNAL( valueChanged( int ) ), this, SLOT( updateRenderer() ) ); connect( mAngleWidget, SIGNAL( valueChanged( int ) ), this, SLOT( updateRenderer() ) );
connect( mHeightWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( updateRenderer() ) ); connect( mHeightWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( updateRenderer() ) );
@ -57,6 +65,7 @@ Qgs25DRendererWidget::Qgs25DRendererWidget( QgsVectorLayer* layer, QgsStyleV2* s
connect( mShadowColorButton, SIGNAL( colorChanged( QColor ) ), this, SLOT( updateRenderer() ) ); connect( mShadowColorButton, SIGNAL( colorChanged( QColor ) ), this, SLOT( updateRenderer() ) );
connect( mShadowEnabledWidget, SIGNAL( toggled( bool ) ), this, SLOT( updateRenderer() ) ); connect( mShadowEnabledWidget, SIGNAL( toggled( bool ) ), this, SLOT( updateRenderer() ) );
connect( mShadowSizeWidget, SIGNAL( valueChanged( double ) ), this, SLOT( updateRenderer() ) ); connect( mShadowSizeWidget, SIGNAL( valueChanged( double ) ), this, SLOT( updateRenderer() ) );
connect( mWallExpositionShading, SIGNAL( toggled( bool ) ), this, SLOT( updateRenderer() ) );
} }
QgsFeatureRendererV2* Qgs25DRendererWidget::renderer() QgsFeatureRendererV2* Qgs25DRendererWidget::renderer()
@ -66,13 +75,20 @@ QgsFeatureRendererV2* Qgs25DRendererWidget::renderer()
void Qgs25DRendererWidget::updateRenderer() void Qgs25DRendererWidget::updateRenderer()
{ {
mRenderer->setHeight( QgsDataDefined( mHeightWidget->currentText() ) );
mRenderer->setAngle( mAngleWidget->value() );
mRenderer->setRoofColor( mRoofColorButton->color() ); mRenderer->setRoofColor( mRoofColorButton->color() );
mRenderer->setWallColor( mWallColorButton->color() ); mRenderer->setWallColor( mWallColorButton->color() );
mRenderer->setShadowColor( mShadowColorButton->color() ); mRenderer->setShadowColor( mShadowColorButton->color() );
mRenderer->setShadowEnabled( mShadowEnabledWidget->isChecked() ); mRenderer->setShadowEnabled( mShadowEnabledWidget->isChecked() );
mRenderer->setShadowSpread( mShadowSizeWidget->value() ); mRenderer->setShadowSpread( mShadowSizeWidget->value() );
mRenderer->setWallShadingEnabled( mWallExpositionShading->isChecked() );
}
void Qgs25DRendererWidget::apply()
{
QgsExpressionContextUtils::setLayerVariable( mLayer, "qgis_25d_height", mHeightWidget->currentText() );
QgsExpressionContextUtils::setLayerVariable( mLayer, "qgis_25d_angle", mAngleWidget->value() );
emit layerVariablesChanged();
} }
QgsRendererV2Widget* Qgs25DRendererWidget::create( QgsVectorLayer* layer, QgsStyleV2* style, QgsFeatureRendererV2* renderer ) QgsRendererV2Widget* Qgs25DRendererWidget::create( QgsVectorLayer* layer, QgsStyleV2* style, QgsFeatureRendererV2* renderer )

View File

@ -45,6 +45,8 @@ class GUI_EXPORT Qgs25DRendererWidget : public QgsRendererV2Widget, Ui::Qgs25DRe
void updateRenderer(); void updateRenderer();
private: private:
void apply() override;
Qgs25DRenderer* mRenderer; Qgs25DRenderer* mRenderer;
}; };

View File

@ -223,6 +223,7 @@ void QgsRendererV2PropertiesDialog::rendererChanged()
if ( mMapCanvas ) if ( mMapCanvas )
mActiveWidget->setMapCanvas( mMapCanvas ); mActiveWidget->setMapCanvas( mMapCanvas );
changeOrderBy( mActiveWidget->renderer()->orderBy() ); changeOrderBy( mActiveWidget->renderer()->orderBy() );
connect( mActiveWidget, SIGNAL( layerVariablesChanged() ), this, SIGNAL( layerVariablesChanged() ) );
} }
} }
else else
@ -239,6 +240,8 @@ void QgsRendererV2PropertiesDialog::apply()
return; return;
} }
mActiveWidget->applyChanges();
QgsFeatureRendererV2* renderer = mActiveWidget->renderer(); QgsFeatureRendererV2* renderer = mActiveWidget->renderer();
if ( renderer ) if ( renderer )
{ {

View File

@ -47,6 +47,14 @@ class GUI_EXPORT QgsRendererV2PropertiesDialog : public QDialog, private Ui::Qgs
*/ */
void setMapCanvas( QgsMapCanvas* canvas ); void setMapCanvas( QgsMapCanvas* canvas );
signals:
/**
* Emitted when expression context variables on the associated
* vector layers have been changed. Will request the parent dialog
* to re-synchronize with the variables.
*/
void layerVariablesChanged();
public slots: public slots:
//! called when user changes renderer type //! called when user changes renderer type
void rendererChanged(); void rendererChanged();

View File

@ -261,11 +261,16 @@ void QgsRendererV2Widget::setMapCanvas( QgsMapCanvas *canvas )
mMapCanvas = canvas; mMapCanvas = canvas;
} }
const QgsMapCanvas*QgsRendererV2Widget::mapCanvas() const const QgsMapCanvas* QgsRendererV2Widget::mapCanvas() const
{ {
return mMapCanvas; return mMapCanvas;
} }
void QgsRendererV2Widget::applyChanges()
{
apply();
}
//////////// ////////////
@ -580,3 +585,8 @@ void QgsDataDefinedWidthDialog::setDataDefined( QgsSymbolV2 *symbol, const QgsDa
{ {
static_cast<QgsLineSymbolV2*>( symbol )->setDataDefinedWidth( dd ); static_cast<QgsLineSymbolV2*>( symbol )->setDataDefinedWidth( dd );
} }
void QgsRendererV2Widget::apply()
{
}

View File

@ -69,6 +69,19 @@ class GUI_EXPORT QgsRendererV2Widget : public QWidget
*/ */
const QgsVectorLayer* vectorLayer() const { return mLayer; } const QgsVectorLayer* vectorLayer() const { return mLayer; }
/**
* This method should be called whenever the renderer is actually set on the layer.
*/
void applyChanges();
signals:
/**
* Emitted when expression context variables on the associated
* vector layers have been changed. Will request the parent dialog
* to re-synchronize with the variables.
*/
void layerVariablesChanged();
protected: protected:
QgsVectorLayer* mLayer; QgsVectorLayer* mLayer;
QgsStyleV2* mStyle; QgsStyleV2* mStyle;
@ -100,6 +113,13 @@ class GUI_EXPORT QgsRendererV2Widget : public QWidget
virtual void copy() {} virtual void copy() {}
virtual void paste() {} virtual void paste() {}
private:
/**
* This will be called whenever the renderer is set on a layer.
* This can be overwritten in subclasses.
*/
virtual void apply();
}; };

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>602</width> <width>657</width>
<height>395</height> <height>587</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -62,7 +62,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2"> <item row="4" column="0" colspan="2">
<widget class="QGroupBox" name="mShadowEnabledWidget"> <widget class="QGroupBox" name="mShadowEnabledWidget">
<property name="title"> <property name="title">
<string>Shadow</string> <string>Shadow</string>
@ -98,6 +98,13 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="3" column="1">
<widget class="QCheckBox" name="mWallExpositionShading">
<property name="text">
<string>Shade walls based on exposition</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -114,6 +121,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="label_7">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Advanced Styling&lt;/span&gt;&lt;br/&gt;This page helps to configure the 2.5D effect as easily as possible with some basic parameters.&lt;/p&gt;&lt;p&gt;Once you have finished the basic styling, you can can convert this to another renderer (single, categorized, graduated) and fine-tune the appearance to your liking.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Overlay problems&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Features are rendered based on their distance to the camera. It is sometimes possible that parts of a feature are in front of another feature by mistake. This happens if any part of the overlapped feature is closer to the camera than the overlapping feature.&lt;/p&gt;&lt;p&gt;In such cases you can avoid rendering problems by cutting the feature in front into smaller pieces.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>

View File

@ -101,5 +101,21 @@ class TestQgsSymbolExpressionVariables(TestCase):
self.assertTrue(result) self.assertTrue(result)
def testSymbolColor(self):
# Create rulebased style
sym1 = QgsFillSymbolV2.createSimple({'color': '#ff0000'})
renderer = QgsSingleSymbolRendererV2(sym1)
renderer.symbols()[0].symbolLayers()[0].setDataDefinedProperty('color', 'set_color_part( @symbol_color, \'value\', "Value" * 4)')
self.layer.setRendererV2(renderer)
# Setup rendering check
renderchecker = QgsMultiRenderChecker()
renderchecker.setMapSettings(self.mapsettings)
renderchecker.setControlName('expected_symbol_color_variable')
result = renderchecker.runTest('symbol_color_variable')
self.assertTrue(result)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB