Skip to content

Commit b477179

Browse files
author
Hugo Mercier
committed
[Atlas] Add feature sorting and filtering
1 parent a05fc28 commit b477179

File tree

2 files changed

+105
-6
lines changed

2 files changed

+105
-6
lines changed

src/core/composer/qgsatlascomposition.cpp

+76-6
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ QgsAtlasComposition::QgsAtlasComposition( QgsComposition* composition ) :
3030
mEnabled( false ),
3131
mComposerMap( 0 ),
3232
mHideCoverage( false ), mFixedScale( false ), mMargin( 0.10 ), mFilenamePattern( "'output_'||$feature" ),
33-
mCoverageLayer( 0 ), mSingleFile( false )
33+
mCoverageLayer( 0 ), mSingleFile( false ),
34+
mSortFeatures( false ), mSortAscending( true ), mFeatureFilter( "" )
3435
{
3536

3637
// declare special columns with a default value
@@ -52,6 +53,33 @@ void QgsAtlasComposition::setCoverageLayer( QgsVectorLayer* layer )
5253
QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )mFeatureIds.size() ) );
5354
}
5455

56+
//
57+
// Private class only used for the sorting of features
58+
class FieldSorter
59+
{
60+
public:
61+
FieldSorter( QgsAtlasComposition::SorterKeys& keys, bool ascending = true ) : mKeys( keys ), mAscending( ascending ) {}
62+
63+
bool operator()( const QgsFeatureId& id1, const QgsFeatureId& id2 )
64+
{
65+
bool result;
66+
if ( mKeys[ id1 ].type() == QVariant::Int ) {
67+
result = mKeys[ id1 ].toInt() < mKeys[ id2 ].toInt();
68+
}
69+
else if ( mKeys[ id1 ].type() == QVariant::Double ) {
70+
result = mKeys[ id1 ].toDouble() < mKeys[ id2 ].toDouble();
71+
}
72+
else if ( mKeys[ id1 ].type() == QVariant::String ) {
73+
result = (QString::localeAwareCompare(mKeys[ id1 ].toString(), mKeys[ id2 ].toString()) < 0);
74+
}
75+
76+
return mAscending ? result : !result;
77+
}
78+
private:
79+
QgsAtlasComposition::SorterKeys& mKeys;
80+
bool mAscending;
81+
};
82+
5583
void QgsAtlasComposition::beginRender()
5684
{
5785
if ( !mComposerMap || !mCoverageLayer )
@@ -84,13 +112,45 @@ void QgsAtlasComposition::beginRender()
84112
// select all features with all attributes
85113
QgsFeatureIterator fit = mCoverageLayer->getFeatures();
86114

115+
std::auto_ptr<QgsExpression> filterExpression;
116+
if ( mFeatureFilter.size() > 0 ) {
117+
filterExpression = std::auto_ptr<QgsExpression>(new QgsExpression( mFeatureFilter ));
118+
if ( filterExpression->hasParserError() )
119+
{
120+
throw std::runtime_error( "Feature filter parser error: " + filterExpression->parserErrorString().toStdString() );
121+
}
122+
}
123+
87124
// We cannot use nextFeature() directly since the feature pointer is rewinded by the rendering process
88125
// We thus store the feature ids for future extraction
89126
QgsFeature feat;
90127
mFeatureIds.clear();
128+
mFeatureKeys.clear();
91129
while ( fit.nextFeature( feat ) )
92130
{
93-
mFeatureIds.push_back( feat.id() );
131+
if ( mFeatureFilter.size() > 0 ) {
132+
QVariant result = filterExpression->evaluate( &feat, mCoverageLayer->pendingFields() );
133+
if ( filterExpression->hasEvalError() )
134+
{
135+
throw std::runtime_error( "Feature filter eval error: " + filterExpression->evalErrorString().toStdString() );
136+
}
137+
138+
// skip this feature if the filter evaluation if false
139+
if ( !result.toBool() ) {
140+
continue;
141+
}
142+
}
143+
mFeatureIds.push_back( feat.id() );
144+
145+
if ( mSortFeatures ) {
146+
mFeatureKeys.insert( std::make_pair( feat.id(), feat.attributes()[ mSortKeyAttributeIdx ] ) );
147+
}
148+
}
149+
150+
// sort features, if asked for
151+
if ( mSortFeatures ) {
152+
FieldSorter sorter( mFeatureKeys, mSortAscending );
153+
std::sort( mFeatureIds.begin(), mFeatureIds.end(), sorter );
94154
}
95155

96156
mOrigExtent = mComposerMap->extent();
@@ -159,10 +219,6 @@ void QgsAtlasComposition::prepareForFeature( size_t featureI )
159219
{
160220
QgsExpression::setSpecialColumn( "$feature", QVariant(( int )featureI + 1 ) );
161221
QVariant filenameRes = mFilenameExpr->evaluate( &mCurrentFeature );
162-
if ( mFilenameExpr->hasEvalError() )
163-
{
164-
throw std::runtime_error( "Filename eval error: " + mFilenameExpr->evalErrorString().toStdString() );
165-
}
166222

167223
mCurrentFilename = filenameRes.toString();
168224
}
@@ -284,6 +340,13 @@ void QgsAtlasComposition::writeXML( QDomElement& elem, QDomDocument& doc ) const
284340
atlasElem.setAttribute( "margin", QString::number( mMargin ) );
285341
atlasElem.setAttribute( "filenamePattern", mFilenamePattern );
286342

343+
atlasElem.setAttribute( "sortFeatures", mSortFeatures ? "true" : "false" );
344+
if ( mSortFeatures ) {
345+
atlasElem.setAttribute( "sortKey", QString::number(mSortKeyAttributeIdx) );
346+
atlasElem.setAttribute( "sortAscending", mSortAscending ? "true" : "false" );
347+
}
348+
atlasElem.setAttribute( "featureFilter", mFeatureFilter );
349+
287350
elem.appendChild( atlasElem );
288351
}
289352

@@ -324,5 +387,12 @@ void QgsAtlasComposition::readXML( const QDomElement& atlasElem, const QDomDocum
324387
mSingleFile = atlasElem.attribute( "singleFile", "false" ) == "true" ? true : false;
325388
mFilenamePattern = atlasElem.attribute( "filenamePattern", "" );
326389

390+
mSortFeatures = atlasElem.attribute( "sortFeatures", "false" ) == "true" ? true : false;
391+
if ( mSortFeatures ) {
392+
mSortKeyAttributeIdx = atlasElem.attribute( "sortKey", "0" ).toInt();
393+
mSortAscending = atlasElem.attribute( "sortAscending", "true" ) == "true" ? true : false;
394+
}
395+
mFeatureFilter = atlasElem.attribute( "featureFilter", "" );
396+
327397
emit parameterChanged();
328398
}

src/core/composer/qgsatlascomposition.h

+29
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ class CORE_EXPORT QgsAtlasComposition : public QObject
6666
bool singleFile() const { return mSingleFile; }
6767
void setSingleFile( bool single ) { mSingleFile = single; }
6868

69+
bool sortFeatures() const { return mSortFeatures; }
70+
void setSortFeatures( bool doSort ) { mSortFeatures = doSort; }
71+
72+
bool sortAscending() const { return mSortAscending; }
73+
void setSortAscending( bool ascending ) { mSortAscending = ascending; }
74+
75+
QString featureFilter() const { return mFeatureFilter; }
76+
void setFeatureFilter( const QString& expression ) { mFeatureFilter = expression; }
77+
78+
size_t sortKeyAttributeIndex() const { return mSortKeyAttributeIdx; }
79+
void setSortKeyAttributeIndex( size_t idx ) { mSortKeyAttributeIdx = idx; }
80+
6981
/** Begins the rendering. */
7082
void beginRender();
7183
/** Ends the rendering. Restores original extent */
@@ -103,7 +115,24 @@ class CORE_EXPORT QgsAtlasComposition : public QObject
103115

104116
QgsCoordinateTransform mTransform;
105117
QString mCurrentFilename;
118+
// feature ordering
119+
bool mSortFeatures;
120+
// sort direction
121+
bool mSortAscending;
122+
public:
123+
typedef std::map< QgsFeatureId, QVariant > SorterKeys;
124+
private:
125+
// value of field that is used for ordering of features
126+
SorterKeys mFeatureKeys;
127+
// key (attribute index) used for ordering
128+
size_t mSortKeyAttributeIdx;
129+
130+
// feature expression filter (or empty)
131+
QString mFeatureFilter;
132+
133+
// id of each iterated feature (after filtering and sorting)
106134
std::vector<QgsFeatureId> mFeatureIds;
135+
107136
QgsFeature mCurrentFeature;
108137
QgsRectangle mOrigExtent;
109138
bool mRestoreLayer;

0 commit comments

Comments
 (0)