mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[FEATURE][processing] New algorithm to categorize a layer using a style XML file
Sets a vector layer's renderer to a categorized renderer using matching symbols from a style database. The specified expression (or field name) is used to create categories for the renderer. A category will be created for each unique value within the layer. Each category is individually matched to the symbols which exist within the specified QGIS XML style database. Whenever a matching symbol name is found, the category's symbol will be set to this matched symbol. The matching is case-insensitive by default, but can be made case-sensitive if required. Optionally, non-alphanumeric characters in both the category value and symbol name can be ignored while performing the match. This allows for greater tolerance when matching categories to symbols. If desired, tables can also be output containing lists of the categories which could not be matched to symbols, and symbols which were not matched to categories.
This commit is contained in:
parent
97a964af4a
commit
5f5294f258
@ -25,6 +25,7 @@ SET(QGIS_ANALYSIS_SRCS
|
||||
processing/qgsalgorithmboundary.cpp
|
||||
processing/qgsalgorithmboundingbox.cpp
|
||||
processing/qgsalgorithmbuffer.cpp
|
||||
processing/qgsalgorithmcategorizeusingstyle.cpp
|
||||
processing/qgsalgorithmcentroid.cpp
|
||||
processing/qgsalgorithmclip.cpp
|
||||
processing/qgsalgorithmconvexhull.cpp
|
||||
|
272
src/analysis/processing/qgsalgorithmcategorizeusingstyle.cpp
Normal file
272
src/analysis/processing/qgsalgorithmcategorizeusingstyle.cpp
Normal file
@ -0,0 +1,272 @@
|
||||
/***************************************************************************
|
||||
qgsalgorithmcategorizeusingstyle.cpp
|
||||
---------------------
|
||||
begin : August 2018
|
||||
copyright : (C) 2018 by Nyall Dawson
|
||||
email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgsalgorithmcategorizeusingstyle.h"
|
||||
#include "qgsstyle.h"
|
||||
#include "qgscategorizedsymbolrenderer.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
QgsCategorizeUsingStyleAlgorithm::QgsCategorizeUsingStyleAlgorithm() = default;
|
||||
|
||||
QgsCategorizeUsingStyleAlgorithm::~QgsCategorizeUsingStyleAlgorithm() = default;
|
||||
|
||||
void QgsCategorizeUsingStyleAlgorithm::initAlgorithm( const QVariantMap & )
|
||||
{
|
||||
addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ),
|
||||
QList< int >() << QgsProcessing::TypeVector ) );
|
||||
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FIELD" ), QObject::tr( "Categorize using expression" ), QVariant(), QStringLiteral( "INPUT" ) ) );
|
||||
|
||||
addParameter( new QgsProcessingParameterFile( QStringLiteral( "STYLE" ), QObject::tr( "Style database" ), QgsProcessingParameterFile::File, QStringLiteral( "xml" ) ) );
|
||||
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "CASE_SENSITIVE" ), QObject::tr( "Use case-sensitive match to symbol names" ), false ) );
|
||||
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "TOLERANT" ), QObject::tr( "Ignore non-alphanumeric characters while matching" ), false ) );
|
||||
|
||||
addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Categorized layer" ) ) );
|
||||
|
||||
std::unique_ptr< QgsProcessingParameterFeatureSink > failCategories = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral( "NON_MATCHING_CATEGORIES" ), QObject::tr( "Non-matching categories" ),
|
||||
QgsProcessing::TypeVector, QVariant(), true, false );
|
||||
// not supported for outputs yet!
|
||||
//failCategories->setFlags( failCategories->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
|
||||
addParameter( failCategories.release() );
|
||||
|
||||
std::unique_ptr< QgsProcessingParameterFeatureSink > failSymbols = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral( "NON_MATCHING_SYMBOLS" ), QObject::tr( "Non-matching symbol names" ),
|
||||
QgsProcessing::TypeVector, QVariant(), true, false );
|
||||
//failSymbols->setFlags( failSymbols->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
|
||||
addParameter( failSymbols.release() );
|
||||
}
|
||||
|
||||
QString QgsCategorizeUsingStyleAlgorithm::name() const
|
||||
{
|
||||
return QStringLiteral( "categorizeusingstyle" );
|
||||
}
|
||||
|
||||
QString QgsCategorizeUsingStyleAlgorithm::displayName() const
|
||||
{
|
||||
return QObject::tr( "Create categorized renderer from styles" );
|
||||
}
|
||||
|
||||
QStringList QgsCategorizeUsingStyleAlgorithm::tags() const
|
||||
{
|
||||
return QObject::tr( "file,database,symbols,names,category,categories" ).split( ',' );
|
||||
}
|
||||
|
||||
QString QgsCategorizeUsingStyleAlgorithm::group() const
|
||||
{
|
||||
return QObject::tr( "Cartography" );
|
||||
}
|
||||
|
||||
QString QgsCategorizeUsingStyleAlgorithm::groupId() const
|
||||
{
|
||||
return QStringLiteral( "cartography" );
|
||||
}
|
||||
|
||||
QString QgsCategorizeUsingStyleAlgorithm::shortHelpString() const
|
||||
{
|
||||
return QObject::tr( "Sets a vector layer's renderer to a categorized renderer using matching symbols from a style database.\n\n"
|
||||
"The specified expression (or field name) is used to create categories for the renderer. A category will be "
|
||||
"created for each unique value within the layer.\n\n"
|
||||
"Each category is individually matched to the symbols which exist within the specified QGIS XML style database. Whenever "
|
||||
"a matching symbol name is found, the category's symbol will be set to this matched symbol.\n\n"
|
||||
"The matching is case-insensitive by default, but can be made case-sensitive if required.\n\n"
|
||||
"Optionally, non-alphanumeric characters in both the category value and symbol name can be ignored "
|
||||
"while performing the match. This allows for greater tolerance when matching categories to symbols.\n\n"
|
||||
"If desired, tables can also be output containing lists of the categories which could not be matched "
|
||||
"to symbols, and symbols which were not matched to categories."
|
||||
);
|
||||
}
|
||||
|
||||
QString QgsCategorizeUsingStyleAlgorithm::shortDescription() const
|
||||
{
|
||||
return QObject::tr( "Sets a vector layer's renderer to a categorized renderer using symbols from a style database." );
|
||||
}
|
||||
|
||||
QgsCategorizeUsingStyleAlgorithm *QgsCategorizeUsingStyleAlgorithm::createInstance() const
|
||||
{
|
||||
return new QgsCategorizeUsingStyleAlgorithm();
|
||||
}
|
||||
|
||||
class SetCategorizedRendererPostProcessor : public QgsProcessingLayerPostProcessorInterface
|
||||
{
|
||||
public:
|
||||
|
||||
SetCategorizedRendererPostProcessor( std::unique_ptr< QgsCategorizedSymbolRenderer > renderer )
|
||||
: mRenderer( std::move( renderer ) )
|
||||
{}
|
||||
|
||||
void postProcessLayer( QgsMapLayer *layer, QgsProcessingContext &, QgsProcessingFeedback * ) override
|
||||
{
|
||||
if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
|
||||
{
|
||||
|
||||
vl->setRenderer( mRenderer.release() );
|
||||
vl->triggerRepaint();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<QgsCategorizedSymbolRenderer> mRenderer;
|
||||
};
|
||||
|
||||
// Do most of the heavy lifting in a background thread, but save the thread-sensitive stuff for main thread execution!
|
||||
|
||||
bool QgsCategorizeUsingStyleAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
|
||||
{
|
||||
QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
|
||||
if ( !layer )
|
||||
throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
|
||||
|
||||
mField = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
|
||||
|
||||
mLayerId = layer->id();
|
||||
mLayerName = layer->name();
|
||||
mLayerGeometryType = layer->geometryType();
|
||||
mLayerFields = layer->fields();
|
||||
|
||||
mExpressionContext << QgsExpressionContextUtils::globalScope()
|
||||
<< QgsExpressionContextUtils::projectScope( context.project() )
|
||||
<< QgsExpressionContextUtils::layerScope( layer );
|
||||
|
||||
mExpression = QgsExpression( mField );
|
||||
mExpression.prepare( &mExpressionContext );
|
||||
|
||||
QgsFeatureRequest req;
|
||||
req.setSubsetOfAttributes( mExpression.referencedColumns(), mLayerFields );
|
||||
if ( !mExpression.needsGeometry() )
|
||||
req.setFlags( QgsFeatureRequest::NoGeometry );
|
||||
|
||||
mIterator = layer->getFeatures( req );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap QgsCategorizeUsingStyleAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
||||
{
|
||||
const QString styleFile = parameterAsFile( parameters, QStringLiteral( "STYLE" ), context );
|
||||
const bool caseSensitive = parameterAsBool( parameters, QStringLiteral( "CASE_SENSITIVE" ), context );
|
||||
const bool tolerant = parameterAsBool( parameters, QStringLiteral( "TOLERANT" ), context );
|
||||
|
||||
QgsStyle style;
|
||||
if ( !style.importXml( styleFile ) )
|
||||
{
|
||||
throw QgsProcessingException( QObject::tr( "An error occurred while reading style file: %1" ).arg( style.errorString() ) );
|
||||
}
|
||||
|
||||
QgsFields nonMatchingCategoryFields;
|
||||
nonMatchingCategoryFields.append( QgsField( QStringLiteral( "category" ), QVariant::String ) );
|
||||
QString nonMatchingCategoriesDest;
|
||||
std::unique_ptr< QgsFeatureSink > nonMatchingCategoriesSink( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING_CATEGORIES" ), context, nonMatchingCategoriesDest, nonMatchingCategoryFields, QgsWkbTypes::NoGeometry ) );
|
||||
if ( !nonMatchingCategoriesSink && parameters.contains( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ) && parameters.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).isValid() )
|
||||
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING_CATEGORIES" ) ) );
|
||||
|
||||
QgsFields nonMatchingSymbolFields;
|
||||
nonMatchingSymbolFields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
|
||||
QString nonMatchingSymbolsDest;
|
||||
std::unique_ptr< QgsFeatureSink > nonMatchingSymbolsSink( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING_SYMBOLS" ), context, nonMatchingSymbolsDest, nonMatchingSymbolFields, QgsWkbTypes::NoGeometry ) );
|
||||
if ( !nonMatchingSymbolsSink && parameters.contains( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ) && parameters.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).isValid() )
|
||||
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING_SYMBOLS" ) ) );
|
||||
|
||||
QSet<QVariant> uniqueVals;
|
||||
QgsFeature feature;
|
||||
while ( mIterator.nextFeature( feature ) )
|
||||
{
|
||||
mExpressionContext.setFeature( feature );
|
||||
QVariant value = mExpression.evaluate( &mExpressionContext );
|
||||
if ( uniqueVals.contains( value ) )
|
||||
continue;
|
||||
uniqueVals << value;
|
||||
}
|
||||
|
||||
QVariantList sortedUniqueVals = uniqueVals.toList();
|
||||
std::sort( sortedUniqueVals.begin(), sortedUniqueVals.end() );
|
||||
|
||||
QgsCategoryList cats;
|
||||
cats.reserve( uniqueVals.count() );
|
||||
std::unique_ptr< QgsSymbol > defaultSymbol( QgsSymbol::defaultSymbol( mLayerGeometryType ) );
|
||||
for ( const QVariant &val : qgis::as_const( sortedUniqueVals ) )
|
||||
{
|
||||
cats.append( QgsRendererCategory( val, defaultSymbol->clone(), val.toString() ) );
|
||||
}
|
||||
|
||||
mRenderer = qgis::make_unique< QgsCategorizedSymbolRenderer >( mField, cats );
|
||||
|
||||
const QgsSymbol::SymbolType type = mLayerGeometryType == QgsWkbTypes::PointGeometry ? QgsSymbol::Marker
|
||||
: mLayerGeometryType == QgsWkbTypes::LineGeometry ? QgsSymbol::Line
|
||||
: QgsSymbol::Fill;
|
||||
|
||||
QVariantList unmatchedCategories;
|
||||
QStringList unmatchedSymbols;
|
||||
const int matched = mRenderer->matchToSymbols( &style, type, unmatchedCategories, unmatchedSymbols, caseSensitive, tolerant );
|
||||
|
||||
if ( matched > 0 )
|
||||
{
|
||||
feedback->pushInfo( QObject::tr( "Matched %1 categories to symbols from file." ).arg( matched ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
feedback->reportError( QObject::tr( "No categories could be matched to symbols in file." ) );
|
||||
}
|
||||
|
||||
if ( !unmatchedCategories.empty() )
|
||||
{
|
||||
feedback->pushInfo( QObject::tr( "\n%1 categories could not be matched:" ).arg( unmatchedCategories.count() ) );
|
||||
std::sort( unmatchedCategories.begin(), unmatchedCategories.end() );
|
||||
for ( const QVariant &cat : qgis::as_const( unmatchedCategories ) )
|
||||
{
|
||||
feedback->pushInfo( QStringLiteral( "∙ “%1”" ).arg( cat.toString() ) );
|
||||
if ( nonMatchingCategoriesSink )
|
||||
{
|
||||
QgsFeature f;
|
||||
f.setAttributes( QgsAttributes() << cat.toString() );
|
||||
nonMatchingCategoriesSink->addFeature( f, QgsFeatureSink::FastInsert );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !unmatchedSymbols.empty() )
|
||||
{
|
||||
feedback->pushInfo( QObject::tr( "\n%1 symbols in style were not matched:" ).arg( unmatchedSymbols.count() ) );
|
||||
std::sort( unmatchedSymbols.begin(), unmatchedSymbols.end() );
|
||||
for ( const QString &name : qgis::as_const( unmatchedSymbols ) )
|
||||
{
|
||||
feedback->pushInfo( QStringLiteral( "∙ “%1”" ).arg( name ) );
|
||||
if ( nonMatchingSymbolsSink )
|
||||
{
|
||||
QgsFeature f;
|
||||
f.setAttributes( QgsAttributes() << name );
|
||||
nonMatchingSymbolsSink->addFeature( f, QgsFeatureSink::FastInsert );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.addLayerToLoadOnCompletion( mLayerId, QgsProcessingContext::LayerDetails( mLayerName, context.project(), mLayerName ) );
|
||||
context.layerToLoadOnCompletionDetails( mLayerId ).setPostProcessor( new SetCategorizedRendererPostProcessor( std::move( mRenderer ) ) );
|
||||
|
||||
QVariantMap results;
|
||||
results.insert( QStringLiteral( "OUTPUT" ), mLayerId );
|
||||
if ( nonMatchingCategoriesSink )
|
||||
results.insert( QStringLiteral( "NON_MATCHING_CATEGORIES" ), nonMatchingCategoriesDest );
|
||||
if ( nonMatchingSymbolsSink )
|
||||
results.insert( QStringLiteral( "NON_MATCHING_SYMBOLS" ), nonMatchingSymbolsDest );
|
||||
return results;
|
||||
}
|
||||
|
||||
///@endcond
|
||||
|
||||
|
||||
|
73
src/analysis/processing/qgsalgorithmcategorizeusingstyle.h
Normal file
73
src/analysis/processing/qgsalgorithmcategorizeusingstyle.h
Normal file
@ -0,0 +1,73 @@
|
||||
/***************************************************************************
|
||||
qgsalgorithmcategorizeusingstyle.h
|
||||
---------------------
|
||||
begin : August 2018
|
||||
copyright : (C) 2018 by Nyall Dawson
|
||||
email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef QGSALGORITHMCATEGORIZEUSINGSTYLE_H
|
||||
#define QGSALGORITHMCATEGORIZEUSINGSTYLE_H
|
||||
|
||||
#define SIP_NO_FILE
|
||||
|
||||
#include "qgis.h"
|
||||
#include "qgsprocessingalgorithm.h"
|
||||
|
||||
class QgsCategorizedSymbolRenderer;
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
/**
|
||||
* Native create categorized renderer from style algorithm
|
||||
*/
|
||||
class QgsCategorizeUsingStyleAlgorithm : public QgsProcessingAlgorithm
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
QgsCategorizeUsingStyleAlgorithm();
|
||||
~QgsCategorizeUsingStyleAlgorithm() override;
|
||||
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
|
||||
QString name() const override;
|
||||
QString displayName() const override;
|
||||
QStringList tags() const override;
|
||||
QString group() const override;
|
||||
QString groupId() const override;
|
||||
QString shortHelpString() const override;
|
||||
QString shortDescription() const override;
|
||||
QgsCategorizeUsingStyleAlgorithm *createInstance() const override SIP_FACTORY;
|
||||
|
||||
protected:
|
||||
|
||||
bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
|
||||
QVariantMap processAlgorithm( const QVariantMap ¶meters,
|
||||
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
|
||||
|
||||
private:
|
||||
|
||||
QString mField;
|
||||
QString mLayerId;
|
||||
QString mLayerName;
|
||||
QgsWkbTypes::GeometryType mLayerGeometryType = QgsWkbTypes::UnknownGeometry;
|
||||
QgsFields mLayerFields;
|
||||
QgsExpression mExpression;
|
||||
QgsExpressionContext mExpressionContext;
|
||||
QgsFeatureIterator mIterator;
|
||||
std::unique_ptr<QgsCategorizedSymbolRenderer> mRenderer;
|
||||
};
|
||||
|
||||
///@endcond PRIVATE
|
||||
|
||||
#endif // QGSALGORITHMCATEGORIZEUSINGSTYLE_H
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "qgsalgorithmboundary.h"
|
||||
#include "qgsalgorithmboundingbox.h"
|
||||
#include "qgsalgorithmbuffer.h"
|
||||
#include "qgsalgorithmcategorizeusingstyle.h"
|
||||
#include "qgsalgorithmcentroid.h"
|
||||
#include "qgsalgorithmclip.h"
|
||||
#include "qgsalgorithmconvexhull.h"
|
||||
@ -143,6 +144,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
|
||||
addAlgorithm( new QgsBoundaryAlgorithm() );
|
||||
addAlgorithm( new QgsBoundingBoxAlgorithm() );
|
||||
addAlgorithm( new QgsBufferAlgorithm() );
|
||||
addAlgorithm( new QgsCategorizeUsingStyleAlgorithm() );
|
||||
addAlgorithm( new QgsCentroidAlgorithm() );
|
||||
addAlgorithm( new QgsClipAlgorithm() );
|
||||
addAlgorithm( new QgsCollectAlgorithm() );
|
||||
|
@ -27,6 +27,8 @@
|
||||
#include "qgsalgorithmtransform.h"
|
||||
#include "qgsalgorithmkmeansclustering.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgscategorizedsymbolrenderer.h"
|
||||
#include "qgssinglesymbolrenderer.h"
|
||||
|
||||
class TestQgsProcessingAlgs: public QObject
|
||||
{
|
||||
@ -44,6 +46,7 @@ class TestQgsProcessingAlgs: public QObject
|
||||
void featureFilterAlg();
|
||||
void transformAlg();
|
||||
void kmeansCluster();
|
||||
void categorizeByStyle();
|
||||
|
||||
private:
|
||||
|
||||
@ -496,6 +499,135 @@ void TestQgsProcessingAlgs::kmeansCluster()
|
||||
QCOMPARE( features[ 2 ].cluster, -1 );
|
||||
}
|
||||
|
||||
void TestQgsProcessingAlgs::categorizeByStyle()
|
||||
{
|
||||
std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:categorizeusingstyle" ) ) );
|
||||
QVERIFY( alg != nullptr );
|
||||
|
||||
std::unique_ptr< QgsProcessingContext > context = qgis::make_unique< QgsProcessingContext >();
|
||||
QgsProject p;
|
||||
context->setProject( &p );
|
||||
|
||||
QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
|
||||
QString styleFileName = dataDir + "/categorized.xml";
|
||||
|
||||
|
||||
QgsProcessingFeedback feedback;
|
||||
|
||||
QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "Point?crs=EPSG:4326&field=col1:string" ), QStringLiteral( "test" ), QStringLiteral( "memory" ) );
|
||||
QVERIFY( layer->isValid() );
|
||||
QgsFeature f, f2, f3;
|
||||
f.setAttributes( QgsAttributes() << "a" );
|
||||
QVERIFY( layer->dataProvider()->addFeature( f ) );
|
||||
f2.setAttributes( QgsAttributes() << "b" );
|
||||
QVERIFY( layer->dataProvider()->addFeature( f2 ) );
|
||||
f3.setAttributes( QgsAttributes() << "c " );
|
||||
QVERIFY( layer->dataProvider()->addFeature( f3 ) );
|
||||
p.addMapLayer( layer );
|
||||
|
||||
QVariantMap parameters;
|
||||
parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "test" ) );
|
||||
parameters.insert( QStringLiteral( "FIELD" ), QStringLiteral( "col1" ) );
|
||||
parameters.insert( QStringLiteral( "STYLE" ), styleFileName );
|
||||
parameters.insert( QStringLiteral( "CASE_SENSITIVE" ), true );
|
||||
parameters.insert( QStringLiteral( "TOLERANT" ), false );
|
||||
parameters.insert( QStringLiteral( "NON_MATCHING_CATEGORIES" ), QStringLiteral( "memory:" ) );
|
||||
parameters.insert( QStringLiteral( "NON_MATCHING_SYMBOLS" ), QStringLiteral( "memory:" ) );
|
||||
|
||||
bool ok = false;
|
||||
QVariantMap results = alg->run( parameters, *context, &feedback, &ok );
|
||||
QVERIFY( ok );
|
||||
context->layerToLoadOnCompletionDetails( layer->id() ).postProcessor()->postProcessLayer( layer, *context, &feedback );
|
||||
QgsCategorizedSymbolRenderer *catRenderer = dynamic_cast< QgsCategorizedSymbolRenderer * >( layer->renderer() );
|
||||
QVERIFY( catRenderer );
|
||||
|
||||
auto allValues = []( QgsVectorLayer * layer )->QStringList
|
||||
{
|
||||
QStringList all;
|
||||
QgsFeature f;
|
||||
QgsFeatureIterator it = layer->getFeatures();
|
||||
while ( it.nextFeature( f ) )
|
||||
{
|
||||
all.append( f.attribute( 0 ).toString() );
|
||||
}
|
||||
return all;
|
||||
};
|
||||
QgsVectorLayer *nonMatchingCats = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).toString() ) );
|
||||
QCOMPARE( allValues( nonMatchingCats ), QStringList() << "b" << "c " );
|
||||
QgsVectorLayer *nonMatchingSymbols = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).toString() ) );
|
||||
QCOMPARE( allValues( nonMatchingSymbols ), QStringList() << " ----c/- " << "B " );
|
||||
|
||||
QCOMPARE( catRenderer->categories().count(), 3 );
|
||||
QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "a" ) ) ).symbol()->color().name(), QStringLiteral( "#ff0000" ) );
|
||||
QVERIFY( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "b" ) ) ).symbol()->color().name() != QStringLiteral( "#00ff00" ) );
|
||||
QVERIFY( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "c " ) ) ).symbol()->color().name() != QStringLiteral( "#0000ff" ) );
|
||||
// reset renderer
|
||||
layer->setRenderer( new QgsSingleSymbolRenderer( QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) ) );
|
||||
|
||||
// case insensitive
|
||||
parameters.insert( QStringLiteral( "CASE_SENSITIVE" ), false );
|
||||
ok = false;
|
||||
results = alg->run( parameters, *context, &feedback, &ok );
|
||||
QVERIFY( ok );
|
||||
context->layerToLoadOnCompletionDetails( layer->id() ).postProcessor()->postProcessLayer( layer, *context, &feedback );
|
||||
catRenderer = dynamic_cast< QgsCategorizedSymbolRenderer * >( layer->renderer() );
|
||||
QVERIFY( catRenderer );
|
||||
|
||||
nonMatchingCats = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).toString() ) );
|
||||
QCOMPARE( allValues( nonMatchingCats ), QStringList() << "c " );
|
||||
nonMatchingSymbols = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).toString() ) );
|
||||
QCOMPARE( allValues( nonMatchingSymbols ), QStringList() << " ----c/- " );
|
||||
|
||||
QCOMPARE( catRenderer->categories().count(), 3 );
|
||||
QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "a" ) ) ).symbol()->color().name(), QStringLiteral( "#ff0000" ) );
|
||||
QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "b" ) ) ).symbol()->color().name(), QStringLiteral( "#00ff00" ) );
|
||||
QVERIFY( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "c " ) ) ).symbol()->color().name() != QStringLiteral( "#0000ff" ) );
|
||||
// reset renderer
|
||||
layer->setRenderer( new QgsSingleSymbolRenderer( QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) ) );
|
||||
|
||||
// tolerant
|
||||
parameters.insert( QStringLiteral( "CASE_SENSITIVE" ), true );
|
||||
parameters.insert( QStringLiteral( "TOLERANT" ), true );
|
||||
|
||||
ok = false;
|
||||
results = alg->run( parameters, *context, &feedback, &ok );
|
||||
QVERIFY( ok );
|
||||
context->layerToLoadOnCompletionDetails( layer->id() ).postProcessor()->postProcessLayer( layer, *context, &feedback );
|
||||
catRenderer = dynamic_cast< QgsCategorizedSymbolRenderer * >( layer->renderer() );
|
||||
QVERIFY( catRenderer );
|
||||
|
||||
nonMatchingCats = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).toString() ) );
|
||||
QCOMPARE( allValues( nonMatchingCats ), QStringList() << "b" );
|
||||
nonMatchingSymbols = qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).toString() ) );
|
||||
QCOMPARE( allValues( nonMatchingSymbols ), QStringList() << "B " );
|
||||
|
||||
QCOMPARE( catRenderer->categories().count(), 3 );
|
||||
QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "a" ) ) ).symbol()->color().name(), QStringLiteral( "#ff0000" ) );
|
||||
QVERIFY( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "b" ) ) ).symbol()->color().name() != QStringLiteral( "#00ff00" ) );
|
||||
QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "c " ) ) ).symbol()->color().name(), QStringLiteral( "#0000ff" ) );
|
||||
// reset renderer
|
||||
layer->setRenderer( new QgsSingleSymbolRenderer( QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) ) );
|
||||
|
||||
// no optional sinks
|
||||
parameters.insert( QStringLiteral( "CASE_SENSITIVE" ), false );
|
||||
parameters.remove( QStringLiteral( "NON_MATCHING_CATEGORIES" ) );
|
||||
parameters.remove( QStringLiteral( "NON_MATCHING_SYMBOLS" ) );
|
||||
ok = false;
|
||||
results = alg->run( parameters, *context, &feedback, &ok );
|
||||
QVERIFY( ok );
|
||||
context->layerToLoadOnCompletionDetails( layer->id() ).postProcessor()->postProcessLayer( layer, *context, &feedback );
|
||||
catRenderer = dynamic_cast< QgsCategorizedSymbolRenderer * >( layer->renderer() );
|
||||
QVERIFY( catRenderer );
|
||||
|
||||
QVERIFY( !context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).toString() ) );
|
||||
QVERIFY( !context->getMapLayer( results.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).toString() ) );
|
||||
|
||||
QCOMPARE( catRenderer->categories().count(), 3 );
|
||||
QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "a" ) ) ).symbol()->color().name(), QStringLiteral( "#ff0000" ) );
|
||||
QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "b" ) ) ).symbol()->color().name(), QStringLiteral( "#00ff00" ) );
|
||||
QCOMPARE( catRenderer->categories().at( catRenderer->categoryIndexForValue( QStringLiteral( "c " ) ) ).symbol()->color().name(), QStringLiteral( "#0000ff" ) );
|
||||
}
|
||||
|
||||
|
||||
QGSTEST_MAIN( TestQgsProcessingAlgs )
|
||||
#include "testqgsprocessingalgs.moc"
|
||||
|
93
tests/testdata/categorized.xml
vendored
Normal file
93
tests/testdata/categorized.xml
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
<!DOCTYPE qgis_style>
|
||||
<qgis_style version="1">
|
||||
<symbols>
|
||||
<symbol tags="New tag" type="marker" alpha="1" clip_to_extent="1" name=" ----c/- ">
|
||||
<layer locked="0" class="SimpleMarker" pass="0" enabled="1">
|
||||
<prop k="angle" v="0"/>
|
||||
<prop k="color" v="0,0,255,255"/>
|
||||
<prop k="horizontal_anchor_point" v="1"/>
|
||||
<prop k="joinstyle" v="bevel"/>
|
||||
<prop k="name" v="circle"/>
|
||||
<prop k="offset" v="0,0"/>
|
||||
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
||||
<prop k="offset_unit" v="MM"/>
|
||||
<prop k="outline_color" v="35,35,35,255"/>
|
||||
<prop k="outline_style" v="solid"/>
|
||||
<prop k="outline_width" v="0"/>
|
||||
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
||||
<prop k="outline_width_unit" v="MM"/>
|
||||
<prop k="scale_method" v="diameter"/>
|
||||
<prop k="size" v="2"/>
|
||||
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
||||
<prop k="size_unit" v="MM"/>
|
||||
<prop k="vertical_anchor_point" v="1"/>
|
||||
<data_defined_properties>
|
||||
<Option type="Map">
|
||||
<Option value="" type="QString" name="name"/>
|
||||
<Option name="properties"/>
|
||||
<Option value="collection" type="QString" name="type"/>
|
||||
</Option>
|
||||
</data_defined_properties>
|
||||
</layer>
|
||||
</symbol>
|
||||
<symbol tags="New tag" type="marker" alpha="1" clip_to_extent="1" name="B ">
|
||||
<layer locked="0" class="SimpleMarker" pass="0" enabled="1">
|
||||
<prop k="angle" v="0"/>
|
||||
<prop k="color" v="0,255,0,255"/>
|
||||
<prop k="horizontal_anchor_point" v="1"/>
|
||||
<prop k="joinstyle" v="bevel"/>
|
||||
<prop k="name" v="circle"/>
|
||||
<prop k="offset" v="0,0"/>
|
||||
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
||||
<prop k="offset_unit" v="MM"/>
|
||||
<prop k="outline_color" v="35,35,35,255"/>
|
||||
<prop k="outline_style" v="solid"/>
|
||||
<prop k="outline_width" v="0"/>
|
||||
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
||||
<prop k="outline_width_unit" v="MM"/>
|
||||
<prop k="scale_method" v="diameter"/>
|
||||
<prop k="size" v="2"/>
|
||||
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
||||
<prop k="size_unit" v="MM"/>
|
||||
<prop k="vertical_anchor_point" v="1"/>
|
||||
<data_defined_properties>
|
||||
<Option type="Map">
|
||||
<Option value="" type="QString" name="name"/>
|
||||
<Option name="properties"/>
|
||||
<Option value="collection" type="QString" name="type"/>
|
||||
</Option>
|
||||
</data_defined_properties>
|
||||
</layer>
|
||||
</symbol>
|
||||
<symbol tags="New tag" type="marker" alpha="1" clip_to_extent="1" name="a">
|
||||
<layer locked="0" class="SimpleMarker" pass="0" enabled="1">
|
||||
<prop k="angle" v="0"/>
|
||||
<prop k="color" v="255,0,0,255"/>
|
||||
<prop k="horizontal_anchor_point" v="1"/>
|
||||
<prop k="joinstyle" v="bevel"/>
|
||||
<prop k="name" v="circle"/>
|
||||
<prop k="offset" v="0,0"/>
|
||||
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
||||
<prop k="offset_unit" v="MM"/>
|
||||
<prop k="outline_color" v="35,35,35,255"/>
|
||||
<prop k="outline_style" v="solid"/>
|
||||
<prop k="outline_width" v="0"/>
|
||||
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
||||
<prop k="outline_width_unit" v="MM"/>
|
||||
<prop k="scale_method" v="diameter"/>
|
||||
<prop k="size" v="2"/>
|
||||
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
|
||||
<prop k="size_unit" v="MM"/>
|
||||
<prop k="vertical_anchor_point" v="1"/>
|
||||
<data_defined_properties>
|
||||
<Option type="Map">
|
||||
<Option value="" type="QString" name="name"/>
|
||||
<Option name="properties"/>
|
||||
<Option value="collection" type="QString" name="type"/>
|
||||
</Option>
|
||||
</data_defined_properties>
|
||||
</layer>
|
||||
</symbol>
|
||||
</symbols>
|
||||
<colorramps/>
|
||||
</qgis_style>
|
Loading…
x
Reference in New Issue
Block a user