Skip to content
Permalink
Browse files

[FEATURE] assistant for varying line width

The qgssizescalewidget as been modified to handle line symbols as well
as markers.
The scale expression has been enhanced to support general exponentials
(i.e. eponents different from Flanery .57 and Area .5a)
The assistant as been modified to disply the exponent when Exponential
scaling is required.
  • Loading branch information
vmora committed Nov 5, 2015
1 parent c445ac1 commit 6d1d213973026016db2e6e1e6efc7867f365585d
@@ -36,8 +36,9 @@ class QgsScaleExpression : QgsExpression
* @param minSize minimum size
* @param maxSize maximum size
* @param nullSize size in case expression evaluates to NULL
* @param exponent to use in case of Exponential type
*/
QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize = 0 );
QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize = 0, double exponent = 1 );

operator bool() const;

@@ -74,6 +75,11 @@ class QgsScaleExpression : QgsExpression
*/
double nullSize() const;

/** Returns the exponent of the exponential expression.
* @see exponent
*/
double exponent() const;

/** Returns the base expression string (or field reference) used for
* calculating the values to be mapped to a size.
*/
@@ -209,6 +209,8 @@ class QgsMarkerLineSymbolLayerV2 : QgsLineSymbolLayerV2

QSet<QString> usedAttributes() const;

void setDataDefinedProperty( const QString& property, QgsDataDefined* dataDefined /Transfer/ );

protected:

void renderPolylineInterval( const QPolygonF& points, QgsSymbolV2RenderContext& context );
@@ -26,21 +26,39 @@ QgsScaleExpression::QgsScaleExpression( const QString& expression )
, mMinValue( 0 )
, mMaxValue( 100 )
, mNullSize( 0 )
, mExponent( 1 )
{
init();
}

QgsScaleExpression::QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize )
: QgsExpression( createExpression( type, baseExpression, minValue, maxValue, minSize, maxSize, nullSize ) )
QgsScaleExpression::QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize, double exponent )
: QgsExpression( createExpression( type, baseExpression, minValue, maxValue, minSize, maxSize, nullSize, exponent ) )
, mExpression( baseExpression )
, mType( type )
, mMinSize( minSize )
, mMaxSize( maxSize )
, mMinValue( minValue )
, mMaxValue( maxValue )
, mNullSize( nullSize )
, mExponent( 1 )
{

switch ( type )
{
case Linear:
mExponent = 1;
break;
case Area:
mExponent = .5;
break;
case Flannery:
mExponent = .57;
break;
case Exponential:
mExponent = exponent;
break;
case Unknown:
break;
}
}

void QgsScaleExpression::init()
@@ -76,17 +94,15 @@ void QgsScaleExpression::init()
}
else if ( "scale_exp" == Functions()[f->fnIndex()]->name() )
{
const double exp = QgsExpression( args[5]->dump() ).evaluate().toDouble( &ok );
mExponent = QgsExpression( args[5]->dump() ).evaluate().toDouble( &ok );
if ( ! ok )
return;
if ( qgsDoubleNear( exp, 0.57, 0.001 ) )
if ( qgsDoubleNear( mExponent, 0.57, 0.001 ) )
mType = Flannery;
else if ( qgsDoubleNear( exp, 0.5, 0.001 ) )
else if ( qgsDoubleNear( mExponent, 0.5, 0.001 ) )
mType = Area;
else
{
return;
}
mType = Exponential;
}
else
{
@@ -111,24 +127,24 @@ void QgsScaleExpression::init()
mExpression = args[0]->dump();
}

QString QgsScaleExpression::createExpression( Type type, const QString & baseExpr, double minValue, double maxValue, double minSize, double maxSize, double nullSize )
QString QgsScaleExpression::createExpression( Type type, const QString & baseExpr, double minValue, double maxValue, double minSize, double maxSize, double nullSize, double exponent )
{
QString minValueString = QString::number( minValue );
QString maxValueString = QString::number( maxValue );
QString minSizeString = QString::number( minSize );
QString maxSizeString = QString::number( maxSize );
QString nullSizeString = QString::number( nullSize );
QString exponentString = QString::number( exponent );

switch ( type )
{
case Linear:
return QString( "coalesce(scale_linear(%1, %2, %3, %4, %5), %6)" ).arg( baseExpr, minValueString, maxValueString, minSizeString, maxSizeString, nullSizeString );

case Area:
return QString( "coalesce(scale_exp(%1, %2, %3, %4, %5, 0.5), %6)" ).arg( baseExpr, minValueString, maxValueString, minSizeString, maxSizeString, nullSizeString );

case Flannery:
return QString( "coalesce(scale_exp(%1, %2, %3, %4, %5, 0.57), %6)" ).arg( baseExpr, minValueString, maxValueString, minSizeString, maxSizeString, nullSizeString );
case Exponential:
return QString( "coalesce(scale_exp(%1, %2, %3, %4, %5, %6), %7)" ).arg( baseExpr, minValueString, maxValueString, minSizeString, maxSizeString, exponentString, nullSizeString );

case Unknown:
break;
@@ -144,10 +160,9 @@ double QgsScaleExpression::size( double value ) const
return mMinSize + ( qBound( mMinValue, value, mMaxValue ) - mMinValue ) * ( mMaxSize - mMinSize ) / ( mMaxValue - mMinValue );

case Area:
return mMinSize + qPow( qBound( mMinValue, value, mMaxValue ) - mMinValue, .5 ) * ( mMaxSize - mMinSize ) / qPow( mMaxValue - mMinValue, .5 );

case Flannery:
return mMinSize + qPow( qBound( mMinValue, value, mMaxValue ) - mMinValue, .57 ) * ( mMaxSize - mMinSize ) / qPow( mMaxValue - mMinValue, .57 );
case Exponential:
return mMinSize + qPow( qBound( mMinValue, value, mMaxValue ) - mMinValue, mExponent ) * ( mMaxSize - mMinSize ) / qPow( mMaxValue - mMinValue, mExponent );

case Unknown:
break;
@@ -34,6 +34,7 @@ class CORE_EXPORT QgsScaleExpression : public QgsExpression
Linear,
Area,
Flannery,
Exponential,
Unknown
};

@@ -52,8 +53,9 @@ class CORE_EXPORT QgsScaleExpression : public QgsExpression
* @param minSize minimum size
* @param maxSize maximum size
* @param nullSize size in case expression evaluates to NULL
* @param exponent to use in case of Exponential type
*/
QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize = 0 );
QgsScaleExpression( Type type, const QString& baseExpression, double minValue, double maxValue, double minSize, double maxSize, double nullSize = 0, double exponent = 1 );

operator bool() const { return ! mExpression.isEmpty(); }

@@ -90,6 +92,11 @@ class CORE_EXPORT QgsScaleExpression : public QgsExpression
*/
double nullSize() const { return mNullSize; }

/** Returns the exponent of the exponential expression.
* @see exponent
*/
double exponent() const { return mExponent; }

/** Returns the base expression string (or field reference) used for
* calculating the values to be mapped to a size.
*/
@@ -108,9 +115,10 @@ class CORE_EXPORT QgsScaleExpression : public QgsExpression
double mMinValue;
double mMaxValue;
double mNullSize;
double mExponent;

void init();
static QString createExpression( Type type, const QString& baseExpr, double minValue, double maxValue, double minSize, double maxSize, double nullSize );
static QString createExpression( Type type, const QString& baseExpr, double minValue, double maxValue, double minSize, double maxSize, double nullSize, double exponent );

};

@@ -1502,6 +1502,15 @@ void QgsMarkerLineSymbolLayerV2::setWidth( double width )
mMarker->setSize( width );
}

void QgsMarkerLineSymbolLayerV2::setDataDefinedProperty( const QString& property, QgsDataDefined* dataDefined )
{
if ( property == QgsSymbolLayerV2::EXPR_WIDTH && mMarker && dataDefined )
{
mMarker->setDataDefinedSize( *dataDefined );
}
QgsLineSymbolLayerV2::setDataDefinedProperty( property, dataDefined );
}

double QgsMarkerLineSymbolLayerV2::width() const
{
return mMarker->size();
@@ -259,6 +259,9 @@ class CORE_EXPORT QgsMarkerLineSymbolLayerV2 : public QgsLineSymbolLayerV2

QSet<QString> usedAttributes() const override;

void setDataDefinedProperty( const QString& property, QgsDataDefined* dataDefined ) override;


protected:

void renderPolylineInterval( const QPolygonF& points, QgsSymbolV2RenderContext& context );
@@ -48,33 +48,60 @@ class ItemDelegate : public QItemDelegate

};

// RAII class to block a QObject signal until destroyed
struct SignalBlocker
{
SignalBlocker( QObject * object )
: mObject( object )
{
mObject->blockSignals( true );
}
~SignalBlocker()
{
mObject->blockSignals( false );
}
private:
QObject * mObject;
};

void QgsSizeScaleWidget::setFromSymbol()
{
if ( !mSymbol )
{
return;
}

QgsDataDefined ddSize = mSymbol->dataDefinedSize();
QgsDataDefined ddSize;
if ( dynamic_cast< const QgsMarkerSymbolV2*>( mSymbol ) )
{
ddSize = static_cast< const QgsMarkerSymbolV2*>( mSymbol )->dataDefinedSize();
}
else if ( dynamic_cast< const QgsLineSymbolV2*>( mSymbol ) )
{
ddSize = dynamic_cast< const QgsLineSymbolV2*>( mSymbol )->dataDefinedWidth();
}

QgsScaleExpression expr( ddSize.expressionString() );
if ( expr )
{
for ( int i = 0; i < scaleMethodComboBox->count(); i++ )
{
if ( scaleMethodComboBox->itemData( i ).toInt() == int( expr.type() ) )
{
scaleMethodComboBox->setCurrentIndex( i );
( SignalBlocker( scaleMethodComboBox ), scaleMethodComboBox->setCurrentIndex( i ) );
break;
}
}

mExpressionWidget->setField( expr.baseExpression() );

minValueSpinBox->setValue( expr.minValue() );
maxValueSpinBox->setValue( expr.maxValue() );
minSizeSpinBox->setValue( expr.minSize() );
maxSizeSpinBox->setValue( expr.maxSize() );
nullSizeSpinBox->setValue( expr.nullSize() );
// the (,) is used to create the Blocker first, then call the setter
// the unamed SignalBlocker is destroyed at the end of the line (semicolumn)
( SignalBlocker( mExpressionWidget ), mExpressionWidget->setField( expr.baseExpression() ) );
( SignalBlocker( minValueSpinBox ), minValueSpinBox->setValue( expr.minValue() ) );
( SignalBlocker( maxValueSpinBox ), maxValueSpinBox->setValue( expr.maxValue() ) );
( SignalBlocker( minSizeSpinBox ), minSizeSpinBox->setValue( expr.minSize() ) );
( SignalBlocker( maxSizeSpinBox ), maxSizeSpinBox->setValue( expr.maxSize() ) );
( SignalBlocker( nullSizeSpinBox ), nullSizeSpinBox->setValue( expr.nullSize() ) );
( SignalBlocker( exponentSpinBox ), exponentSpinBox->setValue( expr.exponent() ) );
}
updatePreview();
}
@@ -104,7 +131,7 @@ static QgsExpressionContext _getExpressionContext( const void* context )
return expContext;
}

QgsSizeScaleWidget::QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsMarkerSymbolV2 * symbol )
QgsSizeScaleWidget::QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsSymbolV2 * symbol )
: mSymbol( symbol )
// we just use the minimumValue and maximumValue from the layer, unfortunately they are
// non const, so we get the layer from the registry instead
@@ -145,9 +172,22 @@ QgsSizeScaleWidget::QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsM
mExpressionWidget->setLayer( mLayer );
}

scaleMethodComboBox->addItem( tr( "Flannery" ), int( QgsScaleExpression::Flannery ) );
scaleMethodComboBox->addItem( tr( "Surface" ), int( QgsScaleExpression::Area ) );
scaleMethodComboBox->addItem( tr( "Radius" ), int( QgsScaleExpression::Linear ) );
if ( dynamic_cast<const QgsMarkerSymbolV2*>( mSymbol ) )
{
scaleMethodComboBox->addItem( tr( "Flannery" ), int( QgsScaleExpression::Flannery ) );
scaleMethodComboBox->addItem( tr( "Surface" ), int( QgsScaleExpression::Area ) );
scaleMethodComboBox->addItem( tr( "Radius" ), int( QgsScaleExpression::Linear ) );
}
else if ( dynamic_cast<const QgsLineSymbolV2*>( mSymbol ) )
{
scaleMethodComboBox->addItem( tr( "Exponential" ), int( QgsScaleExpression::Exponential ) );
scaleMethodComboBox->addItem( tr( "Linear" ), int( QgsScaleExpression::Linear ) );
}
else
{
Q_ASSERT( false );
}


minSizeSpinBox->setShowClearButton( false );
maxSizeSpinBox->setShowClearButton( false );
@@ -163,6 +203,7 @@ QgsSizeScaleWidget::QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsM
connect( minValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
connect( maxValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
connect( nullSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
connect( exponentSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
//potentially very expensive for large layers:
connect( mExpressionWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( computeFromLayerTriggered() ) );
connect( scaleMethodComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( updatePreview() ) );
@@ -187,7 +228,8 @@ QgsScaleExpression *QgsSizeScaleWidget::createExpression() const
maxValueSpinBox->value(),
minSizeSpinBox->value(),
maxSizeSpinBox->value(),
nullSizeSpinBox->value() );
nullSizeSpinBox->value(),
exponentSpinBox->value() );
}

void QgsSizeScaleWidget::updatePreview()
@@ -196,21 +238,40 @@ void QgsSizeScaleWidget::updatePreview()
return;

QScopedPointer<QgsScaleExpression> expr( createExpression() );

if ( expr->type() == QgsScaleExpression::Exponential )
exponentSpinBox->show();
else
exponentSpinBox->hide();

QList<double> breaks = QgsSymbolLayerV2Utils::prettyBreaks( expr->minValue(), expr->maxValue(), 4 );

treeView->setIconSize( QSize( 512, 512 ) );
mPreviewList.clear();
int widthMax = 0;
for ( int i = 0; i < breaks.length(); i++ )
{
QScopedPointer< QgsMarkerSymbolV2 > symbol( static_cast<QgsMarkerSymbolV2*>( mSymbol->clone() ) );
symbol->setDataDefinedSize( QgsDataDefined() );
symbol->setDataDefinedAngle( QgsDataDefined() ); // to avoid symbol not beeing drawn
symbol->setSize( expr->size( breaks[i] ) );
QgsSymbolV2LegendNode node( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), 0 ) );
const QSize sz( node.minimumIconSize() );
node.setIconSize( sz );
QScopedPointer< QStandardItem > item( new QStandardItem( node.data( Qt::DecorationRole ).value<QPixmap>(), QString::number( breaks[i] ) ) );
QScopedPointer< QgsSymbolV2LegendNode > node;
if ( dynamic_cast<const QgsMarkerSymbolV2*>( mSymbol ) )
{
QScopedPointer< QgsMarkerSymbolV2 > symbol( static_cast<QgsMarkerSymbolV2*>( mSymbol->clone() ) );
symbol->setDataDefinedSize( QgsDataDefined() );
symbol->setDataDefinedAngle( QgsDataDefined() ); // to avoid symbol not beeing drawn
symbol->setSize( expr->size( breaks[i] ) );
node.reset( new QgsSymbolV2LegendNode( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), 0 ) ) );
}
else if ( dynamic_cast<const QgsLineSymbolV2*>( mSymbol ) )
{
QScopedPointer< QgsLineSymbolV2 > symbol( static_cast<QgsLineSymbolV2*>( mSymbol->clone() ) );
symbol->setDataDefinedWidth( QgsDataDefined() );
symbol->setWidth( expr->size( breaks[i] ) );
node.reset( new QgsSymbolV2LegendNode( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), 0 ) ) );

}

const QSize sz( node->minimumIconSize() );
node->setIconSize( sz );
QScopedPointer< QStandardItem > item( new QStandardItem( node->data( Qt::DecorationRole ).value<QPixmap>(), QString::number( breaks[i] ) ) );
widthMax = qMax( sz.width(), widthMax );
mPreviewList.appendRow( item.take() );
}
@@ -269,8 +330,8 @@ void QgsSizeScaleWidget::computeFromLayerTriggered()
min = qMin( min, value );
}
}
minValueSpinBox->setValue( min );
maxValueSpinBox->setValue( max );
( SignalBlocker( minValueSpinBox ), minValueSpinBox->setValue( min ) );
( SignalBlocker( maxSizeSpinBox ), maxValueSpinBox->setValue( max ) );
updatePreview();
}

0 comments on commit 6d1d213

Please sign in to comment.
You can’t perform that action at this time.