Skip to content

Commit

Permalink
Add method to evaluate parameters to compatible vector layers
Browse files Browse the repository at this point in the history
of a specified type
  • Loading branch information
nyalldawson committed Jul 8, 2017
1 parent f8e37aa commit 9e184fe
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 0 deletions.
16 changes: 16 additions & 0 deletions python/core/processing/qgsprocessingparameters.sip
Expand Up @@ -472,6 +472,22 @@ class QgsProcessingParameters
:rtype: QgsProcessingFeatureSource
%End

static QString parameterAsCompatibleSourceLayerPath( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters,
QgsProcessingContext &context, const QStringList &compatibleFormats, const QString &preferredFormat = QString( "shp" ), QgsProcessingFeedback *feedback = 0 );
%Docstring
Evaluates the parameter with matching ``definition`` to a source vector layer file path of compatible format.

If the parameter is evaluated to an existing layer, and that layer is not of the format listed in the
``compatibleFormats`` argument, then the layer will first be exported to a compatible format
in a temporary location. The function will then return the path to that temporary file.

``compatibleFormats`` should consist entirely of lowercase file extensions, e.g. 'shp'.

The ``preferredFormat`` argument is used to specify to desired file extension to use when a temporary
layer export is required. This defaults to shapefiles, because shapefiles are the future (don't believe the geopackage hype!).
:rtype: str
%End

static QgsMapLayer *parameterAsLayer( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context );
%Docstring
Evaluates the parameter with matching ``definition`` to a map layer.
Expand Down
21 changes: 21 additions & 0 deletions python/core/processing/qgsprocessingutils.sip
Expand Up @@ -158,6 +158,27 @@ class QgsProcessingUtils
:rtype: str
%End

static QString convertToCompatibleFormat( const QgsVectorLayer *layer,
bool selectedFeaturesOnly,
const QString &baseName,
const QStringList &compatibleFormats,
const QString &preferredFormat,
QgsProcessingContext &context,
QgsProcessingFeedback *feedback );
%Docstring
Converts a source vector ``layer`` to a file path to a vector layer of compatible format.

If the specified ``layer`` is not of the format listed in the
``compatibleFormats`` argument, then the layer will first be exported to a compatible format
in a temporary location using ``baseName``. The function will then return the path to that temporary file.

``compatibleFormats`` should consist entirely of lowercase file extensions, e.g. 'shp'.

The ``preferredFormat`` argument is used to specify to desired file extension to use when a temporary
layer export is required. This defaults to shapefiles.
:rtype: str
%End

};

class QgsProcessingFeatureSource : QgsFeatureSource
Expand Down
44 changes: 44 additions & 0 deletions src/core/processing/qgsprocessingparameters.cpp
Expand Up @@ -21,6 +21,7 @@
#include "qgsvectorlayerfeatureiterator.h"
#include "qgsprocessingoutputs.h"
#include "qgssettings.h"
#include "qgsvectorfilewriter.h"

bool QgsProcessingParameters::isDynamic( const QVariantMap &parameters, const QString &name )
{
Expand Down Expand Up @@ -327,6 +328,49 @@ QgsProcessingFeatureSource *QgsProcessingParameters::parameterAsSource( const Qg
}
}

QString QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context, const QStringList &compatibleFormats, const QString &preferredFormat, QgsProcessingFeedback *feedback )
{
if ( !definition )
return QString();

QVariant val = parameters.value( definition->name() );

bool selectedFeaturesOnly = false;
if ( val.canConvert<QgsProcessingFeatureSourceDefinition>() )
{
// input is a QgsProcessingFeatureSourceDefinition - get extra properties from it
QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( val );
selectedFeaturesOnly = fromVar.selectedFeaturesOnly;
val = fromVar.source;
}

QString layerRef;
if ( val.canConvert<QgsProperty>() )
{
layerRef = val.value< QgsProperty >().valueAsString( context.expressionContext(), definition->defaultValue().toString() );
}
else if ( !val.isValid() || val.toString().isEmpty() )
{
// fall back to default
layerRef = definition->defaultValue().toString();
}
else
{
layerRef = val.toString();
}

if ( layerRef.isEmpty() )
return QString();

QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( layerRef, context ) );
if ( !vl )
return QString();

return QgsProcessingUtils::convertToCompatibleFormat( vl, selectedFeaturesOnly, definition->name(),
compatibleFormats, preferredFormat, context, feedback );
}


QgsMapLayer *QgsProcessingParameters::parameterAsLayer( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context )
{
if ( !definition )
Expand Down
16 changes: 16 additions & 0 deletions src/core/processing/qgsprocessingparameters.h
Expand Up @@ -32,6 +32,7 @@ class QgsFeatureSink;
class QgsFeatureSource;
class QgsProcessingFeatureSource;
class QgsProcessingOutputDefinition;
class QgsProcessingFeedback;

/**
* \class QgsProcessingFeatureSourceDefinition
Expand Down Expand Up @@ -508,6 +509,21 @@ class CORE_EXPORT QgsProcessingParameters
*/
static QgsProcessingFeatureSource *parameterAsSource( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context ) SIP_FACTORY;

/**
* Evaluates the parameter with matching \a definition to a source vector layer file path of compatible format.
*
* If the parameter is evaluated to an existing layer, and that layer is not of the format listed in the
* \a compatibleFormats argument, then the layer will first be exported to a compatible format
* in a temporary location. The function will then return the path to that temporary file.
*
* \a compatibleFormats should consist entirely of lowercase file extensions, e.g. 'shp'.
*
* The \a preferredFormat argument is used to specify to desired file extension to use when a temporary
* layer export is required. This defaults to shapefiles, because shapefiles are the future (don't believe the geopackage hype!).
*/
static QString parameterAsCompatibleSourceLayerPath( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters,
QgsProcessingContext &context, const QStringList &compatibleFormats, const QString &preferredFormat = QString( "shp" ), QgsProcessingFeedback *feedback = nullptr );

/**
* Evaluates the parameter with matching \a definition to a map layer.
*
Expand Down
36 changes: 36 additions & 0 deletions src/core/processing/qgsprocessingutils.cpp
Expand Up @@ -461,6 +461,42 @@ QString QgsProcessingUtils::formatHelpMapAsHtml( const QVariantMap &map, const Q
return s;
}

QString QgsProcessingUtils::convertToCompatibleFormat( const QgsVectorLayer *vl, bool selectedFeaturesOnly, const QString &baseName, const QStringList &compatibleFormats, const QString &preferredFormat, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
bool requiresTranslation = selectedFeaturesOnly;
if ( !selectedFeaturesOnly )
{
QFileInfo fi( vl->source() );
requiresTranslation = !compatibleFormats.contains( fi.suffix(), Qt::CaseInsensitive );
}

if ( requiresTranslation )
{
QString temp = QgsProcessingUtils::generateTempFilename( baseName + '.' + preferredFormat );

QgsVectorFileWriter writer( temp, context.defaultEncoding(),
vl->fields(), vl->wkbType(), vl->crs(), QgsVectorFileWriter::driverForExtension( preferredFormat ) );
QgsFeature f;
QgsFeatureIterator it;
if ( selectedFeaturesOnly )
it = vl->getSelectedFeatures();
else
it = vl->getFeatures();

while ( it.nextFeature( f ) )
{
if ( feedback->isCanceled() )
return QString();
writer.addFeature( f, QgsFeatureSink::FastInsert );
}
return temp;
}
else
{
return vl->source();
}
}


//
// QgsProcessingFeatureSource
Expand Down
21 changes: 21 additions & 0 deletions src/core/processing/qgsprocessingutils.h
Expand Up @@ -28,6 +28,7 @@
class QgsProject;
class QgsProcessingContext;
class QgsMapLayerStore;
class QgsProcessingFeedback;

#include <QString>
#include <QVariant>
Expand Down Expand Up @@ -189,6 +190,26 @@ class CORE_EXPORT QgsProcessingUtils
*/
static QString formatHelpMapAsHtml( const QVariantMap &map, const QgsProcessingAlgorithm *algorithm );

/**
* Converts a source vector \a layer to a file path to a vector layer of compatible format.
*
* If the specified \a layer is not of the format listed in the
* \a compatibleFormats argument, then the layer will first be exported to a compatible format
* in a temporary location using \a baseName. The function will then return the path to that temporary file.
*
* \a compatibleFormats should consist entirely of lowercase file extensions, e.g. 'shp'.
*
* The \a preferredFormat argument is used to specify to desired file extension to use when a temporary
* layer export is required. This defaults to shapefiles.
*/
static QString convertToCompatibleFormat( const QgsVectorLayer *layer,
bool selectedFeaturesOnly,
const QString &baseName,
const QStringList &compatibleFormats,
const QString &preferredFormat,
QgsProcessingContext &context,
QgsProcessingFeedback *feedback );

private:

static bool canUseLayer( const QgsRasterLayer *layer );
Expand Down
76 changes: 76 additions & 0 deletions tests/src/core/testqgsprocessing.cpp
Expand Up @@ -333,6 +333,7 @@ class TestQgsProcessing: public QObject
void modelExecution();
void modelAcceptableValues();
void tempUtils();
void convertCompatible();

private:

Expand Down Expand Up @@ -5039,5 +5040,80 @@ void TestQgsProcessing::tempUtils()
QVERIFY( tempFile2.startsWith( tempFolder ) );
}

void TestQgsProcessing::convertCompatible()
{
// start with a compatible shapefile
QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
QString vector = testDataDir + "points.shp";
QgsVectorLayer *layer = new QgsVectorLayer( vector, "vl" );
QgsProject p;
p.addMapLayer( layer );

QgsProcessingContext context;
context.setProject( &p );
QgsProcessingFeedback feedback;
QString out = QgsProcessingUtils::convertToCompatibleFormat( layer, false, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback );
// layer should be returned unchanged - underlying source is compatible
QCOMPARE( out, layer->source() );

// don't include shp as compatible type
out = QgsProcessingUtils::convertToCompatibleFormat( layer, false, QStringLiteral( "test" ), QStringList() << "tab", QString( "tab" ), context, &feedback );
QVERIFY( out != layer->source() );
QVERIFY( out.endsWith( ".tab" ) );
QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );

// make sure all features are copied
QgsVectorLayer *t = new QgsVectorLayer( out, "vl2" );
QCOMPARE( layer->featureCount(), t->featureCount() );
QCOMPARE( layer->crs(), t->crs() );

// use a selection - this will require translation
QgsFeatureIds ids;
QgsFeature f;
QgsFeatureIterator it = layer->getFeatures();
it.nextFeature( f );
ids.insert( f.id() );
it.nextFeature( f );
ids.insert( f.id() );

layer->selectByIds( ids );
out = QgsProcessingUtils::convertToCompatibleFormat( layer, true, QStringLiteral( "test" ), QStringList() << "tab", QString( "tab" ), context, &feedback );
QVERIFY( out != layer->source() );
QVERIFY( out.endsWith( ".tab" ) );
QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
delete t;
t = new QgsVectorLayer( out, "vl2" );
QCOMPARE( t->featureCount(), static_cast< long >( ids.count() ) );

// using a selection but existing format - will still require translation
out = QgsProcessingUtils::convertToCompatibleFormat( layer, true, QStringLiteral( "test" ), QStringList() << "shp", QString( "shp" ), context, &feedback );
QVERIFY( out != layer->source() );
QVERIFY( out.endsWith( ".shp" ) );
QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
delete t;
t = new QgsVectorLayer( out, "vl2" );
QCOMPARE( t->featureCount(), static_cast< long >( ids.count() ) );


// also test evaluating parameter to compatible format
std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterFeatureSource( QStringLiteral( "source" ) ) );
QVariantMap params;
params.insert( QStringLiteral( "source" ), QgsProcessingFeatureSourceDefinition( layer->id(), false ) );
out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback );
QCOMPARE( out, layer->source() );

out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "tab", QString( "tab" ), &feedback );
QVERIFY( out != layer->source() );
QVERIFY( out.endsWith( ".tab" ) );
QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );

// selected only, will force export
params.insert( QStringLiteral( "source" ), QgsProcessingFeatureSourceDefinition( layer->id(), true ) );
out = QgsProcessingParameters::parameterAsCompatibleSourceLayerPath( def.get(), params, context, QStringList() << "shp", QString( "shp" ), &feedback );
QVERIFY( out != layer->source() );
QVERIFY( out.endsWith( ".shp" ) );
QVERIFY( out.startsWith( QgsProcessingUtils::tempFolder() ) );
}

QGSTEST_MAIN( TestQgsProcessing )
#include "testqgsprocessing.moc"

0 comments on commit 9e184fe

Please sign in to comment.