[FEATURE] Rule-based labeling

Work in progress

This code has been funded by Tuscany Region (Italy) - SITA (CIG: 63526840AE) and commissioned to Gis3W s.a.s.
This commit is contained in:
Martin Dobias 2015-09-23 10:12:56 +08:00
parent 287590eea2
commit 1782d1a505
23 changed files with 744 additions and 231 deletions

View File

@ -163,6 +163,7 @@ SET(QGIS_CORE_SRCS
qgsrenderchecker.cpp
qgsrendercontext.cpp
qgsrectangle.cpp
qgsrulebasedlabeling.cpp
qgsrunprocess.cpp
qgsscalecalculator.cpp
qgsscaleexpression.cpp
@ -186,6 +187,7 @@ SET(QGIS_CORE_SRCS
qgsvectorlayerfeatureiterator.cpp
qgsvectorlayerimport.cpp
qgsvectorlayerjoinbuffer.cpp
qgsvectorlayerlabeling.cpp
qgsvectorlayerlabelprovider.cpp
qgsvectorlayerrenderer.cpp
qgsvectorlayerundocommand.cpp

View File

@ -388,11 +388,6 @@ namespace pal
}
}
QString LabelPosition::getLayerName() const
{
return feature->layer()->name();
}
void LabelPosition::setConflictsWithObstacle( bool conflicts )
{
mHasObstacleConflict = conflicts;

View File

@ -164,9 +164,6 @@ namespace pal
if ( nextPart ) nextPart->setProblemIds( probFid, lpId );
}
/** Return pointer to layer's name. used for stats */
QString getLayerName() const;
/** Returns the candidate label position's geographical cost.
* @see setCost
*/

View File

@ -45,8 +45,9 @@
namespace pal
{
Layer::Layer( const QString &lyrName, Arrangement arrangement, double defaultPriority, bool active, bool toLabel, Pal *pal, bool displayAll )
: mName( lyrName )
Layer::Layer( QgsAbstractLabelProvider* provider, const QString& name, Arrangement arrangement, double defaultPriority, bool active, bool toLabel, Pal *pal, bool displayAll )
: mProvider( provider )
, mName( name )
, pal( pal )
, mObstacleType( PolygonInterior )
, mActive( active )

View File

@ -81,6 +81,9 @@ namespace pal
*/
int featureCount() { return mHashtable.size(); }
/** Returns pointer to the associated provider */
QgsAbstractLabelProvider* provider() const { return mProvider; }
/** Returns the layer's name.
*/
QString name() const { return mName; }
@ -242,7 +245,8 @@ namespace pal
void chopFeaturesAtRepeatDistance();
protected:
QString mName; /* unique */
QgsAbstractLabelProvider* mProvider; // not owned
QString mName;
/** List of feature parts */
QLinkedList<FeaturePart*> mFeatureParts;
@ -279,7 +283,8 @@ namespace pal
/**
* \brief Create a new layer
*
* @param lyrName layer's name
* @param provider Associated provider
* @param name Name of the layer (for stats, debugging - does not need to be unique)
* @param arrangement Arrangement mode : how to place candidates
* @param defaultPriority layer's prioriry (0 is the best, 1 the worst)
* @param active is the layer is active (currently displayed)
@ -288,7 +293,7 @@ namespace pal
* @param displayAll if true, all features will be labelled even though overlaps occur
*
*/
Layer( const QString& lyrName, Arrangement arrangement, double defaultPriority, bool active, bool toLabel, Pal *pal, bool displayAll = false );
Layer( QgsAbstractLabelProvider* provider, const QString& name, Arrangement arrangement, double defaultPriority, bool active, bool toLabel, Pal *pal, bool displayAll = false );
/** Add newly created feature part into r tree and to the list */
void addFeaturePart( FeaturePart* fpart, const QString &labelText = QString() );

View File

@ -90,38 +90,17 @@ namespace pal
}
QList<Layer*> Pal::getLayers()
{
// TODO make const ! or whatever else
return mLayers.values();
}
Layer *Pal::getLayer( const QString& layerName )
{
mMutex.lock();
if ( !mLayers.contains( layerName ) )
{
mMutex.unlock();
throw new PalException::UnknownLayer();
}
Layer* result = mLayers.value( layerName );
mMutex.unlock();
return result;
}
void Pal::removeLayer( Layer *layer )
{
if ( !layer )
return;
mMutex.lock();
QString key = mLayers.key( layer, QString() );
if ( !key.isEmpty() )
if ( QgsAbstractLabelProvider* key = mLayers.key( layer, 0 ) )
{
mLayers.remove( key );
delete layer;
}
delete layer;
mMutex.unlock();
}
@ -138,21 +117,14 @@ namespace pal
//finishGEOS();
}
Layer* Pal::addLayer( const QString &layerName, Arrangement arrangement, double defaultPriority, bool active, bool toLabel, bool displayAll )
Layer* Pal::addLayer( QgsAbstractLabelProvider* provider, const QString& layerName, Arrangement arrangement, double defaultPriority, bool active, bool toLabel, bool displayAll )
{
mMutex.lock();
//check if layer is already known
if ( mLayers.contains( layerName ) )
{
mMutex.unlock();
//There is already a layer with this name, so we just return the existing one.
//Sometimes the same layer is added twice (e.g. datetime split with otf-reprojection)
return mLayers.value( layerName );
}
Q_ASSERT( !mLayers.contains( provider ) );
Layer* layer = new Layer( layerName, arrangement, defaultPriority, active, toLabel, this, displayAll );
mLayers.insert( layerName, layer );
Layer* layer = new Layer( provider, layerName, arrangement, defaultPriority, active, toLabel, this, displayAll );
mLayers.insert( provider, layer );
mMutex.unlock();
return layer;
@ -266,7 +238,7 @@ namespace pal
return true;
}
Problem* Pal::extract( const QStringList& layerNames, double lambda_min, double phi_min, double lambda_max, double phi_max )
Problem* Pal::extract( double lambda_min, double phi_min, double lambda_max, double phi_max )
{
// to store obstacles
RTree<FeaturePart*, double, 2, double> *obstacles = new RTree<FeaturePart*, double, 2, double>();
@ -308,14 +280,12 @@ namespace pal
// first step : extract features from layers
int previousFeatureCount = 0;
Layer *layer;
QStringList layersWithFeaturesInBBox;
mMutex.lock();
Q_FOREACH ( const QString& layerName, layerNames )
Q_FOREACH ( Layer* layer, mLayers.values() )
{
layer = mLayers.value( layerName, 0 );
if ( !layer )
{
// invalid layer name
@ -503,15 +473,10 @@ namespace pal
return prob;
}
std::list<LabelPosition*>* Pal::labeller( double bbox[4], PalStat **stats, bool displayAll )
{
return labeller( mLayers.keys(), bbox, stats, displayAll );
}
/*
* BIG MACHINE
*/
std::list<LabelPosition*>* Pal::labeller( const QStringList& layerNames, double bbox[4], PalStat **stats, bool displayAll )
std::list<LabelPosition*>* Pal::labeller( double bbox[4], PalStat **stats, bool displayAll )
{
#ifdef _DEBUG_
std::cout << "LABELLER (selection)" << std::endl;
@ -536,7 +501,7 @@ namespace pal
t.start();
// First, extract the problem
if (( prob = extract( layerNames, bbox[0], bbox[1], bbox[2], bbox[3] ) ) == NULL )
if (( prob = extract( bbox[0], bbox[1], bbox[2], bbox[3] ) ) == NULL )
{
// nothing to be done => return an empty result set
if ( stats )
@ -611,7 +576,7 @@ namespace pal
Problem* Pal::extractProblem( double bbox[4] )
{
return extract( mLayers.keys(), bbox[0], bbox[1], bbox[2], bbox[3] );
return extract( bbox[0], bbox[1], bbox[2], bbox[3] );
}
std::list<LabelPosition*>* Pal::solveProblem( Problem* prob, bool displayAll )

View File

@ -39,6 +39,8 @@
// TODO ${MAJOR} ${MINOR} etc instead of 0.2
class QgsAbstractLabelProvider;
/**
*
* \section intro_sec Introduction
@ -127,6 +129,7 @@ namespace pal
/**
* \brief add a new layer
*
* @param provider Provider associated with the layer
* @param layerName layer's name
* @param arrangement Howto place candidates
* @param defaultPriority layer's prioriry (0 is the best, 1 the worst)
@ -138,25 +141,7 @@ namespace pal
*
* @todo add symbolUnit
*/
Layer* addLayer( const QString& layerName, Arrangement arrangement, double defaultPriority, bool active, bool toLabel, bool displayAll = false );
/**
* \brief Look for a layer
*
* @param layerName name of layer to search
*
* @throws PalException::UnkownLayer
*
* @return a pointer on layer or NULL if layer not exist
*/
Layer *getLayer( const QString &layerName );
/**
* \brief get all layers
*
* @return a list of all layers
*/
QList<Layer*> getLayers();
Layer* addLayer( QgsAbstractLabelProvider* provider, const QString& layerName, Arrangement arrangement, double defaultPriority, bool active, bool toLabel, bool displayAll = false );
/**
* \brief remove a layer
@ -177,24 +162,6 @@ namespace pal
*/
std::list<LabelPosition*> *labeller( double bbox[4], PalStat **stats, bool displayAll );
/**
* \brief the labeling machine
* Active layers are specifiend through layersName array
* @todo add obstacles and tolabel arrays
* @param layerNames names of layers to label
* @param bbox map extent
* @param stat will be filled with labelling process statistics, can be NULL
* @param displayAll if true, all feature will be labelled even though overlaps occur
*
* @todo UnknownLayer will be ignored ? should throw exception or not ???
*
* @return A list of label to display on map
*/
std::list<LabelPosition*> *labeller( const QStringList& layerNames,
double bbox[4],
PalStat **stat,
bool displayAll );
typedef bool ( *FnIsCancelled )( void* ctx );
/** Register a function that returns whether this job has been cancelled - PAL calls it during the computation */
@ -279,7 +246,7 @@ namespace pal
private:
QHash< QString, Layer* > mLayers;
QHash< QgsAbstractLabelProvider*, Layer* > mLayers;
QMutex mMutex;
@ -326,14 +293,12 @@ namespace pal
* \brief Problem factory
* Extract features to label and generates candidates for them,
* respects to a bounding box
* @param layersName layers name to be extracted
* @param lambda_min xMin bounding-box
* @param phi_min yMin bounding-box
* @param lambda_max xMax bounding-box
* @param phi_max yMax bounding-box
*/
Problem* extract( const QStringList& layersName,
double lambda_min, double phi_min,
Problem* extract( double lambda_min, double phi_min,
double lambda_max, double phi_max );

View File

@ -2674,7 +2674,7 @@ namespace pal
int k;
for ( i = 0; i < nbft; i++ )
{
lyrName = mLabelPositions.at( featStartId[i] )->getLayerName();
lyrName = mLabelPositions.at( featStartId[i] )->getFeaturePart()->feature()->provider()->name();
k = -1;
for ( j = 0; j < stats->nbLayers; j++ )
{

View File

@ -48,6 +48,7 @@ QgsLabelingEngineV2::~QgsLabelingEngineV2()
{
delete mResults;
qDeleteAll( mProviders );
qDeleteAll( mSubProviders );
}
void QgsLabelingEngineV2::addProvider( QgsAbstractLabelProvider* provider )
@ -65,6 +66,95 @@ void QgsLabelingEngineV2::removeProvider( QgsAbstractLabelProvider* provider )
}
}
void QgsLabelingEngineV2::processProvider( QgsAbstractLabelProvider* provider, QgsRenderContext& context, pal::Pal& p )
{
// how to place the labels
pal::Arrangement arrangement;
switch ( provider->placement() )
{
case QgsPalLayerSettings::AroundPoint: arrangement = pal::P_POINT; break;
case QgsPalLayerSettings::OverPoint: arrangement = pal::P_POINT_OVER; break;
case QgsPalLayerSettings::Line: arrangement = pal::P_LINE; break;
case QgsPalLayerSettings::Curved: arrangement = pal::P_CURVED; break;
case QgsPalLayerSettings::Horizontal: arrangement = pal::P_HORIZ; break;
case QgsPalLayerSettings::Free: arrangement = pal::P_FREE; break;
default: Q_ASSERT( "unsupported placement" && 0 ); return;
}
QgsAbstractLabelProvider::Flags flags = provider->flags();
// create the pal layer
pal::Layer* l = p.addLayer( provider,
provider->name(),
arrangement,
provider->priority(),
true,
flags.testFlag( QgsAbstractLabelProvider::DrawLabels ),
flags.testFlag( QgsAbstractLabelProvider::DrawAllLabels ) );
// extra flags for placement of labels for linestrings
l->setArrangementFlags(( pal::LineArrangementFlags ) provider->linePlacementFlags() );
// set label mode (label per feature is the default)
l->setLabelMode( flags.testFlag( QgsAbstractLabelProvider::LabelPerFeaturePart ) ? pal::Layer::LabelPerFeaturePart : pal::Layer::LabelPerFeature );
// set whether adjacent lines should be merged
l->setMergeConnectedLines( flags.testFlag( QgsAbstractLabelProvider::MergeConnectedLines ) );
// set obstacle type
switch ( provider->obstacleType() )
{
case QgsPalLayerSettings::PolygonInterior:
l->setObstacleType( pal::PolygonInterior );
break;
case QgsPalLayerSettings::PolygonBoundary:
l->setObstacleType( pal::PolygonBoundary );
break;
}
// set whether location of centroid must be inside of polygons
l->setCentroidInside( flags.testFlag( QgsAbstractLabelProvider::CentroidMustBeInside ) );
// set whether labels must fall completely within the polygon
l->setFitInPolygonOnly( flags.testFlag( QgsAbstractLabelProvider::FitInPolygonOnly ) );
// set how to show upside-down labels
pal::Layer::UpsideDownLabels upsdnlabels;
switch ( provider->upsidedownLabels() )
{
case QgsPalLayerSettings::Upright: upsdnlabels = pal::Layer::Upright; break;
case QgsPalLayerSettings::ShowDefined: upsdnlabels = pal::Layer::ShowDefined; break;
case QgsPalLayerSettings::ShowAll: upsdnlabels = pal::Layer::ShowAll; break;
default: Q_ASSERT( "unsupported upside-down label setting" && 0 ); return;
}
l->setUpsidedownLabels( upsdnlabels );
QList<QgsLabelFeature*> features = provider->labelFeatures( context );
foreach ( QgsLabelFeature* feature, features )
{
try
{
l->registerFeature( feature );
}
catch ( std::exception &e )
{
Q_UNUSED( e );
QgsDebugMsgLevel( QString( "Ignoring feature %1 due PAL exception:" ).arg( feature->id() ) + QString::fromLatin1( e.what() ), 4 );
continue;
}
}
// any sub-providers?
Q_FOREACH ( QgsAbstractLabelProvider* subProvider, provider->subProviders() )
{
mSubProviders << subProvider;
processProvider( subProvider, context, p );
}
}
void QgsLabelingEngineV2::run( QgsRenderContext& context )
{
pal::Pal p;
@ -92,82 +182,7 @@ void QgsLabelingEngineV2::run( QgsRenderContext& context )
// for each provider: get labels and register them in PAL
foreach ( QgsAbstractLabelProvider* provider, mProviders )
{
// how to place the labels
pal::Arrangement arrangement;
switch ( provider->placement() )
{
case QgsPalLayerSettings::AroundPoint: arrangement = pal::P_POINT; break;
case QgsPalLayerSettings::OverPoint: arrangement = pal::P_POINT_OVER; break;
case QgsPalLayerSettings::Line: arrangement = pal::P_LINE; break;
case QgsPalLayerSettings::Curved: arrangement = pal::P_CURVED; break;
case QgsPalLayerSettings::Horizontal: arrangement = pal::P_HORIZ; break;
case QgsPalLayerSettings::Free: arrangement = pal::P_FREE; break;
default: Q_ASSERT( "unsupported placement" && 0 ); return;
}
QgsAbstractLabelProvider::Flags flags = provider->flags();
// create the pal layer
pal::Layer* l = p.addLayer( provider->id(),
arrangement,
provider->priority(),
true,
flags.testFlag( QgsAbstractLabelProvider::DrawLabels ),
flags.testFlag( QgsAbstractLabelProvider::DrawAllLabels ) );
// extra flags for placement of labels for linestrings
l->setArrangementFlags(( pal::LineArrangementFlags ) provider->linePlacementFlags() );
// set label mode (label per feature is the default)
l->setLabelMode( flags.testFlag( QgsAbstractLabelProvider::LabelPerFeaturePart ) ? pal::Layer::LabelPerFeaturePart : pal::Layer::LabelPerFeature );
// set whether adjacent lines should be merged
l->setMergeConnectedLines( flags.testFlag( QgsAbstractLabelProvider::MergeConnectedLines ) );
// set obstacle type
switch ( provider->obstacleType() )
{
case QgsPalLayerSettings::PolygonInterior:
l->setObstacleType( pal::PolygonInterior );
break;
case QgsPalLayerSettings::PolygonBoundary:
l->setObstacleType( pal::PolygonBoundary );
break;
}
// set whether location of centroid must be inside of polygons
l->setCentroidInside( flags.testFlag( QgsAbstractLabelProvider::CentroidMustBeInside ) );
// set whether labels must fall completely within the polygon
l->setFitInPolygonOnly( flags.testFlag( QgsAbstractLabelProvider::FitInPolygonOnly ) );
// set how to show upside-down labels
pal::Layer::UpsideDownLabels upsdnlabels;
switch ( provider->upsidedownLabels() )
{
case QgsPalLayerSettings::Upright: upsdnlabels = pal::Layer::Upright; break;
case QgsPalLayerSettings::ShowDefined: upsdnlabels = pal::Layer::ShowDefined; break;
case QgsPalLayerSettings::ShowAll: upsdnlabels = pal::Layer::ShowAll; break;
default: Q_ASSERT( "unsupported upside-down label setting" && 0 ); return;
}
l->setUpsidedownLabels( upsdnlabels );
QList<QgsLabelFeature*> features = provider->labelFeatures( context );
foreach ( QgsLabelFeature* feature, features )
{
try
{
l->registerFeature( feature );
}
catch ( std::exception &e )
{
Q_UNUSED( e );
QgsDebugMsgLevel( QString( "Ignoring feature %1 due PAL exception:" ).arg( feature->id() ) + QString::fromLatin1( e.what() ), 4 );
continue;
}
}
processProvider( provider, context, p );
}
@ -269,10 +284,7 @@ void QgsLabelingEngineV2::run( QgsRenderContext& context )
continue;
}
//layer names
QString layerName = ( *it )->getLayerName();
providerById( layerName )->drawLabel( context, *it );
lf->provider()->drawLabel( context, *it );
}
// Reset composition mode for further drawing operations
@ -327,16 +339,6 @@ void QgsLabelingEngineV2::writeSettingsToProject()
QgsProject::instance()->writeEntry( "PAL", "/DrawOutlineLabels", mFlags.testFlag( RenderOutlineLabels ) );
}
QgsAbstractLabelProvider* QgsLabelingEngineV2::providerById( const QString& id )
{
Q_FOREACH ( QgsAbstractLabelProvider* provider, mProviders )
{
if ( provider->id() == id )
return provider;
}
return 0;
}
////
@ -369,6 +371,12 @@ QgsLabelFeature::~QgsLabelFeature()
delete mInfo;
}
QgsAbstractLabelProvider*QgsLabelFeature::provider() const
{
return mLayer ? mLayer->provider() : 0;
}
QgsAbstractLabelProvider::QgsAbstractLabelProvider()
: mEngine( 0 )
, mFlags( DrawLabels )

View File

@ -24,6 +24,7 @@
#include <QFlags>
class QgsAbstractLabelProvider;
class QgsRenderContext;
class QgsGeometry;
@ -183,6 +184,9 @@ class CORE_EXPORT QgsLabelFeature
//! Assign PAL layer to the label feature. Should be only used internally in PAL
void setLayer( pal::Layer* layer ) { mLayer = layer; }
//! Return provider of this instance
QgsAbstractLabelProvider* provider() const;
protected:
//! Pointer to PAL layer (assigned when registered to PAL)
pal::Layer* mLayer;
@ -262,15 +266,18 @@ class CORE_EXPORT QgsAbstractLabelProvider
};
Q_DECLARE_FLAGS( Flags, Flag )
//! Return unique identifier of the provider
virtual QString id() const = 0;
//! Return list of label features (they are owned by the provider and thus deleted on its destruction)
virtual QList<QgsLabelFeature*> labelFeatures( const QgsRenderContext& context ) = 0;
//! draw this label at the position determined by the labeling engine
virtual void drawLabel( QgsRenderContext& context, pal::LabelPosition* label ) const = 0;
//! Return list of child providers - useful if the provider needs to put labels into more layers with different configuration
virtual QList<QgsAbstractLabelProvider*> subProviders() { return QList<QgsAbstractLabelProvider*>(); }
//! Name of the layer (for statistics, debugging etc.) - does not need to be unique
QString name() const { return mName; }
//! Flags associated with the provider
Flags flags() const { return mFlags; }
@ -289,11 +296,12 @@ class CORE_EXPORT QgsAbstractLabelProvider
//! How to handle labels that would be upside down
QgsPalLayerSettings::UpsideDownLabels upsidedownLabels() const { return mUpsidedownLabels; }
protected:
//! Associated labeling engine
const QgsLabelingEngineV2* mEngine;
//! Name of the layer
QString mName;
//! Flags altering drawing and registration of features
Flags mFlags;
//! Placement strategy
@ -366,9 +374,6 @@ class CORE_EXPORT QgsLabelingEngineV2
//! Remove provider if the provider's initialization failed. Provider instance is deleted.
void removeProvider( QgsAbstractLabelProvider* provider );
//! Lookup provider by its ID
QgsAbstractLabelProvider* providerById( const QString& id );
//! compute the labeling with given map settings and providers
void run( QgsRenderContext& context );
@ -402,11 +407,15 @@ class CORE_EXPORT QgsLabelingEngineV2
//! Write configuration of the labeling engine to the current project file
void writeSettingsToProject();
protected:
void processProvider( QgsAbstractLabelProvider* provider, QgsRenderContext& context, pal::Pal& p );
protected:
//! Associated map settings instance
QgsMapSettings mMapSettings;
//! List of providers (the are owned by the labeling engine)
QList<QgsAbstractLabelProvider*> mProviders;
QList<QgsAbstractLabelProvider*> mSubProviders;
//! Flags
Flags mFlags;
//! search method to use for removal collisions between labels

View File

@ -3207,14 +3207,20 @@ void QgsPalLabeling::clearActiveLayer( const QString &layerID )
Q_UNUSED( layerID );
}
#include "qgsvectorlayerlabeling.h"
int QgsPalLabeling::prepareLayer( QgsVectorLayer* layer, QStringList& attrNames, QgsRenderContext& ctx )
{
if ( !willUseLayer( layer ) || !layer->labelsEnabled() )
if ( !willUseLayer( layer ) )
{
return 0;
}
QgsVectorLayerLabelProvider* lp = new QgsVectorLayerLabelProvider( layer, false );
QgsVectorLayerLabelProvider* lp = layer->labeling().provider( layer );
if ( !lp )
return 0;
//QgsVectorLayerLabelProvider* lp = new QgsVectorLayerLabelProvider( layer, false );
// need to be added before calling prepare() - uses map settings from engine
mEngine->addProvider( lp );
mLabelProviders[layer->id()] = lp; // fast lookup table by layer ID

View File

@ -0,0 +1,183 @@
#include "qgsrulebasedlabeling.h"
QgsRuleBasedLabelProvider::QgsRuleBasedLabelProvider( const QgsRuleBasedLabeling& rules, QgsVectorLayer* layer, bool withFeatureLoop )
: QgsVectorLayerLabelProvider( layer, withFeatureLoop )
, mRules( rules )
{
mRules.rootRule()->createSubProviders( layer, mSubProviders );
}
QgsRuleBasedLabelProvider::~QgsRuleBasedLabelProvider()
{
// sub-providers owned by labeling engine
}
bool QgsRuleBasedLabelProvider::prepare( const QgsRenderContext& context, QStringList& attributeNames )
{
Q_FOREACH ( QgsVectorLayerLabelProvider* provider, mSubProviders.values() )
provider->setEngine( mEngine );
// populate sub-providers
mRules.rootRule()->prepare( context, attributeNames, mSubProviders );
return true;
}
void QgsRuleBasedLabelProvider::registerFeature( QgsFeature& feature, const QgsRenderContext& context )
{
// will register the feature to relevant sub-providers
mRules.rootRule()->registerFeature( feature, context, mSubProviders );
}
QList<QgsAbstractLabelProvider*> QgsRuleBasedLabelProvider::subProviders()
{
QList<QgsAbstractLabelProvider*> lst;
Q_FOREACH ( QgsVectorLayerLabelProvider* subprovider, mSubProviders.values() )
lst << subprovider;
return lst;
}
////////////////////
QgsRuleBasedLabeling::Rule::Rule( QgsPalLayerSettings* settings, int scaleMinDenom, int scaleMaxDenom, const QString& filterExp, const QString& label, const QString& description, bool elseRule )
: mParent( 0 ), mSettings( settings )
, mScaleMinDenom( scaleMinDenom ), mScaleMaxDenom( scaleMaxDenom )
, mFilterExp( filterExp ), mLabel( label ), mDescription( description )
, mElseRule( elseRule )
//, mIsActive( true )
, mFilter( 0 )
{
initFilter();
}
QgsRuleBasedLabeling::Rule::~Rule()
{
delete mSettings;
delete mFilter;
qDeleteAll( mChildren );
// do NOT delete parent
}
void QgsRuleBasedLabeling::Rule::initFilter()
{
if ( mElseRule || mFilterExp.compare( "ELSE", Qt::CaseInsensitive ) == 0 )
{
mElseRule = true;
mFilter = 0;
}
else if ( !mFilterExp.isEmpty() )
{
delete mFilter;
mFilter = new QgsExpression( mFilterExp );
}
else
{
mFilter = 0;
}
}
void QgsRuleBasedLabeling::Rule::appendChild( QgsRuleBasedLabeling::Rule* rule )
{
mChildren.append( rule );
rule->mParent = this;
// TODO updateElseRules();
}
QgsRuleBasedLabeling::Rule*QgsRuleBasedLabeling::Rule::clone() const
{
QgsPalLayerSettings* s = mSettings ? new QgsPalLayerSettings( *mSettings ) : 0;
Rule* newrule = new Rule( s, mScaleMinDenom, mScaleMaxDenom, mFilterExp, mLabel, mDescription );
// TODO newrule->setCheckState( mIsActive );
// clone children
Q_FOREACH ( Rule* rule, mChildren )
newrule->appendChild( rule->clone() );
return newrule;
}
void QgsRuleBasedLabeling::Rule::createSubProviders( QgsVectorLayer* layer, QgsRuleBasedLabeling::RuleToProviderMap& subProviders )
{
if ( mSettings )
{
// add provider!
QgsVectorLayerLabelProvider* p = new QgsVectorLayerLabelProvider( layer, false, mSettings );
subProviders[this] = p;
}
// call recursively
Q_FOREACH ( Rule* rule, mChildren )
{
rule->createSubProviders( layer, subProviders );
}
}
void QgsRuleBasedLabeling::Rule::prepare( const QgsRenderContext& context, QStringList& attributeNames, QgsRuleBasedLabeling::RuleToProviderMap& subProviders )
{
if ( mSettings )
{
QgsVectorLayerLabelProvider* p = subProviders[this];
if ( !p->prepare( context, attributeNames ) )
{
subProviders.remove( this );
delete p;
}
}
if ( mFilter )
mFilter->prepare( &context.expressionContext() );
// call recursively
Q_FOREACH ( Rule* rule, mChildren )
{
rule->prepare( context, attributeNames, subProviders );
}
}
void QgsRuleBasedLabeling::Rule::registerFeature( QgsFeature& feature, const QgsRenderContext& context, QgsRuleBasedLabeling::RuleToProviderMap& subProviders )
{
bool isOK = isFilterOK( feature, const_cast<QgsRenderContext&>( context ) ); // TODO: remove const_cast
if ( !isOK )
return;
// do we have active subprovider for the rule?
if ( subProviders.contains( this ) )
subProviders[this]->registerFeature( feature, context );
// call recursively
Q_FOREACH ( Rule* rule, mChildren )
{
rule->registerFeature( feature, context, subProviders );
}
}
bool QgsRuleBasedLabeling::Rule::isFilterOK( QgsFeature& f, QgsRenderContext& context ) const
{
if ( ! mFilter || mElseRule )
return true;
context.expressionContext().setFeature( f );
QVariant res = mFilter->evaluate( &context.expressionContext() );
return res.toInt() != 0;
}
////////////////////
QgsRuleBasedLabeling::QgsRuleBasedLabeling( QgsRuleBasedLabeling::Rule* root )
: mRootRule( root )
{
}
QgsRuleBasedLabeling::QgsRuleBasedLabeling( const QgsRuleBasedLabeling& other )
{
mRootRule = other.mRootRule->clone();
}
QgsRuleBasedLabeling::~QgsRuleBasedLabeling()
{
delete mRootRule;
}

View File

@ -0,0 +1,189 @@
#ifndef QGSRULEBASEDLABELING_H
#define QGSRULEBASEDLABELING_H
#include <QStringList>
class QgsExpression;
class QgsFeature;
class QgsPalLayerSettings;
class QgsRenderContext;
class QgsVectorLayer;
class QgsVectorLayerLabelProvider;
class CORE_EXPORT QgsRuleBasedLabeling
{
public:
class Rule;
typedef QList<Rule*> RuleList;
typedef QMap<Rule*, QgsVectorLayerLabelProvider*> RuleToProviderMap;
class Rule
{
public:
//! takes ownership of settings
Rule( QgsPalLayerSettings* settings, int scaleMinDenom = 0, int scaleMaxDenom = 0, const QString& filterExp = QString(), const QString& label = QString(), const QString& description = QString(), bool elseRule = false );
~Rule();
QgsPalLayerSettings* settings() const { return mSettings; }
QString label() const { return mLabel; }
bool dependsOnScale() const { return mScaleMinDenom != 0 || mScaleMaxDenom != 0; }
int scaleMinDenom() const { return mScaleMinDenom; }
int scaleMaxDenom() const { return mScaleMaxDenom; }
/**
* A filter that will check if this rule applies
* @return An expression
*/
QString filterExpression() const { return mFilterExp; }
/**
* A human readable description for this rule
*
* @return Description
*/
QString description() const { return mDescription; }
/**
* Check if this rule is an ELSE rule
*
* @return True if this rule is an else rule
*/
bool isElse() { return mElseRule; }
void setLabel( QString label ) { mLabel = label; }
/**
* Set the minimum denominator for which this rule shall apply.
* E.g. 1000 if it shall be evaluated between 1:1000 and 1:100'000
* Set to 0 to disable the minimum check
* @param scaleMinDenom The minimum scale denominator for this rule
*/
void setScaleMinDenom( int scaleMinDenom ) { mScaleMinDenom = scaleMinDenom; }
/**
* Set the maximum denominator for which this rule shall apply.
* E.g. 100'000 if it shall be evaluated between 1:1000 and 1:100'000
* Set to 0 to disable the maximum check
* @param scaleMaxDenom maximum scale denominator for this rule
*/
void setScaleMaxDenom( int scaleMaxDenom ) { mScaleMaxDenom = scaleMaxDenom; }
/**
* Set the expression used to check if a given feature shall be rendered with this rule
*
* @param filterExp An expression
*/
void setFilterExpression( QString filterExp ) { mFilterExp = filterExp; initFilter(); }
/**
* Set a human readable description for this rule
*
* @param description Description
*/
void setDescription( QString description ) { mDescription = description; }
/**
* Sets if this rule is an ELSE rule
*
* @param iselse If true, this rule is an ELSE rule
*/
void setIsElse( bool iselse ) { mElseRule = iselse; }
// parent / child operations
/**
* Return all children rules of this rule
*
* @return A list of rules
*/
RuleList& children() { return mChildren; }
/**
* The parent rule
*
* @return Parent rule
*/
Rule* parent() { return mParent; }
//! add child rule, take ownership, sets this as parent
void appendChild( Rule* rule );
//! clone this rule, return new instance
Rule* clone() const;
// TODO: load / save
// evaluation
//! add providers
void createSubProviders( QgsVectorLayer* layer, RuleToProviderMap& subProviders );
//! call prepare() on sub-providers and populate attributeNames
void prepare( const QgsRenderContext& context, QStringList& attributeNames, RuleToProviderMap& subProviders );
//! register individual features
void registerFeature( QgsFeature& feature, const QgsRenderContext& context, RuleToProviderMap& subProviders );
protected:
/**
* Check if a given feature shall be labelled by this rule
*
* @param f The feature to test
* @param context The context in which the rendering happens
* @return True if the feature shall be rendered
*/
bool isFilterOK( QgsFeature& f, QgsRenderContext& context ) const;
void initFilter();
protected:
Rule* mParent; // parent rule (NULL only for root rule)
QgsPalLayerSettings* mSettings;
int mScaleMinDenom, mScaleMaxDenom;
QString mFilterExp, mLabel, mDescription;
bool mElseRule;
RuleList mChildren;
// temporary
QgsExpression* mFilter;
};
//! Constructs the labeling from given tree of rules (takes ownership)
explicit QgsRuleBasedLabeling( QgsRuleBasedLabeling::Rule* root );
//! Copy constructor
QgsRuleBasedLabeling( const QgsRuleBasedLabeling& other );
~QgsRuleBasedLabeling();
Rule* rootRule() { return mRootRule; }
// TODO: static create() from DOM
protected:
Rule* mRootRule;
};
#include "qgsvectorlayerlabelprovider.h"
class CORE_EXPORT QgsRuleBasedLabelProvider : public QgsVectorLayerLabelProvider
{
public:
QgsRuleBasedLabelProvider( const QgsRuleBasedLabeling& rules, QgsVectorLayer* layer, bool withFeatureLoop = true );
~QgsRuleBasedLabelProvider();
// reimplemented
virtual bool prepare( const QgsRenderContext& context, QStringList& attributeNames ) override;
virtual void registerFeature( QgsFeature& feature, const QgsRenderContext& context ) override;
// new methods
virtual QList<QgsAbstractLabelProvider*> subProviders() override;
protected:
//! owned copy
QgsRuleBasedLabeling mRules;
//! label providers are owned by labeling engine
QgsRuleBasedLabeling::RuleToProviderMap mSubProviders;
};
#endif // QGSRULEBASEDLABELING_H

View File

@ -69,6 +69,7 @@
#include "qgsvectorlayereditutils.h"
#include "qgsvectorlayerfeatureiterator.h"
#include "qgsvectorlayerjoinbuffer.h"
#include "qgsvectorlayerlabeling.h"
#include "qgsvectorlayerrenderer.h"
#include "qgsvectorlayerundocommand.h"
#include "qgspointv2.h"
@ -131,6 +132,7 @@ QgsVectorLayer::QgsVectorLayer( QString vectorLayerPath,
, mRendererV2( NULL )
, mLabel( 0 )
, mLabelOn( false )
, mLabeling( new QgsVectorLayerLabeling )
, mLabelFontNotFoundNotified( false )
, mFeatureBlendMode( QPainter::CompositionMode_SourceOver ) // Default to normal feature blending
, mLayerTransparency( 0 )
@ -184,7 +186,8 @@ QgsVectorLayer::~QgsVectorLayer()
delete mJoinBuffer;
delete mExpressionFieldBuffer;
delete mCache;
delete mLabel;
delete mLabel; // old deprecated implementation
delete mLabeling;
delete mDiagramLayerSettings;
delete mDiagramRenderer;

View File

@ -63,6 +63,7 @@ class QgsSymbolV2;
class QgsVectorDataProvider;
class QgsVectorLayerEditBuffer;
class QgsVectorLayerJoinBuffer;
class QgsVectorLayerLabeling;
class QgsPointV2;
typedef QList<int> QgsAttributeList;
@ -1261,6 +1262,11 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
*/
Q_DECL_DEPRECATED bool hasLabelsEnabled() const;
/** Access to labeling configuration
* @note added in 2.12
*/
QgsVectorLayerLabeling& labeling() { return *mLabeling; }
/** Returns true if the provider is in editing mode */
virtual bool isEditable() const override;
@ -2079,12 +2085,15 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
/** Simplification object which holds the information about how to simplify the features for fast rendering */
QgsVectorSimplifyMethod mSimplifyMethod;
/** Label */
/** Label [old deprecated implementation] */
QgsLabel *mLabel;
/** Display labels */
/** Display labels [old deprecated implementation] */
bool mLabelOn;
/** Labeling configuration */
QgsVectorLayerLabeling* mLabeling;
/** Whether 'labeling font not found' has be shown for this layer (only show once in QgsMessageBar, on first rendering) */
bool mLabelFontNotFoundNotified;

View File

@ -59,6 +59,7 @@ QgsVectorLayerDiagramProvider::QgsVectorLayerDiagramProvider( QgsVectorLayer* la
void QgsVectorLayerDiagramProvider::init()
{
mName = mLayerId;
mPriority = 1 - mSettings.priority / 10.0; // convert 0..10 --> 1..0
mPlacement = QgsPalLayerSettings::Placement( mSettings.placement );
mLinePlacementFlags = mSettings.placementFlags;
@ -77,11 +78,6 @@ QgsVectorLayerDiagramProvider::~QgsVectorLayerDiagramProvider()
}
QString QgsVectorLayerDiagramProvider::id() const
{
return mLayerId + "d";
}
QList<QgsLabelFeature*> QgsVectorLayerDiagramProvider::labelFeatures( const QgsRenderContext& context )
{
if ( !mSource )
@ -154,10 +150,7 @@ void QgsVectorLayerDiagramProvider::drawLabel( QgsRenderContext& context, pal::L
mSettings.renderer->renderDiagram( feature, context, centerPt.toQPointF() );
//insert into label search tree to manipulate position interactively
//for diagrams, remove the additional 'd' at the end of the layer id
QString layerId = id();
layerId.chop( 1 );
mEngine->results()->mLabelSearchTree->insertLabel( label, label->getFeaturePart()->featureId(), layerId, QString(), QFont(), true, false );
mEngine->results()->mLabelSearchTree->insertLabel( label, label->getFeaturePart()->featureId(), mLayerId, QString(), QFont(), true, false );
}

View File

@ -70,8 +70,6 @@ class CORE_EXPORT QgsVectorLayerDiagramProvider : public QgsAbstractLabelProvide
//! Clean up
~QgsVectorLayerDiagramProvider();
virtual QString id() const override;
virtual QList<QgsLabelFeature*> labelFeatures( const QgsRenderContext& context ) override;
virtual void drawLabel( QgsRenderContext& context, pal::LabelPosition* label ) const override;

View File

@ -0,0 +1,75 @@
#include "qgsvectorlayerlabeling.h"
#include "qgspallabeling.h"
#include "qgsrulebasedlabeling.h"
#include "qgsvectorlayer.h"
QgsVectorLayerLabeling::QgsVectorLayerLabeling()
: mMode( SimpleLabels )
//, mSimpleLabeling( 0 )
, mRuleBasedLabeling( 0 )
{
}
QgsVectorLayerLabeling::~QgsVectorLayerLabeling()
{
//delete mSimpleLabeling;
delete mRuleBasedLabeling;
}
//QgsPalLayerSettings QgsVectorLayerLabeling::simpleLabeling()
//{
//}
//void QgsVectorLayerLabeling::setSimpleLabeling(QgsPalLayerSettings* settings)
//{
// delete mSimpleLabeling;
// mSimpleLabeling = settings;
//}
void QgsVectorLayerLabeling::setRuleBasedLabeling( QgsRuleBasedLabeling* settings )
{
delete mRuleBasedLabeling;
mRuleBasedLabeling = settings;
}
QgsVectorLayerLabelProvider* QgsVectorLayerLabeling::provider( QgsVectorLayer* layer )
{
if ( mMode == SimpleLabels )
{
if ( layer->customProperty( "labeling" ).toString() == QString( "pal" ) && layer->labelsEnabled() )
return new QgsVectorLayerLabelProvider( layer, false );
}
else // rule-based
{
if ( mRuleBasedLabeling )
return new QgsRuleBasedLabelProvider( *mRuleBasedLabeling, layer, false );
}
return 0;
}
/*
QgsAbstractLabelProvider* QgsVectorLayerLabeling::addProviderToEngine( QgsVectorLayer* layer, QgsLabelingEngineV2* engine, QgsRenderContext& context )
{
if ( mMode == SimpleLabels )
{
QgsVectorLayerLabelProvider* provider = 0;
if ( layer->labelsEnabled() )
{
provider = new QgsVectorLayerLabelProvider( layer, false );
engine->addProvider( provider );
if ( !provider->prepare( context, attributeNames ) )
{
engine->removeProvider( provider );
provider = 0; // deleted by engine
}
}
}
else // rule-based
{
return 0; // TODO
}
}
*/

View File

@ -0,0 +1,49 @@
#ifndef QGSVECTORLAYERLABELING_H
#define QGSVECTORLAYERLABELING_H
class QgsPalLayerSettings;
class QgsRuleBasedLabeling;
class QgsVectorLayer;
class QgsVectorLayerLabelProvider;
class CORE_EXPORT QgsVectorLayerLabeling
{
public:
enum Mode
{
//NoLabels, //!< the layer does not participate in labeling
//Obstacles, //!< no labels are shown, but layer's features act as obstacles for other labels
SimpleLabels, //!< the layer is labelled with one style
RuleBasedLabels //!< the layer is labelled with multiple styles defined with rules
};
//! Defaults to no labels
QgsVectorLayerLabeling();
~QgsVectorLayerLabeling();
Mode mode() const { return mMode; }
void setMode( Mode m ) { mMode = m; }
//QgsPalLayerSettings simpleLabeling();
//QgsPalLayerSettings* simpleLabeling() const { return mSimpleLabeling; }
//! Assign simple labeling configuration (takes ownership)
//void setSimpleLabeling( QgsPalLayerSettings* settings );
QgsRuleBasedLabeling* ruleBasedLabeling() const { return mRuleBasedLabeling; }
//! Assign rule-based labeling configuration (takes ownership)
void setRuleBasedLabeling( QgsRuleBasedLabeling* settings );
//! Factory for label provider implementation - according to the current mode
QgsVectorLayerLabelProvider* provider( QgsVectorLayer* layer );
protected:
Mode mMode;
//QgsPalLayerSettings* mSimpleLabeling;
QgsRuleBasedLabeling* mRuleBasedLabeling;
};
#endif // QGSVECTORLAYERLABELING_H

View File

@ -43,14 +43,11 @@ static void _fixQPictureDPI( QPainter* p )
}
typedef QgsPalLayerSettings QgsVectorLayerLabelSettings;
QgsVectorLayerLabelProvider::QgsVectorLayerLabelProvider( QgsVectorLayer* layer, bool withFeatureLoop )
QgsVectorLayerLabelProvider::QgsVectorLayerLabelProvider( QgsVectorLayer* layer, bool withFeatureLoop, const QgsPalLayerSettings* settings, const QString& layerName )
{
if ( layer->customProperty( "labeling" ).toString() != QString( "pal" ) || !layer->labelsEnabled() )
return;
mSettings = QgsVectorLayerLabelSettings::fromLayer( layer );
mSettings = settings ? *settings : QgsPalLayerSettings::fromLayer( layer );
mName = layerName.isEmpty() ? layer->id() : layerName;
mLayerId = layer->id();
mFields = layer->fields();
mCrs = layer->crs();
@ -120,14 +117,10 @@ QgsVectorLayerLabelProvider::~QgsVectorLayerLabelProvider()
delete mSource;
}
QString QgsVectorLayerLabelProvider::id() const
{
return mLayerId;
}
bool QgsVectorLayerLabelProvider::prepare( const QgsRenderContext& context, QStringList& attributeNames )
{
QgsVectorLayerLabelSettings& lyr = mSettings;
QgsPalLayerSettings& lyr = mSettings;
const QgsMapSettings& mapSettings = mEngine->mapSettings();
QgsDebugMsgLevel( "PREPARE LAYER " + mLayerId, 4 );
@ -389,7 +382,7 @@ void QgsVectorLayerLabelProvider::drawLabel( QgsRenderContext& context, pal::Lab
// add to the results
QString labeltext = label->getFeaturePart()->feature()->labelText();
mEngine->results()->mLabelSearchTree->insertLabel( label, label->getFeaturePart()->featureId(), id(), labeltext, dFont, false, lf->hasFixedPosition() );
mEngine->results()->mLabelSearchTree->insertLabel( label, label->getFeaturePart()->featureId(), mLayerId, labeltext, dFont, false, lf->hasFixedPosition() );
}

View File

@ -32,7 +32,7 @@ class CORE_EXPORT QgsVectorLayerLabelProvider : public QgsAbstractLabelProvider
public:
//! Convenience constructor to initialize the provider from given vector layer
explicit QgsVectorLayerLabelProvider( QgsVectorLayer* layer, bool withFeatureLoop = true );
explicit QgsVectorLayerLabelProvider( QgsVectorLayer* layer, bool withFeatureLoop = true, const QgsPalLayerSettings* settings = 0, const QString& layerName = QString() );
//! Construct diagram provider with all the necessary configuration parameters
QgsVectorLayerLabelProvider( const QgsPalLayerSettings& settings,
@ -44,8 +44,6 @@ class CORE_EXPORT QgsVectorLayerLabelProvider : public QgsAbstractLabelProvider
~QgsVectorLayerLabelProvider();
virtual QString id() const override;
virtual QList<QgsLabelFeature*> labelFeatures( const QgsRenderContext& context ) override;
virtual void drawLabel( QgsRenderContext& context, pal::LabelPosition* label ) const override;

View File

@ -29,6 +29,7 @@
#include "qgsvectorlayer.h"
#include "qgsvectorlayerdiagramprovider.h"
#include "qgsvectorlayerfeatureiterator.h"
#include "qgsvectorlayerlabeling.h"
#include "qgsvectorlayerlabelprovider.h"
#include "qgspainteffect.h"
@ -494,7 +495,19 @@ void QgsVectorLayerRenderer::prepareLabeling( QgsVectorLayer* layer, QStringList
{
if ( QgsLabelingEngineV2* engine2 = mContext.labelingEngineV2() )
{
if ( layer->labelsEnabled() )
mLabelProvider = layer->labeling().provider( layer );
if ( mLabelProvider )
{
engine2->addProvider( mLabelProvider );
if ( !mLabelProvider->prepare( mContext, attributeNames ) )
{
engine2->removeProvider( mLabelProvider );
mLabelProvider = 0; // deleted by engine
}
}
//mLabelProvider = layer->labeling().addProviderToEngine( layer, engine2, mContext );
/*if ( layer->labelsEnabled() )
{
mLabelProvider = new QgsVectorLayerLabelProvider( layer, false );
engine2->addProvider( mLabelProvider );
@ -503,7 +516,7 @@ void QgsVectorLayerRenderer::prepareLabeling( QgsVectorLayer* layer, QStringList
engine2->removeProvider( mLabelProvider );
mLabelProvider = 0; // deleted by engine
}
}
}*/
}
return;
}

View File

@ -31,6 +31,7 @@ class TestQgsLabelingEngineV2 : public QObject
void cleanupTestCase();
void testBasic();
void testDiagrams();
void testRuleBased();
private:
QgsVectorLayer* vl;
@ -135,6 +136,62 @@ void TestQgsLabelingEngineV2::testDiagrams()
QImage img2 = job.renderedImage();
QCOMPARE( img, img2 );
vl->loadDefaultStyle( res );
}
#include "qgsvectorlayerlabeling.h"
#include "qgsrulebasedlabeling.h"
void TestQgsLabelingEngineV2::testRuleBased()
{
QSize size( 640, 480 );
QgsMapSettings mapSettings;
mapSettings.setOutputSize( size );
mapSettings.setExtent( vl->extent() );
mapSettings.setLayers( QStringList() << vl->id() );
// set up most basic rule-based labeling for layer
QgsRuleBasedLabeling::Rule* root = new QgsRuleBasedLabeling::Rule( 0 );
QgsPalLayerSettings s1;
s1.enabled = true;
s1.fieldName = "Class";
s1.obstacle = false;
s1.dist = 2;
s1.distInMapUnits = false;
root->appendChild( new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings( s1 ) ) );
QgsPalLayerSettings s2;
s2.enabled = true;
s2.fieldName = "Class";
s2.obstacle = false;
s2.dist = 2;
s2.textColor = Qt::red;
root->appendChild( new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings( s2 ), 0, 0, "Class = 'Jet'" ) );
vl->labeling().setMode( QgsVectorLayerLabeling::RuleBasedLabels );
vl->labeling().setRuleBasedLabeling( new QgsRuleBasedLabeling( root ) );
QgsMapRendererSequentialJob job( mapSettings );
job.start();
job.waitForFinished();
QImage img = job.renderedImage();
img.save( "/tmp/rules.png" );
vl->labeling().setMode( QgsVectorLayerLabeling::SimpleLabels );
/*
QPainter p( &img );
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
context.setPainter( &p );
QgsLabelingEngineV2 engine;
engine.setMapSettings( mapSettings );
engine.addProvider( new QgsRuleBasedLabelProvider( , vl ) );
engine.run( context );*/
}
QTEST_MAIN( TestQgsLabelingEngineV2 )