QGIS/tests/src/core/testqgsproperty.cpp

1801 lines
75 KiB
C++

/***************************************************************************
testqgsproperty.cpp
-------------------
begin : April 2015
copyright : (C) 2015 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 "qgstest.h"
#include "qgsproperty.h"
#include "qgspropertycollection.h"
#include "qgsvectorlayer.h"
#include "qgsapplication.h"
#include "qgscolorramp.h"
#include "qgssymbollayerutils.h"
#include <QObject>
enum PropertyKeys
{
Property1,
Property2,
Property3,
Property4,
};
class TestTransformer : public QgsPropertyTransformer
{
public:
TestTransformer( double minValue, double maxValue )
: QgsPropertyTransformer( minValue, maxValue )
{
}
virtual Type transformerType() const override { return SizeScaleTransformer; }
virtual TestTransformer *clone() const override
{
return new TestTransformer( mMinValue, mMaxValue );
}
virtual QString toExpression( const QString & ) const override { return QString(); }
private:
virtual QVariant transform( const QgsExpressionContext &context, const QVariant &value ) const override
{
Q_UNUSED( context );
if ( value.isNull() )
return -1.0;
return value.toDouble() * 2;
}
};
class TestQgsProperty : public QObject
{
Q_OBJECT
private slots:
void initTestCase();// will be called before the first testfunction is executed.
void cleanupTestCase();// will be called after the last testfunction was executed.
void init();// will be called before each testfunction is executed.
void cleanup();// will be called after every testfunction.
void conversions(); //test QgsProperty static conversion methods
void invalid(); //test invalid properties
void staticProperty(); //test for QgsStaticProperty
void fieldBasedProperty(); //test for QgsFieldBasedProperty
void expressionBasedProperty(); //test for QgsExpressionBasedProperty
void equality();
void propertyTransformer(); //test for QgsPropertyTransformer
void propertyTransformerFromExpression(); // text converting expression into QgsPropertyTransformer
void genericNumericTransformer();
void genericNumericTransformerFromExpression(); // text converting expression to QgsGenericNumericTransformer
void sizeScaleTransformer(); //test for QgsSizeScaleTransformer
void sizeScaleTransformerFromExpression(); // text converting expression to QgsSizeScaleTransformer
void colorRampTransformer(); //test for QgsColorRampTransformer
void propertyToTransformer(); //test converting expression based property to transformer/expression pair
void asExpression(); //test converting property to expression
void propertyCollection(); //test for QgsPropertyCollection
void collectionStack(); //test for QgsPropertyCollectionStack
void curveTransform();
void asVariant();
private:
QgsPropertiesDefinition mDefinitions;
void checkCurveResult( const QList< QgsPointXY > &controlPoints, const QVector<double> &x, const QVector<double> &y );
};
void TestQgsProperty::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
mDefinitions.insert( Property1, QgsPropertyDefinition( "p1", QgsPropertyDefinition::DataTypeString, QString(), QString() ) );
mDefinitions.insert( Property2, QgsPropertyDefinition( "p2", QgsPropertyDefinition::DataTypeString, QString(), QString() ) );
mDefinitions.insert( Property3, QgsPropertyDefinition( "p3", QgsPropertyDefinition::DataTypeString, QString(), QString() ) );
mDefinitions.insert( Property4, QgsPropertyDefinition( "p4", QgsPropertyDefinition::DataTypeString, QString(), QString() ) );
}
void TestQgsProperty::cleanupTestCase()
{
QgsApplication::exitQgis();
}
void TestQgsProperty::init()
{
}
void TestQgsProperty::cleanup()
{
}
void TestQgsProperty::conversions()
{
QgsExpressionContext context;
//all these tests are done for both a property and a collection
QgsPropertyCollection collection;
//test color conversions
//no color, should return defaultColor
QgsProperty c1 = QgsProperty::fromValue( QVariant(), true );
collection.setProperty( 0, c1 );
QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
c1.setStaticValue( QColor( 255, 200, 100, 50 ) ); //color in qvariant
collection.property( 0 ).setStaticValue( QColor( 255, 200, 100, 50 ) ); //color in qvariant
QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 255, 200, 100, 50 ) );
QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 255, 200, 100, 50 ) );
c1.setStaticValue( QColor() ); //invalid color in qvariant, should return default color
collection.property( 0 ).setStaticValue( QColor() ); //invalid color in qvariant, should return default color
QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
c1.setStaticValue( QgsSymbolLayerUtils::encodeColor( QColor( 255, 200, 100, 50 ) ) ); //encoded color
collection.property( 0 ).setStaticValue( QgsSymbolLayerUtils::encodeColor( QColor( 255, 200, 100, 50 ) ) ); //encoded color
QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 255, 200, 100, 50 ) );
QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 255, 200, 100, 50 ) );
c1.setStaticValue( "i am not a color" ); //badly encoded color, should return default color
collection.property( 0 ).setStaticValue( "i am not a color" ); //badly encoded color, should return default color
QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
// test double conversions
QgsProperty d1 = QgsProperty::fromValue( QVariant(), true );
collection.setProperty( 1, d1 );
QCOMPARE( d1.valueAsDouble( context, -1.2 ), -1.2 );
QCOMPARE( collection.valueAsDouble( 1, context, -1.2 ), -1.2 );
d1.setStaticValue( 12.3 ); //double in qvariant
collection.property( 1 ).setStaticValue( 12.3 ); //double in qvariant
QCOMPARE( d1.valueAsDouble( context, -1.2 ), 12.3 );
QCOMPARE( collection.valueAsDouble( 1, context, -1.2 ), 12.3 );
d1.setStaticValue( "15.6" ); //double as string
collection.property( 1 ).setStaticValue( "15.6" ); //double as string
QCOMPARE( d1.valueAsDouble( context, -1.2 ), 15.6 );
QCOMPARE( collection.valueAsDouble( 1, context, -1.2 ), 15.6 );
d1.setStaticValue( "i am not a double" ); //not a double, should return default value
collection.property( 1 ).setStaticValue( "i am not a double" ); //not a double, should return default value
QCOMPARE( d1.valueAsDouble( context, -1.2 ), -1.2 );
QCOMPARE( collection.valueAsDouble( 1, context, -1.2 ), -1.2 );
// test integer conversions
QgsProperty i1 = QgsProperty::fromValue( QVariant(), true );
collection.setProperty( 2, i1 );
QCOMPARE( i1.valueAsInt( context, -11 ), -11 );
QCOMPARE( collection.valueAsInt( 2, context, -11 ), -11 );
i1.setStaticValue( 13 ); //integer in qvariant
collection.property( 2 ).setStaticValue( 13 ); //integer in qvariant
QCOMPARE( i1.valueAsInt( context, -11 ), 13 );
QCOMPARE( collection.valueAsInt( 2, context, -11 ), 13 );
i1.setStaticValue( 13.9 ); //double in qvariant, should be rounded
collection.property( 2 ).setStaticValue( 13.9 ); //double in qvariant, should be rounded
QCOMPARE( i1.valueAsInt( context, -11 ), 14 );
QCOMPARE( collection.valueAsInt( 2, context, -11 ), 14 );
i1.setStaticValue( "15" ); //integer as string
collection.property( 2 ).setStaticValue( "15" ); //integer as string
QCOMPARE( i1.valueAsInt( context, -11 ), 15 );
QCOMPARE( collection.valueAsInt( 2, context, -11 ), 15 );
i1.setStaticValue( "15.9" ); //double as string, should be rounded
collection.property( 2 ).setStaticValue( "15.9" ); //double as string, should be rounded
QCOMPARE( i1.valueAsInt( context, -11 ), 16 );
QCOMPARE( collection.valueAsInt( 2, context, -11 ), 16 );
i1.setStaticValue( "i am not a int" ); //not a int, should return default value
collection.property( 2 ).setStaticValue( "i am not a int" ); //not a int, should return default value
QCOMPARE( i1.valueAsInt( context, -11 ), -11 );
QCOMPARE( collection.valueAsInt( 2, context, -11 ), -11 );
// test boolean conversions
QgsProperty b1 = QgsProperty::fromValue( QVariant(), true );
collection.setProperty( 3, b1 );
QCOMPARE( b1.valueAsBool( context, false ), false );
QCOMPARE( b1.valueAsBool( context, true ), true );
QCOMPARE( collection.valueAsBool( 3, context, false ), false );
QCOMPARE( collection.valueAsBool( 3, context, true ), true );
b1.setStaticValue( true );
collection.property( 3 ).setStaticValue( true );
QCOMPARE( b1.valueAsBool( context, false ), true );
QCOMPARE( b1.valueAsBool( context, true ), true );
QCOMPARE( collection.valueAsBool( 3, context, false ), true );
QCOMPARE( collection.valueAsBool( 3, context, true ), true );
b1.setStaticValue( false );
collection.property( 3 ).setStaticValue( false );
QCOMPARE( b1.valueAsBool( context, false ), false );
QCOMPARE( b1.valueAsBool( context, true ), false );
QCOMPARE( collection.valueAsBool( 3, context, false ), false );
QCOMPARE( collection.valueAsBool( 3, context, true ), false );
b1.setStaticValue( 1 );
collection.property( 3 ).setStaticValue( 1 );
QCOMPARE( b1.valueAsBool( context, false ), true );
QCOMPARE( b1.valueAsBool( context, true ), true );
QCOMPARE( collection.valueAsBool( 3, context, false ), true );
QCOMPARE( collection.valueAsBool( 3, context, true ), true );
b1.setStaticValue( 0 );
collection.property( 3 ).setStaticValue( 0 );
QCOMPARE( b1.valueAsBool( context, false ), false );
QCOMPARE( b1.valueAsBool( context, true ), false );
QCOMPARE( collection.valueAsBool( 3, context, false ), false );
QCOMPARE( collection.valueAsBool( 3, context, true ), false );
b1.setStaticValue( "true" );
collection.property( 3 ).setStaticValue( "true" );
QCOMPARE( b1.valueAsBool( context, false ), true );
QCOMPARE( b1.valueAsBool( context, true ), true );
QCOMPARE( collection.valueAsBool( 3, context, false ), true );
QCOMPARE( collection.valueAsBool( 3, context, true ), true );
b1.setStaticValue( "" );
collection.property( 3 ).setStaticValue( "" );
QCOMPARE( b1.valueAsBool( context, false ), false );
QCOMPARE( b1.valueAsBool( context, true ), false );
QCOMPARE( collection.valueAsBool( 3, context, false ), false );
QCOMPARE( collection.valueAsBool( 3, context, true ), false );
// test string conversions
QgsProperty s1 = QgsProperty::fromValue( QVariant(), true );
collection.setProperty( 4, s1 );
QCOMPARE( s1.valueAsString( context, "n" ), QStringLiteral( "n" ) );
QCOMPARE( collection.valueAsString( 4, context, "y" ), QStringLiteral( "y" ) );
s1.setStaticValue( "s" );
collection.property( 4 ).setStaticValue( "s" );
QCOMPARE( s1.valueAsString( context, "n" ), QStringLiteral( "s" ) );
QCOMPARE( collection.valueAsString( 4, context, "y" ), QStringLiteral( "s" ) );
}
void TestQgsProperty::invalid()
{
QgsProperty p; //invalid property
QCOMPARE( p.propertyType(), QgsProperty::InvalidProperty );
QgsProperty p2( p );
QCOMPARE( p2.propertyType(), QgsProperty::InvalidProperty );
QgsProperty p3 = QgsProperty::fromValue( 5 );
p3 = p;
QCOMPARE( p3.propertyType(), QgsProperty::InvalidProperty );
}
void TestQgsProperty::staticProperty()
{
QgsExpressionContext context;
QgsProperty property = QgsProperty::fromValue( QStringLiteral( "test" ), true );
QCOMPARE( property.propertyType(), QgsProperty::StaticProperty );
QVERIFY( property.isActive() );
QVERIFY( property.referencedFields( context ).isEmpty() );
QCOMPARE( property.value( context, QStringLiteral( "default" ) ).toString(), QStringLiteral( "test" ) );
property.setActive( false );
QVERIFY( property.referencedFields( context ).isEmpty() );
QVERIFY( !property.isActive() );
QCOMPARE( property.value( context, QStringLiteral( "default" ) ).toString(), QStringLiteral( "default" ) );
property.setStaticValue( 5 );
property.setActive( true );
QCOMPARE( property.value( context ).toInt(), 5 );
//saving and restoring
//create a test dom element
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
QgsProperty p1;
p1.setActive( true );
p1.setStaticValue( "test" );
p1.setTransformer( new TestTransformer( 10, 20 ) );
QVariant element = p1.toVariant();
QgsProperty r1;
r1.loadVariant( element );
QVERIFY( r1.isActive() );
QVERIFY( r1.transformer() );
QCOMPARE( r1.staticValue(), QVariant( "test" ) );
p1.setActive( false );
element = p1.toVariant();
r1.loadVariant( element );
QVERIFY( !r1.isActive() );
//saving/restoring different types
p1.setStaticValue( QVariant( 5 ) ); //int
element = p1.toVariant();
r1.loadVariant( element );
QCOMPARE( r1.staticValue(), p1.staticValue() );
p1.setStaticValue( QVariant( 5.7 ) ); //double
element = p1.toVariant();
r1.loadVariant( element );
QCOMPARE( r1.staticValue(), p1.staticValue() );
p1.setStaticValue( QVariant( true ) ); //bool
element = p1.toVariant();
r1.loadVariant( element );
QCOMPARE( r1.staticValue(), p1.staticValue() );
p1.setStaticValue( QVariant( 5LL ) ); //longlong
element = p1.toVariant();
r1.loadVariant( element );
QCOMPARE( r1.staticValue(), p1.staticValue() );
// test copying a static property
QgsProperty p2;
p2.setActive( true );
p2.setStaticValue( "test" );
p2.setTransformer( new TestTransformer( 10, 20 ) );
// copy assign
QgsProperty p3;
p3 = p2;;
QVERIFY( p3.isActive() );
QCOMPARE( p3.staticValue().toString(), QStringLiteral( "test" ) );
QVERIFY( p3.transformer() );
p2.setActive( false );
p2.setStaticValue( 5.9 );
p3 = p2;
QVERIFY( !p3.isActive() );
QCOMPARE( p3.staticValue().toDouble(), 5.9 );
// copy constructor
QgsProperty p4( p2 );
QVERIFY( !p4.isActive() );
QCOMPARE( p4.staticValue().toDouble(), 5.9 );
QVERIFY( p4.transformer() );
}
void TestQgsProperty::fieldBasedProperty()
{
//make a feature
QgsFeature ft;
QgsFields fields;
fields.append( QgsField( "field1", QVariant::Int ) );
fields.append( QgsField( "field2", QVariant::Int ) );
ft.setFields( fields );
QgsAttributes attr;
attr << QVariant( 5 ) << QVariant( 7 );
ft.setAttributes( attr );
ft.setValid( true );
// throw it in an expression context
QgsExpressionContext context;
context.setFeature( ft );
context.setFields( fields );
QgsProperty property = QgsProperty::fromField( QStringLiteral( "field1" ), true );
QCOMPARE( property.propertyType(), QgsProperty::FieldBasedProperty );
QVERIFY( property.isActive() );
QCOMPARE( property.value( context, -1 ).toInt(), 5 );
QCOMPARE( property.referencedFields( context ), QSet< QString >() << "field1" );
property.setActive( false );
QVERIFY( !property.isActive() );
QCOMPARE( property.value( context, -1 ).toInt(), -1 );
QVERIFY( property.referencedFields( context ).isEmpty() );
property.setField( "field2" );
property.setActive( true );
QCOMPARE( property.value( context, -1 ).toInt(), 7 );
QCOMPARE( property.referencedFields( context ), QSet< QString >() << "field2" );
//bad field reference
property.setField( "bad_field" );
QCOMPARE( property.value( context, -1 ).toInt(), -1 );
// unset field name
QgsProperty defaultProperty = QgsProperty::fromField( QString() );
QCOMPARE( defaultProperty.value( context, -1 ).toInt(), -1 );
QVERIFY( defaultProperty.referencedFields( context ).isEmpty() );
defaultProperty.setActive( true );
QCOMPARE( defaultProperty.value( context, -1 ).toInt(), -1 );
QVERIFY( defaultProperty.referencedFields( context ).isEmpty() );
//test preparation
QgsProperty property3 = QgsProperty::fromField( QStringLiteral( "field1" ), true );
QVERIFY( property3.prepare( context ) );
QCOMPARE( property3.value( context, -1 ).toInt(), 5 );
//saving and restoring
//create a test dom element
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
QgsProperty p1;
p1.setActive( true );
p1.setField( "test_field" );
QVariant element;
QgsProperty r1;
//try reading from an empty element
r1.loadVariant( element );
QVERIFY( !r1.isActive() );
QVERIFY( r1.field().isEmpty() );
// now populate element and re-read
element = p1.toVariant();
r1.loadVariant( element );
QVERIFY( r1.isActive() );
QCOMPARE( r1.field(), QStringLiteral( "test_field" ) );
p1.setActive( false );
element = p1.toVariant();
r1.loadVariant( element );
QVERIFY( !r1.isActive() );
// test copying a field based property
QgsProperty p2;
p2.setActive( true );
p2.setField( "test" );
p2.setTransformer( new TestTransformer( 10, 20 ) );
// copy constructor
QgsProperty p3( p2 );
QVERIFY( p3.isActive() );
QCOMPARE( p3.field(), QStringLiteral( "test" ) );
QVERIFY( p3.transformer() );
p2.setActive( false );
// assignment operator
QgsProperty p4;
p4 = p2;
QVERIFY( !p4.isActive() );
QCOMPARE( p4.field(), QStringLiteral( "test" ) );
QVERIFY( p4.transformer() );
}
void TestQgsProperty::expressionBasedProperty()
{
//make a feature
QgsFeature ft;
QgsFields fields;
fields.append( QgsField( "field1", QVariant::Int ) );
fields.append( QgsField( "field2", QVariant::Int ) );
ft.setFields( fields );
QgsAttributes attr;
attr << QVariant( 5 ) << QVariant( 7 );
ft.setAttributes( attr );
ft.setValid( true );
// throw it in an expression context
QgsExpressionContext context;
context.setFeature( ft );
context.setFields( fields );
QgsProperty property = QgsProperty::fromExpression( QStringLiteral( "\"field1\" + \"field2\"" ), true );
QCOMPARE( property.propertyType(), QgsProperty::ExpressionBasedProperty );
QVERIFY( property.isActive() );
QCOMPARE( property.value( context, -1 ).toInt(), 12 );
QCOMPARE( property.referencedFields( context ).count(), 2 );
QVERIFY( property.referencedFields( context ).contains( "field1" ) );
QVERIFY( property.referencedFields( context ).contains( "field2" ) );
property.setExpressionString( "\"field2\"*2" );
QCOMPARE( property.value( context, -1 ).toInt(), 14 );
QCOMPARE( property.referencedFields( context ), QSet< QString >() << "field2" );
property.setActive( false );
QVERIFY( !property.isActive() );
QCOMPARE( property.value( context, -1 ).toInt(), -1 );
QVERIFY( property.referencedFields( context ).isEmpty() );
property.setExpressionString( "'a'||'b'" );
property.setActive( true );
QVERIFY( property.referencedFields( context ).isEmpty() );
QCOMPARE( property.value( context, "bb" ).toString(), QStringLiteral( "ab" ) );
//bad expression
property.setExpressionString( "bad_ 5" );
QCOMPARE( property.value( context, -1 ).toInt(), -1 );
QVERIFY( property.referencedFields( context ).isEmpty() );
// unset expression
QgsProperty defaultProperty = QgsProperty::fromExpression( QString() );
QCOMPARE( defaultProperty.value( context, -1 ).toInt(), -1 );
QVERIFY( defaultProperty.referencedFields( context ).isEmpty() );
defaultProperty.setActive( true );
QCOMPARE( defaultProperty.value( context, -1 ).toInt(), -1 );
QVERIFY( defaultProperty.referencedFields( context ).isEmpty() );
//preparation
QgsProperty property3 = QgsProperty::fromExpression( QStringLiteral( "\"field1\" + \"field2\"" ), true );
QVERIFY( property3.prepare( context ) );
QCOMPARE( property3.value( context, -1 ).toInt(), 12 );
QgsProperty property4 = QgsProperty::fromExpression( QStringLiteral( "\"field1\" + " ), true );
QVERIFY( !property4.prepare( context ) );
//saving and restoring
//create a test dom element
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
QgsProperty p1;
p1.setActive( true );
p1.setExpressionString( "4+5" );
QVariant element;
QgsProperty r1;
//try reading from an empty element
r1.loadVariant( element );
QVERIFY( !r1.isActive() );
QVERIFY( r1.expressionString().isEmpty() );
QCOMPARE( r1.value( context, -1 ).toInt(), -1 );
// now populate element and re-read
element = p1.toVariant();
r1.loadVariant( element );
QVERIFY( r1.isActive() );
QCOMPARE( r1.expressionString(), QStringLiteral( "4+5" ) );
QCOMPARE( r1.value( context, -1 ).toInt(), 9 );
p1.setActive( false );
element = p1.toVariant();
r1.loadVariant( element );
QVERIFY( !r1.isActive() );
QCOMPARE( r1.value( context, -1 ).toInt(), -1 );
// test copying an expression based property
QgsProperty p2;
p2.setActive( true );
p2.setExpressionString( "1+6" );
// copy constructor
QgsProperty p3( p2 );
QVERIFY( p3.isActive() );
QCOMPARE( p3.expressionString(), QStringLiteral( "1+6" ) );
QCOMPARE( p3.value( context, -1 ).toInt(), 7 );
// assignment operator
QgsProperty p4;
p2.setActive( false );
p4 = p2;
QVERIFY( !p4.isActive() );
QCOMPARE( p4.value( context, -1 ).toInt(), -1 );
p2.setTransformer( new TestTransformer( 10, 20 ) );
p4 = p2;
QVERIFY( p4.transformer() );
}
void TestQgsProperty::equality()
{
QgsProperty dd1;
dd1.setActive( true );
dd1.setField( QStringLiteral( "field" ) );
QgsProperty dd2;
dd2.setActive( true );
dd2.setField( QStringLiteral( "field" ) );
QVERIFY( dd1 == dd2 );
QVERIFY( !( dd1 != dd2 ) );
dd1.setExpressionString( QStringLiteral( "expression" ) );
dd2.setExpressionString( QStringLiteral( "expression" ) );
QVERIFY( dd1 == dd2 );
QVERIFY( !( dd1 != dd2 ) );
//test that all applicable components contribute to equality
dd2.setActive( false );
QVERIFY( !( dd1 == dd2 ) );
QVERIFY( dd1 != dd2 );
dd2.setActive( true );
dd2.setExpressionString( QStringLiteral( "a" ) );
QVERIFY( !( dd1 == dd2 ) );
QVERIFY( dd1 != dd2 );
dd2.setField( QStringLiteral( "field" ) );
QVERIFY( !( dd1 == dd2 ) );
QVERIFY( dd1 != dd2 );
dd1.setField( QStringLiteral( "fieldb" ) );
QVERIFY( !( dd1 == dd2 ) );
QVERIFY( dd1 != dd2 );
dd1.setField( QStringLiteral( "field" ) );
QVERIFY( dd1 == dd2 );
QVERIFY( !( dd1 != dd2 ) );
// with transformer
dd1.setTransformer( new QgsGenericNumericTransformer( 1, 2, 3, 4 ) );
QVERIFY( !( dd1 == dd2 ) );
QVERIFY( dd1 != dd2 );
dd2.setTransformer( new QgsGenericNumericTransformer( 1, 2, 3, 4 ) );
QVERIFY( dd1 == dd2 );
QVERIFY( !( dd1 != dd2 ) );
}
void TestQgsProperty::propertyTransformer()
{
QgsExpressionContext context;
TestTransformer transform( -5, 5 );
QCOMPARE( transform.minValue(), -5.0 );
transform.setMinValue( -1 );
QCOMPARE( transform.minValue(), -1.0 );
QCOMPARE( transform.maxValue(), 5.0 );
transform.setMaxValue( 10.0 );
QCOMPARE( transform.maxValue(), 10.0 );
//saving and restoring
//create a test dom element
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
TestTransformer t1( -5, 6 );
QVariant element;
TestTransformer r1( -99, -98 );
element = t1.toVariant();
QVERIFY( r1.loadVariant( element ) );
QCOMPARE( r1.minValue(), -5.0 );
QCOMPARE( r1.maxValue(), 6.0 );
//install into property and test evaluation
QgsProperty p1;
p1.setTransformer( new TestTransformer( 10, 20 ) );
QVERIFY( dynamic_cast< const TestTransformer * >( p1.transformer() ) );
QCOMPARE( static_cast< const TestTransformer * >( p1.transformer() )->minValue(), 10.0 );
QCOMPARE( static_cast< const TestTransformer * >( p1.transformer() )->maxValue(), 20.0 );
p1.setStaticValue( QVariant( QVariant::Double ) );
QCOMPARE( p1.value( context, -99 ).toDouble(), -1.0 );
p1.setStaticValue( 11.0 );
QCOMPARE( p1.value( context, -99 ).toDouble(), 22.0 );
//test that transform is saved/restored with property
QVariant propElement;
QgsProperty p2;
QVERIFY( !p2.transformer() );
propElement = p1.toVariant();
p2.loadVariant( propElement );
QVERIFY( p2.transformer() );
QCOMPARE( p2.transformer()->minValue(), 10.0 );
QCOMPARE( p2.transformer()->maxValue(), 20.0 );
//test that copy constructor copies transformer
QgsProperty p4( p1 );
QVERIFY( p4.transformer() );
QCOMPARE( p4.transformer()->minValue(), 10.0 );
QCOMPARE( p4.transformer()->maxValue(), 20.0 );
//test that assignment operator copies transformer
QgsProperty p5;
p5 = p1;
QVERIFY( p5.transformer() );
QCOMPARE( p5.transformer()->minValue(), 10.0 );
QCOMPARE( p5.transformer()->maxValue(), 20.0 );
}
void TestQgsProperty::propertyTransformerFromExpression()
{
QString baseExpression;
QString fieldName;
// not convertible to a transformer
std::unique_ptr< QgsPropertyTransformer > exp( QgsPropertyTransformer::fromExpression( QStringLiteral( "1 * 2" ), baseExpression, fieldName ) );
QVERIFY( !exp.get() );
QVERIFY( baseExpression.isEmpty() );
QVERIFY( fieldName.isEmpty() );
// convertible to a size scale transformer
exp.reset( QgsPropertyTransformer::fromExpression( QStringLiteral( "coalesce(scale_linear(column, 1, 7, 2, 10), 0)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->transformerType(), QgsPropertyTransformer::SizeScaleTransformer );
QCOMPARE( fieldName, QStringLiteral( "column" ) );
QVERIFY( baseExpression.isEmpty() );
exp.reset( QgsPropertyTransformer::fromExpression( QStringLiteral( "coalesce(scale_linear(column * 2, 1, 7, 2, 10), 0)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->transformerType(), QgsPropertyTransformer::SizeScaleTransformer );
QCOMPARE( baseExpression, QStringLiteral( "column * 2" ) );
QVERIFY( fieldName.isEmpty() );
}
void TestQgsProperty::genericNumericTransformer()
{
QgsExpressionContext context;
QgsGenericNumericTransformer t1( 10,
20,
100,
200,
-10,
1.0 );
QCOMPARE( t1.transformerType(), QgsPropertyTransformer::GenericNumericTransformer );
QCOMPARE( t1.minValue(), 10.0 );
QCOMPARE( t1.maxValue(), 20.0 );
QCOMPARE( t1.minOutputValue(), 100.0 );
QCOMPARE( t1.maxOutputValue(), 200.0 );
QCOMPARE( t1.nullOutputValue(), -10.0 );
QCOMPARE( t1.exponent(), 1.0 );
//transform
QCOMPARE( t1.transform( context, 10 ).toInt(), 100 );
QCOMPARE( t1.transform( context, 20 ).toInt(), 200 );
//null value
QCOMPARE( t1.transform( context, QVariant( QVariant::Double ) ).toInt(), -10 );
//non numeric value
QCOMPARE( t1.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
// add a curve
QVERIFY( !t1.curveTransform() );
t1.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) ) );
QVERIFY( t1.curveTransform() );
QCOMPARE( t1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
QCOMPARE( t1.transform( context, 10 ).toInt(), 180 );
QCOMPARE( t1.transform( context, 20 ).toInt(), 120 );
// copy
QgsGenericNumericTransformer s1( t1 );
QVERIFY( s1.curveTransform() );
QCOMPARE( s1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
// assignment
QgsGenericNumericTransformer s2;
s2 = t1;
QVERIFY( s2.curveTransform() );
QCOMPARE( s2.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
//saving and restoring
//create a test dom element
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
QgsGenericNumericTransformer t2( 15,
25,
150,
250,
-10,
99 );
t2.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) ) );
QVariant element;
element = t2.toVariant();
QgsGenericNumericTransformer r1;
QVERIFY( r1.loadVariant( element ) );
QCOMPARE( r1.minValue(), 15.0 );
QCOMPARE( r1.maxValue(), 25.0 );
QCOMPARE( r1.minOutputValue(), 150.0 );
QCOMPARE( r1.maxOutputValue(), 250.0 );
QCOMPARE( r1.nullOutputValue(), -10.0 );
QCOMPARE( r1.exponent(), 99.0 );
QVERIFY( r1.curveTransform() );
QCOMPARE( r1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
// test cloning
std::unique_ptr< QgsGenericNumericTransformer > r2( t2.clone() );
QCOMPARE( r2->minValue(), 15.0 );
QCOMPARE( r2->maxValue(), 25.0 );
QCOMPARE( r2->minOutputValue(), 150.0 );
QCOMPARE( r2->maxOutputValue(), 250.0 );
QCOMPARE( r2->nullOutputValue(), -10.0 );
QCOMPARE( r2->exponent(), 99.0 );
QVERIFY( r2->curveTransform() );
QCOMPARE( r2->curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
//test various min/max value/size and scaling methods
//getters and setters
QgsGenericNumericTransformer t;
t.setMinValue( 100 );
QCOMPARE( t.minValue(), 100.0 );
t.setMaxValue( 200 );
QCOMPARE( t.maxValue(), 200.0 );
t.setMinOutputValue( 10.0 );
QCOMPARE( t.minOutputValue(), 10.0 );
t.setMaxOutputValue( 20.0 );
QCOMPARE( t.maxOutputValue(), 20.0 );
t.setNullOutputValue( 1 );
QCOMPARE( t.nullOutputValue(), 1.0 );
t.setExponent( 2.5 );
QCOMPARE( t.exponent(), 2.5 );
//test linear scaling
t.setExponent( 1.0 );
QCOMPARE( t.value( 100 ), 10.0 );
QCOMPARE( t.value( 150 ), 15.0 );
QCOMPARE( t.value( 200 ), 20.0 );
//test exponential scaling
t.setExponent( 1.5 );
QCOMPARE( t.value( 100 ), 10.0 );
QGSCOMPARENEAR( t.value( 150 ), 13.5355, 0.001 );
QCOMPARE( t.value( 200 ), 20.0 );
//as expression
QgsGenericNumericTransformer t3( 15,
25,
150,
250,
-10,
1.0 );
QCOMPARE( t3.toExpression( "5+6" ), QStringLiteral( "coalesce(scale_linear(5+6, 15, 25, 150, 250), -10)" ) );
t3.setExponent( 1.6 );
QCOMPARE( t3.toExpression( "5+6" ), QStringLiteral( "coalesce(scale_exp(5+6, 15, 25, 150, 250, 1.6), -10)" ) );
// test size scale transformer inside property
QgsProperty p;
p.setTransformer( new QgsGenericNumericTransformer( 15,
25,
150,
250,
-10,
99 ) );
p.setStaticValue( QVariant() );
bool ok = false;
QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
QVERIFY( ok );
p.setExpressionString( QStringLiteral( "NULL" ) );
QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
QVERIFY( ok );
p.setExpressionString( QStringLiteral( "no field" ) );
QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
QVERIFY( ok );
}
void TestQgsProperty::genericNumericTransformerFromExpression()
{
QString baseExpression;
QString fieldName;
std::unique_ptr< QgsGenericNumericTransformer > exp( QgsGenericNumericTransformer::fromExpression( QStringLiteral( "coalesce(scale_linear(column, 1, 7, 2, 10), 0)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( fieldName, QStringLiteral( "column" ) );
QVERIFY( baseExpression.isEmpty() );
QCOMPARE( exp->minValue(), 1. );
QCOMPARE( exp->maxValue(), 7. );
QCOMPARE( exp->minOutputValue(), 2. );
QCOMPARE( exp->maxOutputValue(), 10. );
QCOMPARE( exp->nullOutputValue(), 0.0 );
exp.reset( QgsGenericNumericTransformer::fromExpression( QStringLiteral( "scale_linear(column, 1, 7, 2, 10)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( fieldName, QStringLiteral( "column" ) );
QVERIFY( baseExpression.isEmpty() );
QCOMPARE( exp->minValue(), 1. );
QCOMPARE( exp->maxValue(), 7. );
QCOMPARE( exp->minOutputValue(), 2. );
QCOMPARE( exp->maxOutputValue(), 10. );
exp.reset( QgsGenericNumericTransformer::fromExpression( QStringLiteral( "scale_linear(column * 2, 1, 7, 2, 10)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( baseExpression, QStringLiteral( "column * 2" ) );
QVERIFY( fieldName.isEmpty() );
QCOMPARE( exp->minValue(), 1. );
QCOMPARE( exp->maxValue(), 7. );
QCOMPARE( exp->minOutputValue(), 2. );
QCOMPARE( exp->maxOutputValue(), 10. );
exp.reset( QgsGenericNumericTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, 2, 10, 0.51), 1)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->minValue(), 1. );
QCOMPARE( exp->maxValue(), 7. );
QCOMPARE( exp->minOutputValue(), 2. );
QCOMPARE( exp->maxOutputValue(), 10. );
QCOMPARE( exp->exponent(), 0.51 );
QCOMPARE( exp->nullOutputValue(), 1.0 );
QVERIFY( !QgsGenericNumericTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, a, 10, 0.5), 0)" ), baseExpression, fieldName ) );
QVERIFY( !QgsGenericNumericTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7), 0)" ), baseExpression, fieldName ) );
QVERIFY( !QgsGenericNumericTransformer::fromExpression( QStringLiteral( "1+2" ), baseExpression, fieldName ) );
QVERIFY( !QgsGenericNumericTransformer::fromExpression( QStringLiteral( "" ), baseExpression, fieldName ) );
}
void TestQgsProperty::sizeScaleTransformer()
{
QgsExpressionContext context;
QgsSizeScaleTransformer scale( QgsSizeScaleTransformer::Linear,
10,
20,
100,
200,
-10,
1.0 );
QCOMPARE( scale.transformerType(), QgsPropertyTransformer::SizeScaleTransformer );
QCOMPARE( scale.minValue(), 10.0 );
QCOMPARE( scale.maxValue(), 20.0 );
QCOMPARE( scale.minSize(), 100.0 );
QCOMPARE( scale.maxSize(), 200.0 );
QCOMPARE( scale.nullSize(), -10.0 );
QCOMPARE( scale.exponent(), 1.0 );
QCOMPARE( scale.type(), QgsSizeScaleTransformer::Linear );
//transform
QCOMPARE( scale.transform( context, 10 ).toInt(), 100 );
QCOMPARE( scale.transform( context, 20 ).toInt(), 200 );
//null value
QCOMPARE( scale.transform( context, QVariant( QVariant::Double ) ).toInt(), -10 );
//non numeric value
QCOMPARE( scale.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
// add a curve
QVERIFY( !scale.curveTransform() );
scale.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) ) );
QVERIFY( scale.curveTransform() );
QCOMPARE( scale.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
QCOMPARE( scale.transform( context, 10 ).toInt(), 120 );
QCOMPARE( scale.transform( context, 20 ).toInt(), 180 );
// copy
QgsSizeScaleTransformer s1( scale );
QVERIFY( s1.curveTransform() );
QCOMPARE( s1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
// assignment
QgsSizeScaleTransformer s2;
s2 = scale;
QVERIFY( s2.curveTransform() );
QCOMPARE( s2.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
//saving and restoring
//create a test dom element
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
QgsSizeScaleTransformer t1( QgsSizeScaleTransformer::Exponential,
15,
25,
150,
250,
-10,
99 );
t1.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) ) );
QVariant element;
element = t1.toVariant();
QgsSizeScaleTransformer r1;
QVERIFY( r1.loadVariant( element ) );
QCOMPARE( r1.minValue(), 15.0 );
QCOMPARE( r1.maxValue(), 25.0 );
QCOMPARE( r1.minSize(), 150.0 );
QCOMPARE( r1.maxSize(), 250.0 );
QCOMPARE( r1.nullSize(), -10.0 );
QCOMPARE( r1.exponent(), 99.0 );
QCOMPARE( r1.type(), QgsSizeScaleTransformer::Exponential );
QVERIFY( r1.curveTransform() );
QCOMPARE( r1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
// test cloning
std::unique_ptr< QgsSizeScaleTransformer > r2( t1.clone() );
QCOMPARE( r2->minValue(), 15.0 );
QCOMPARE( r2->maxValue(), 25.0 );
QCOMPARE( r2->minSize(), 150.0 );
QCOMPARE( r2->maxSize(), 250.0 );
QCOMPARE( r2->nullSize(), -10.0 );
QCOMPARE( r2->exponent(), 99.0 );
QCOMPARE( r2->type(), QgsSizeScaleTransformer::Exponential );
QVERIFY( r2->curveTransform() );
QCOMPARE( r2->curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
//test various min/max value/size and scaling methods
//getters and setters
QgsSizeScaleTransformer t;
t.setMinValue( 100 );
QCOMPARE( t.minValue(), 100.0 );
t.setMaxValue( 200 );
QCOMPARE( t.maxValue(), 200.0 );
t.setMinSize( 10.0 );
QCOMPARE( t.minSize(), 10.0 );
t.setMaxSize( 20.0 );
QCOMPARE( t.maxSize(), 20.0 );
t.setNullSize( 1 );
QCOMPARE( t.nullSize(), 1.0 );
t.setType( QgsSizeScaleTransformer::Area );
QCOMPARE( t.type(), QgsSizeScaleTransformer::Area );
t.setExponent( 2.5 );
QCOMPARE( t.exponent(), 2.5 );
//test that setting type updates exponent
t.setType( QgsSizeScaleTransformer::Linear );
QCOMPARE( t.exponent(), 1.0 );
t.setType( QgsSizeScaleTransformer::Area );
QCOMPARE( t.exponent(), 0.5 );
t.setType( QgsSizeScaleTransformer::Flannery );
QCOMPARE( t.exponent(), 0.57 );
//test linear scaling
t.setType( QgsSizeScaleTransformer::Linear );
QCOMPARE( t.size( 100 ), 10.0 );
QCOMPARE( t.size( 150 ), 15.0 );
QCOMPARE( t.size( 200 ), 20.0 );
//test area scaling
t.setType( QgsSizeScaleTransformer::Area );
QCOMPARE( t.size( 100 ), 10.0 );
QGSCOMPARENEAR( t.size( 150 ), 17.0711, 0.001 );
QCOMPARE( t.size( 200 ), 20.0 );
//test flannery scaling
t.setType( QgsSizeScaleTransformer::Flannery );
QCOMPARE( t.size( 100 ), 10.0 );
QGSCOMPARENEAR( t.size( 150 ), 16.7362, 0.001 );
QCOMPARE( t.size( 200 ), 20.0 );
//test exponential scaling
t.setType( QgsSizeScaleTransformer::Exponential );
t.setExponent( 1.5 );
QCOMPARE( t.size( 100 ), 10.0 );
QGSCOMPARENEAR( t.size( 150 ), 13.5355, 0.001 );
QCOMPARE( t.size( 200 ), 20.0 );
//as expression
QgsSizeScaleTransformer t2( QgsSizeScaleTransformer::Linear,
15,
25,
150,
250,
-10,
1.6 );
QCOMPARE( t2.toExpression( "5+6" ), QStringLiteral( "coalesce(scale_linear(5+6, 15, 25, 150, 250), -10)" ) );
t2.setType( QgsSizeScaleTransformer::Exponential );
t2.setExponent( 1.6 );
QCOMPARE( t2.toExpression( "5+6" ), QStringLiteral( "coalesce(scale_exp(5+6, 15, 25, 150, 250, 1.6), -10)" ) );
// test size scale transformer inside property
QgsProperty p;
p.setTransformer( new QgsSizeScaleTransformer( QgsSizeScaleTransformer::Exponential,
15,
25,
150,
250,
-10,
99 ) );
p.setStaticValue( QVariant() );
bool ok = false;
QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
QVERIFY( ok );
p.setExpressionString( QStringLiteral( "NULL" ) );
QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
QVERIFY( ok );
p.setExpressionString( QStringLiteral( "no field" ) );
QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
QVERIFY( ok );
}
void TestQgsProperty::sizeScaleTransformerFromExpression()
{
QString baseExpression;
QString fieldName;
std::unique_ptr< QgsSizeScaleTransformer > exp( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_linear(column, 1, 7, 2, 10), 0)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->type(), QgsSizeScaleTransformer::Linear );
QCOMPARE( fieldName, QStringLiteral( "column" ) );
QVERIFY( baseExpression.isEmpty() );
QCOMPARE( exp->minValue(), 1. );
QCOMPARE( exp->maxValue(), 7. );
QCOMPARE( exp->minSize(), 2. );
QCOMPARE( exp->maxSize(), 10. );
QCOMPARE( exp->nullSize(), 0.0 );
exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, 2, 10, 0.5), 0)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->type(), QgsSizeScaleTransformer::Area );
exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, 2, 10, 0.57), 0)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->type(), QgsSizeScaleTransformer::Flannery );
exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "scale_linear(column, 1, 7, 2, 10)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->type(), QgsSizeScaleTransformer::Linear );
QCOMPARE( fieldName, QStringLiteral( "column" ) );
QVERIFY( baseExpression.isEmpty() );
QCOMPARE( exp->minValue(), 1. );
QCOMPARE( exp->maxValue(), 7. );
QCOMPARE( exp->minSize(), 2. );
QCOMPARE( exp->maxSize(), 10. );
exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "scale_linear(column * 2, 1, 7, 2, 10)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->type(), QgsSizeScaleTransformer::Linear );
QCOMPARE( baseExpression, QStringLiteral( "column * 2" ) );
QVERIFY( fieldName.isEmpty() );
QCOMPARE( exp->minValue(), 1. );
QCOMPARE( exp->maxValue(), 7. );
QCOMPARE( exp->minSize(), 2. );
QCOMPARE( exp->maxSize(), 10. );
exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "scale_exp(column, 1, 7, 2, 10, 0.5)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->type(), QgsSizeScaleTransformer::Area );
exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "scale_exp(column, 1, 7, 2, 10, 0.57)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->type(), QgsSizeScaleTransformer::Flannery );
exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, 2, 10, 0.51), 22)" ), baseExpression, fieldName ) );
QVERIFY( exp.get() );
QCOMPARE( exp->type(), QgsSizeScaleTransformer::Exponential );
QCOMPARE( exp->nullSize(), 22.0 );
QVERIFY( !QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, a, 10, 0.5), 0)" ), baseExpression, fieldName ) );
QVERIFY( !QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7), 0)" ), baseExpression, fieldName ) );
QVERIFY( !QgsSizeScaleTransformer::fromExpression( QStringLiteral( "1+2" ), baseExpression, fieldName ) );
QVERIFY( !QgsSizeScaleTransformer::fromExpression( QStringLiteral( "" ), baseExpression, fieldName ) );
}
void TestQgsProperty::colorRampTransformer()
{
QgsExpressionContext context;
QgsColorRampTransformer scale( 10,
20,
new QgsGradientColorRamp( QColor( 0, 0, 0 ), QColor( 255, 255, 255 ) ),
QColor( 100, 150, 200 ) );
QCOMPARE( scale.transformerType(), QgsPropertyTransformer::ColorRampTransformer );
QCOMPARE( scale.minValue(), 10.0 );
QCOMPARE( scale.maxValue(), 20.0 );
QVERIFY( scale.colorRamp() );
QCOMPARE( scale.nullColor(), QColor( 100, 150, 200 ) );
//transform
QCOMPARE( scale.transform( context, 10 ).value<QColor>(), QColor( 0, 0, 0 ) );
QCOMPARE( scale.transform( context, 20 ).value<QColor>(), QColor( 255, 255, 255 ) );
//null value
QCOMPARE( scale.transform( context, QVariant( QVariant::Double ) ).value<QColor>(), QColor( 100, 150, 200 ) );
//non numeric value
QCOMPARE( scale.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
// add a curve
QVERIFY( !scale.curveTransform() );
scale.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) ) );
QVERIFY( scale.curveTransform() );
QCOMPARE( scale.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
QCOMPARE( scale.transform( context, 10 ).value<QColor>().name(), QString( "#333333" ) );
QCOMPARE( scale.transform( context, 20 ).value<QColor>().name(), QString( "#cccccc" ) );
// copy
QgsColorRampTransformer s1( scale );
QVERIFY( s1.curveTransform() );
QCOMPARE( s1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
// assignment
QgsColorRampTransformer s2;
s2 = scale;
QVERIFY( s2.curveTransform() );
QCOMPARE( s2.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
//saving and restoring
//create a test dom element
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
QgsColorRampTransformer t1( 15,
25,
new QgsGradientColorRamp( QColor( 10, 20, 30 ), QColor( 200, 190, 180 ) ),
QColor( 100, 150, 200 ) );
t1.setRampName( "rampname " );
t1.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) ) );
QVariant element;
element = t1.toVariant();
QgsColorRampTransformer r1;
QVERIFY( r1.loadVariant( element ) );
QCOMPARE( r1.minValue(), 15.0 );
QCOMPARE( r1.maxValue(), 25.0 );
QCOMPARE( r1.nullColor(), QColor( 100, 150, 200 ) );
QCOMPARE( r1.rampName(), QStringLiteral( "rampname " ) );
QVERIFY( dynamic_cast< QgsGradientColorRamp * >( r1.colorRamp() ) );
QCOMPARE( static_cast< QgsGradientColorRamp * >( r1.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
QCOMPARE( static_cast< QgsGradientColorRamp * >( r1.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
QVERIFY( r1.curveTransform() );
QCOMPARE( r1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
// test cloning
std::unique_ptr< QgsColorRampTransformer > r2( t1.clone() );
QCOMPARE( r2->minValue(), 15.0 );
QCOMPARE( r2->maxValue(), 25.0 );
QCOMPARE( r2->nullColor(), QColor( 100, 150, 200 ) );
QCOMPARE( r2->rampName(), QStringLiteral( "rampname " ) );
QCOMPARE( static_cast< QgsGradientColorRamp * >( r2->colorRamp() )->color1(), QColor( 10, 20, 30 ) );
QCOMPARE( static_cast< QgsGradientColorRamp * >( r2->colorRamp() )->color2(), QColor( 200, 190, 180 ) );
QVERIFY( r2->curveTransform() );
QCOMPARE( r2->curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
// copy constructor
QgsColorRampTransformer r3( t1 );
QCOMPARE( r3.minValue(), 15.0 );
QCOMPARE( r3.maxValue(), 25.0 );
QCOMPARE( r3.nullColor(), QColor( 100, 150, 200 ) );
QCOMPARE( r3.rampName(), QStringLiteral( "rampname " ) );
QCOMPARE( static_cast< QgsGradientColorRamp * >( r3.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
QCOMPARE( static_cast< QgsGradientColorRamp * >( r3.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
QVERIFY( r3.curveTransform() );
QCOMPARE( r3.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
// assignment operator
QgsColorRampTransformer r4;
r4 = t1;
QCOMPARE( r4.minValue(), 15.0 );
QCOMPARE( r4.maxValue(), 25.0 );
QCOMPARE( r4.nullColor(), QColor( 100, 150, 200 ) );
QCOMPARE( r4.rampName(), QStringLiteral( "rampname " ) );
QCOMPARE( static_cast< QgsGradientColorRamp * >( r4.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
QCOMPARE( static_cast< QgsGradientColorRamp * >( r4.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
QVERIFY( r4.curveTransform() );
QCOMPARE( r4.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
//test various min/max value/color and scaling methods
//getters and setters
QgsColorRampTransformer t;
t.setMinValue( 100 );
QCOMPARE( t.minValue(), 100.0 );
t.setMaxValue( 200 );
QCOMPARE( t.maxValue(), 200.0 );
t.setNullColor( QColor( 1, 10, 11, 21 ) );
QCOMPARE( t.nullColor(), QColor( 1, 10, 11, 21 ) );
t.setColorRamp( new QgsGradientColorRamp( QColor( 10, 20, 100 ), QColor( 100, 200, 200 ) ) );
QCOMPARE( static_cast< QgsGradientColorRamp * >( t.colorRamp() )->color1(), QColor( 10, 20, 100 ) );
t.setRampName( "colorramp" );
QCOMPARE( t.rampName(), QStringLiteral( "colorramp" ) );
//test colors
QCOMPARE( t.color( 50 ), QColor( 10, 20, 100 ) ); //out of range
QCOMPARE( t.color( 100 ), QColor( 10, 20, 100 ) );
QCOMPARE( t.color( 150 ), QColor( 55, 110, 150 ) );
QCOMPARE( t.color( 200 ), QColor( 100, 200, 200 ) );
QCOMPARE( t.color( 250 ), QColor( 100, 200, 200 ) ); //out of range
//toExpression
QgsColorRampTransformer t5( 15,
25,
new QgsGradientColorRamp( QColor( 10, 20, 30 ), QColor( 200, 190, 180 ) ),
QColor( 100, 150, 200 ) );
QCOMPARE( t5.toExpression( "5+6" ), QStringLiteral( "coalesce(ramp_color('custom ramp',scale_linear(5+6, 15, 25, 0, 1), '#6496c8')" ) );
t5.setRampName( "my ramp" );
QCOMPARE( t5.toExpression( "5+6" ), QStringLiteral( "coalesce(ramp_color('my ramp',scale_linear(5+6, 15, 25, 0, 1), '#6496c8')" ) );
}
void TestQgsProperty::propertyToTransformer()
{
// not convertible to a transformer:
// fields cannot be converted
QgsProperty p = QgsProperty::fromField( "a field" );
QVERIFY( !p.convertToTransformer() );
QVERIFY( !p.transformer() );
QCOMPARE( p.field(), QStringLiteral( "a field" ) );
// static values cannot be converted
p = QgsProperty::fromValue( 5 );
QVERIFY( !p.convertToTransformer() );
QVERIFY( !p.transformer() );
QCOMPARE( p.staticValue(), QVariant( 5 ) );
// bad expression which cannot be converted
p = QgsProperty::fromExpression( "5*5" );
QVERIFY( !p.convertToTransformer() );
QVERIFY( !p.transformer() );
QCOMPARE( p.expressionString(), QStringLiteral( "5*5" ) );
// expression which can be converted to size scale transformer with base expression
p = QgsProperty::fromExpression( QStringLiteral( "coalesce(scale_linear(column * 2, 1, 7, 2, 10), 0)" ) );
QVERIFY( p.convertToTransformer() );
QVERIFY( p.transformer() );
QCOMPARE( p.expressionString(), QStringLiteral( "column * 2" ) );
// expression which can be converted to a size scale transformer with base column ref
p = QgsProperty::fromExpression( QStringLiteral( "coalesce(scale_linear(column, 1, 7, 2, 10), 0)" ) );
QVERIFY( p.convertToTransformer() );
QVERIFY( p.transformer() );
QCOMPARE( p.field(), QStringLiteral( "column" ) );
}
void TestQgsProperty::asExpression()
{
// static property
QgsProperty p = QgsProperty::fromValue( 5 );
QCOMPARE( p.asExpression(), QStringLiteral( "5" ) );
p = QgsProperty::fromValue( "value" );
QCOMPARE( p.asExpression(), QStringLiteral( "'value'" ) );
// field based property
p = QgsProperty::fromField( "a field" );
QCOMPARE( p.asExpression(), QStringLiteral( "\"a field\"" ) );
// expression based property
p = QgsProperty::fromExpression( "5 + 6" );
QCOMPARE( p.asExpression(), QStringLiteral( "5 + 6" ) );
// with transformer
p.setTransformer( new QgsSizeScaleTransformer( QgsSizeScaleTransformer::Linear,
15,
25,
150,
250,
-10,
1 ) );
QCOMPARE( p.asExpression(), QStringLiteral( "coalesce(scale_linear(5 + 6, 15, 25, 150, 250), -10)" ) );
}
void TestQgsProperty::propertyCollection()
{
//make a feature
QgsFeature ft;
QgsFields fields;
fields.append( QgsField( "field1", QVariant::Int ) );
fields.append( QgsField( "field2", QVariant::Int ) );
ft.setFields( fields );
QgsAttributes attr;
attr << QVariant( 5 ) << QVariant( 7 );
ft.setAttributes( attr );
ft.setValid( true );
// throw it in an expression context
QgsExpressionContext context;
context.setFeature( ft );
context.setFields( fields );
QgsPropertyCollection collection( "collection" );
QCOMPARE( collection.name(), QStringLiteral( "collection" ) );
QVERIFY( !collection.hasProperty( Property1 ) );
QVERIFY( collection.referencedFields( context ).isEmpty() );
QCOMPARE( collection.count(), 0 );
QCOMPARE( collection.propertyKeys(), QSet< int >() );
QVERIFY( !collection.hasDynamicProperties() );
QVERIFY( !collection.hasActiveProperties() );
QgsProperty property = QgsProperty::fromValue( "value", true );
collection.setProperty( Property1, property );
QVERIFY( collection.hasProperty( Property1 ) );
QCOMPARE( collection.count(), 1 );
QCOMPARE( collection.propertyKeys(), QSet< int >() << Property1 );
QCOMPARE( collection.property( Property1 ).value( context ), property.value( context ) );
QCOMPARE( collection.value( Property1, context ), property.value( context ) );
QVERIFY( collection.isActive( Property1 ) );
QVERIFY( collection.hasActiveProperties() );
QVERIFY( !collection.hasDynamicProperties() );
//preparation
QVERIFY( collection.prepare( context ) );
//test bad property
QVERIFY( !const_cast< const QgsPropertyCollection * >( &collection )->property( Property2 ) );
QVERIFY( !collection.value( Property2, context ).isValid() );
QCOMPARE( collection.value( Property2, context, QStringLiteral( "default" ) ).toString(), QStringLiteral( "default" ) );
QVERIFY( !collection.isActive( Property2 ) );
//test replacing property
QgsProperty property2 = QgsProperty::fromValue( "value2", true );
collection.setProperty( Property1, property2 );
QCOMPARE( collection.count(), 1 );
QCOMPARE( collection.propertyKeys(), QSet< int >() << Property1 );
QCOMPARE( collection.property( Property1 ).value( context ), property2.value( context ) );
QVERIFY( collection.hasActiveProperties() );
QVERIFY( !collection.hasDynamicProperties() );
//implicit conversion
collection.setProperty( Property3, 5 );
QCOMPARE( collection.property( Property3 ).value( context ).toInt(), 5 );
QVERIFY( collection.property( Property3 ).isActive() );
QCOMPARE( collection.count(), 2 );
QCOMPARE( collection.propertyKeys(), QSet<int>() << Property1 << Property3 );
//test removing a property
collection.setProperty( Property1, QgsProperty() );
QVERIFY( !const_cast< const QgsPropertyCollection * >( &collection )->property( Property1 ) );
QVERIFY( !collection.hasProperty( Property1 ) );
QCOMPARE( collection.propertyKeys(), QSet<int>() << Property3 );
QVERIFY( !collection.property( Property1 ) ); // should insert a default created invalid property in internal hash
QVERIFY( !collection.hasProperty( Property1 ) );
//clear
collection.clear();
QCOMPARE( collection.count(), 0 );
QCOMPARE( collection.propertyKeys(), QSet<int>() );
QVERIFY( !collection.hasActiveProperties() );
QVERIFY( !collection.hasDynamicProperties() );
collection.setProperty( Property1, QgsProperty::fromValue( "v1", true ) );
collection.setProperty( Property2, QgsProperty::fromValue( "v2", false ) );
collection.setProperty( Property3, QgsProperty::fromField( "field1", true ) );
collection.setProperty( Property4, QgsProperty::fromExpression( "\"field1\" + \"field2\"", true ) );
QCOMPARE( collection.count(), 4 );
// test referenced fields
QCOMPARE( collection.referencedFields( context ).count(), 2 );
QVERIFY( collection.referencedFields( context ).contains( "field1" ) );
QVERIFY( collection.referencedFields( context ).contains( "field2" ) );
//saving and restoring
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
QVariant collectionElement = collection.toVariant( mDefinitions );
QgsPropertyCollection restoredCollection;
restoredCollection.loadVariant( collectionElement, mDefinitions );
QCOMPARE( restoredCollection.name(), QStringLiteral( "collection" ) );
QCOMPARE( restoredCollection.count(), 4 );
QCOMPARE( restoredCollection.property( Property1 ).propertyType(), QgsProperty::StaticProperty );
QVERIFY( restoredCollection.property( Property1 ).isActive() );
QCOMPARE( restoredCollection.property( Property1 ).staticValue(), QVariant( "v1" ) );
QCOMPARE( restoredCollection.property( Property2 ).propertyType(), QgsProperty::StaticProperty );
QVERIFY( !restoredCollection.property( Property2 ).isActive() );
QCOMPARE( restoredCollection.property( Property2 ).staticValue(), QVariant( "v2" ) );
QCOMPARE( restoredCollection.property( Property3 ).propertyType(), QgsProperty::FieldBasedProperty );
QVERIFY( restoredCollection.property( Property3 ).isActive() );
QCOMPARE( restoredCollection.property( Property3 ).field(), QStringLiteral( "field1" ) );
QCOMPARE( restoredCollection.property( Property4 ).propertyType(), QgsProperty::ExpressionBasedProperty );
QVERIFY( restoredCollection.property( Property4 ).isActive() );
QCOMPARE( restoredCollection.property( Property4 ).expressionString(), QStringLiteral( "\"field1\" + \"field2\"" ) );
QVERIFY( restoredCollection.hasActiveProperties() );
QVERIFY( restoredCollection.hasDynamicProperties() );
// copy constructor
QgsPropertyCollection collection2( collection );
QCOMPARE( collection2.name(), QStringLiteral( "collection" ) );
QCOMPARE( collection2.count(), 4 );
QCOMPARE( collection2.property( Property1 ).propertyType(), QgsProperty::StaticProperty );
QVERIFY( collection2.property( Property1 ).isActive() );
QCOMPARE( collection2.property( Property1 ).staticValue(), QVariant( "v1" ) );
QCOMPARE( collection2.property( Property2 ).propertyType(), QgsProperty::StaticProperty );
QVERIFY( !collection2.property( Property2 ).isActive() );
QCOMPARE( collection2.property( Property2 ).staticValue(), QVariant( "v2" ) );
QCOMPARE( collection2.property( Property3 ).propertyType(), QgsProperty::FieldBasedProperty );
QVERIFY( collection2.property( Property3 ).isActive() );
QCOMPARE( collection2.property( Property3 ).field(), QStringLiteral( "field1" ) );
QCOMPARE( collection2.property( Property4 ).propertyType(), QgsProperty::ExpressionBasedProperty );
QVERIFY( collection2.property( Property4 ).isActive() );
QCOMPARE( collection2.property( Property4 ).expressionString(), QStringLiteral( "\"field1\" + \"field2\"" ) );
QVERIFY( collection2.hasActiveProperties() );
QVERIFY( collection2.hasDynamicProperties() );
// assignment operator
QgsPropertyCollection collection3;
collection3.setProperty( Property1, QgsProperty::fromValue( "aaaa", false ) );
collection3 = collection;
QCOMPARE( collection3.name(), QStringLiteral( "collection" ) );
QCOMPARE( collection3.count(), 4 );
QCOMPARE( collection3.property( Property1 ).propertyType(), QgsProperty::StaticProperty );
QVERIFY( collection3.property( Property1 ).isActive() );
QCOMPARE( collection3.property( Property1 ).staticValue(), QVariant( "v1" ) );
QCOMPARE( collection3.property( Property2 ).propertyType(), QgsProperty::StaticProperty );
QVERIFY( !collection3.property( Property2 ).isActive() );
QCOMPARE( collection3.property( Property2 ).staticValue(), QVariant( "v2" ) );
QCOMPARE( collection3.property( Property3 ).propertyType(), QgsProperty::FieldBasedProperty );
QVERIFY( collection3.property( Property3 ).isActive() );
QCOMPARE( collection3.property( Property3 ).field(), QStringLiteral( "field1" ) );
QCOMPARE( collection3.property( Property4 ).propertyType(), QgsProperty::ExpressionBasedProperty );
QVERIFY( collection3.property( Property4 ).isActive() );
QCOMPARE( collection3.property( Property4 ).expressionString(), QStringLiteral( "\"field1\" + \"field2\"" ) );
QVERIFY( collection3.hasActiveProperties() );
QVERIFY( collection3.hasDynamicProperties() );
//test hasActiveProperties() and hasDynamicProperties()
collection3.property( Property1 ).setActive( false );
collection3.property( Property2 ).setActive( false );
collection3.property( Property3 ).setActive( false );
collection3.property( Property4 ).setActive( false );
QVERIFY( !collection3.hasActiveProperties() );
QVERIFY( !collection3.hasDynamicProperties() );
collection3.property( Property4 ).setActive( true );
QVERIFY( collection3.hasDynamicProperties() );
QVERIFY( collection3.hasActiveProperties() );
collection3.property( Property4 ).setActive( false );
collection3.property( Property2 ).setActive( true );
QVERIFY( !collection3.hasDynamicProperties() );
QVERIFY( collection3.hasActiveProperties() );
collection3.property( Property2 ).setActive( false );
QVERIFY( !collection3.hasActiveProperties() );
collection3.setProperty( Property1, "5" );
QVERIFY( collection3.hasActiveProperties() );
collection3.setProperty( Property1, QgsProperty::fromValue( "6", true ) );
QVERIFY( collection3.hasActiveProperties() );
collection3.setProperty( Property1, QgsProperty::fromValue( "7", false ) );
QVERIFY( !collection3.hasActiveProperties() );
collection3.setProperty( Property3, QVariant( "val" ) );
QVERIFY( collection3.hasActiveProperties() );
}
void TestQgsProperty::collectionStack()
{
//make a feature
QgsFeature ft;
QgsFields fields;
fields.append( QgsField( "field1", QVariant::Int ) );
fields.append( QgsField( "field2", QVariant::Int ) );
ft.setFields( fields );
QgsAttributes attr;
attr << QVariant( 5 ) << QVariant( 7 );
ft.setAttributes( attr );
ft.setValid( true );
// throw it in an expression context
QgsExpressionContext context;
context.setFeature( ft );
context.setFields( fields );
QgsPropertyCollectionStack stack;
//test retrieving from empty stack
QVERIFY( !stack.property( Property1 ) );
QVERIFY( !stack.at( 0 ) );
QVERIFY( !const_cast< const QgsPropertyCollectionStack * >( &stack )->at( 0 ) );
QVERIFY( !stack.collection( "nothing" ) );
QVERIFY( !stack.value( Property1, context ).isValid() );
QCOMPARE( stack.value( Property1, context, "default" ).toString(), QStringLiteral( "default" ) );
QVERIFY( !stack.isActive( Property1 ) );
QVERIFY( stack.referencedFields( context ).isEmpty() );
QCOMPARE( stack.count(), 0 );
QVERIFY( !stack.hasDynamicProperties() );
QVERIFY( !stack.hasActiveProperties() );
//add a collection to the stack
QgsPropertyCollection *collection = new QgsPropertyCollection( "collection" );
stack.appendCollection( collection );
QCOMPARE( stack.count(), 1 );
QCOMPARE( stack.at( 0 ), collection );
QCOMPARE( const_cast< const QgsPropertyCollectionStack * >( &stack )->at( 0 ), collection );
QVERIFY( !stack.collection( "nothing" ) );
QCOMPARE( stack.collection( "collection" ), collection );
QVERIFY( !stack.property( Property1 ) );
QVERIFY( !stack.value( Property1, context ).isValid() );
QCOMPARE( stack.value( Property1, context, "default" ).toString(), QStringLiteral( "default" ) );
QVERIFY( !stack.isActive( Property1 ) );
QVERIFY( !stack.hasDynamicProperties() );
QVERIFY( !stack.hasActiveProperties() );
QVERIFY( stack.referencedFields( context ).isEmpty() );
//now add a property to the collection
QgsProperty property = QgsProperty::fromValue( "value", true );
stack.at( 0 )->setProperty( Property1, property );
QVERIFY( stack.isActive( Property1 ) );
QCOMPARE( stack.property( Property1 ).value( context ), property.value( context ) );
QCOMPARE( stack.value( Property1, context ), property.value( context ) );
QVERIFY( !stack.hasDynamicProperties() );
QVERIFY( stack.hasActiveProperties() );
QVERIFY( !stack.isActive( Property2 ) );
collection->setProperty( Property2, QgsProperty::fromValue( "value1", true ) );
QVERIFY( stack.isActive( Property2 ) );
QVERIFY( !stack.hasDynamicProperties() );
QVERIFY( stack.hasActiveProperties() );
//add a second collection
QgsPropertyCollection *collection2 = new QgsPropertyCollection( "collection2" );
stack.appendCollection( collection2 );
QCOMPARE( stack.count(), 2 );
QCOMPARE( stack.at( 1 ), collection2 );
QCOMPARE( const_cast< const QgsPropertyCollectionStack * >( &stack )->at( 1 ), collection2 );
QCOMPARE( stack.collection( "collection2" ), collection2 );
QVERIFY( !stack.hasDynamicProperties() );
QVERIFY( stack.hasActiveProperties() );
QgsProperty property2 = QgsProperty::fromValue( "value2", true );
collection2->setProperty( Property2, property2 );
QVERIFY( stack.isActive( Property2 ) );
QCOMPARE( stack.property( Property2 ).value( context ), property2.value( context ) );
QCOMPARE( stack.value( Property2, context ), property2.value( context ) );
QVERIFY( !stack.hasDynamicProperties() );
QVERIFY( stack.hasActiveProperties() );
//preparation
QVERIFY( stack.prepare( context ) );
//test adding active property later in the stack
QgsProperty property3 = QgsProperty::fromValue( "value3", true );
collection2->setProperty( Property1, property3 );
QVERIFY( stack.isActive( Property1 ) );
QCOMPARE( stack.property( Property1 ).value( context, "default" ), property3.value( context ) );
QCOMPARE( stack.value( Property1, context ), property3.value( context ) );
collection2->property( Property1 ).setActive( false );
QCOMPARE( stack.value( Property1, context ), property.value( context ) );
//test overriding a property
QgsProperty property4 = QgsProperty::fromValue( "value4", true );
collection2->setProperty( Property2, property4 );
QVERIFY( stack.isActive( Property2 ) );
QCOMPARE( stack.property( Property2 ).value( context ), property4.value( context ) );
QCOMPARE( stack.value( Property2, context ), property4.value( context ) );
collection2->property( Property2 ).setActive( false );
QCOMPARE( stack.property( Property2 ).value( context ), QVariant( "value1" ) );
QCOMPARE( stack.value( Property2, context ), QVariant( "value1" ) );
//clearing
stack.clear();
QCOMPARE( stack.count(), 0 );
QVERIFY( !stack.hasDynamicProperties() );
QVERIFY( !stack.hasActiveProperties() );
// test copying a stack
QgsPropertyCollectionStack stack2;
stack2.appendCollection( new QgsPropertyCollection( "collection1" ) );
stack2.at( 0 )->setProperty( Property1, "val1" );
stack2.at( 0 )->setProperty( Property2, "val2" );
stack2.appendCollection( new QgsPropertyCollection( "collection2" ) );
stack2.at( 1 )->setProperty( Property3, "val3" );
//copy constructor
QgsPropertyCollectionStack stack3( stack2 );
QCOMPARE( stack3.count(), 2 );
QCOMPARE( stack3.at( 0 )->name(), QStringLiteral( "collection1" ) );
QCOMPARE( stack3.at( 1 )->name(), QStringLiteral( "collection2" ) );
QCOMPARE( stack3.at( 0 )->property( Property1 ).staticValue(), QVariant( "val1" ) );
QCOMPARE( stack3.at( 0 )->property( Property2 ).staticValue(), QVariant( "val2" ) );
QCOMPARE( stack3.at( 1 )->property( Property3 ).staticValue(), QVariant( "val3" ) );
QVERIFY( !stack3.hasDynamicProperties() );
QVERIFY( stack3.hasActiveProperties() );
//assignment operator
stack3.clear();
stack3.appendCollection( new QgsPropertyCollection( "temp" ) );
stack3 = stack2;
QCOMPARE( stack3.count(), 2 );
QCOMPARE( stack3.at( 0 )->name(), QStringLiteral( "collection1" ) );
QCOMPARE( stack3.at( 1 )->name(), QStringLiteral( "collection2" ) );
QCOMPARE( stack3.at( 0 )->property( Property1 ).staticValue(), QVariant( "val1" ) );
QCOMPARE( stack3.at( 0 )->property( Property2 ).staticValue(), QVariant( "val2" ) );
QCOMPARE( stack3.at( 1 )->property( Property3 ).staticValue(), QVariant( "val3" ) );
QVERIFY( !stack3.hasDynamicProperties() );
QVERIFY( stack3.hasActiveProperties() );
//check hasDynamicProperties() and hasActiveProperties()
QgsPropertyCollectionStack stack4;
stack4.appendCollection( new QgsPropertyCollection( "collection1" ) );
stack4.at( 0 )->setProperty( Property1, "val1" );
QVERIFY( !stack4.hasDynamicProperties() );
QVERIFY( stack4.hasActiveProperties() );
stack4.at( 0 )->property( Property1 ).setActive( false );
QVERIFY( !stack4.hasActiveProperties() );
stack4.at( 0 )->setProperty( Property1, "6" );
QVERIFY( stack4.hasActiveProperties() );
stack4.at( 0 )->setProperty( Property2, QgsProperty::fromExpression( "\"field1\" + \"field2\"", true ) );
QVERIFY( stack4.hasActiveProperties() );
QVERIFY( stack4.hasDynamicProperties() );
QCOMPARE( stack4.referencedFields( context ), QSet< QString>() << "field1" << "field2" );
stack4.at( 0 )->property( Property1 ).setActive( false );
QVERIFY( stack4.hasActiveProperties() );
QVERIFY( stack4.hasDynamicProperties() );
stack4.at( 0 )->property( Property2 ).setActive( false );
QVERIFY( !stack4.hasActiveProperties() );
QVERIFY( !stack4.hasDynamicProperties() );
}
void TestQgsProperty::curveTransform()
{
QgsCurveTransform t;
// linear transform
QCOMPARE( t.y( -1 ), 0.0 );
QCOMPARE( t.y( 0 ), 0.0 );
QCOMPARE( t.y( 0.2 ), 0.2 );
QCOMPARE( t.y( 0.5 ), 0.5 );
QCOMPARE( t.y( 0.8 ), 0.8 );
QCOMPARE( t.y( 1 ), 1.0 );
QCOMPARE( t.y( 2 ), 1.0 );
QVector< double > x;
x << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2;
QVector< double > y = t.y( x );
QCOMPARE( y[0], 0.0 );
QCOMPARE( y[1], 0.0 );
QCOMPARE( y[2], 0.2 );
QCOMPARE( y[3], 0.5 );
QCOMPARE( y[4], 0.8 );
QCOMPARE( y[5], 1.0 );
QCOMPARE( y[6], 1.0 );
// linear transform with y =/= x
checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1.0, 0.8 ),
QVector< double >() << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2,
QVector< double >() << 0.2 << 0.2 << 0.32 << 0.5 << 0.68 << 0.8 << 0.8 );
// reverse linear transform with y = -x
checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0.0, 1.0 ) << QgsPointXY( 1.0, 0 ),
QVector< double >() << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2,
QVector< double >() << 1.0 << 1.0 << 0.8 << 0.5 << 0.2 << 0.0 << 0.0 );
// OK, time for some more complex tests...
// 3 control points, but linear
checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0, 0.0 ) << QgsPointXY( 0.2, 0.2 ) << QgsPointXY( 1.0, 1.0 ),
QVector< double >() << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2,
QVector< double >() << 0.0 << 0.0 << 0.2 << 0.5 << 0.8 << 1.0 << 1.0 );
// test for "flat" response for x outside of control point range
checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0.2, 0.2 ) << QgsPointXY( 0.5, 0.5 ) << QgsPointXY( 0.8, 0.8 ),
QVector< double >() << -1 << 0 << 0.1 << 0.2 << 0.5 << 0.8 << 0.9 << 1 << 2,
QVector< double >() << 0.2 << 0.2 << 0.2 << 0.2 << 0.5 << 0.8 << 0.8 << 0.8 << 0.8 );
//curves!
checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.4, 0.6 ) << QgsPointXY( 0.6, 0.8 ) << QgsPointXY( 1.0, 1.0 ),
QVector< double >() << -1 << 0 << 0.2 << 0.4 << 0.5 << 0.6 << 0.8 << 0.9 << 1.0 << 2.0,
QVector< double >() << 0.0 << 0.0 << 0.321429 << 0.6 << 0.710714 << 0.8 << 0.921429 << 0.963393 << 1.0 << 1.0 );
//curves with more control points
checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.2, 0.6 ) << QgsPointXY( 0.4, 0.6 ) << QgsPointXY( 0.6, 0.8 ) << QgsPointXY( 0.8, 0.3 ) << QgsPointXY( 1.0, 1.0 ),
QVector< double >() << -1 << 0 << 0.2 << 0.4 << 0.5 << 0.6 << 0.8 << 0.9 << 1.0 << 2.0,
QVector< double >() << 0.0 << 0.0 << 0.6 << 0.6 << 0.751316 << 0.8 << 0.3 << 0.508074 << 1.0 << 1.0 );
// general tests
QList< QgsPointXY > points = QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.4, 0.6 ) << QgsPointXY( 0.6, 0.8 ) << QgsPointXY( 1.0, 1.0 );
QgsCurveTransform src( points );
QCOMPARE( src.controlPoints(), points );
points = QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.5, 0.6 ) << QgsPointXY( 0.6, 0.8 ) << QgsPointXY( 1.0, 1.0 );
src.setControlPoints( points );
QCOMPARE( src.controlPoints(), points );
src.setControlPoints( QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 1.0, 1.0 ) );
src.addControlPoint( 0.2, 0.3 );
src.addControlPoint( 0.1, 0.4 );
QCOMPARE( src.controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.1, 0.4 ) << QgsPointXY( 0.2, 0.3 ) << QgsPointXY( 1.0, 1.0 ) );
// remove non-existent point
src.removeControlPoint( 0.6, 0.7 );
QCOMPARE( src.controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.1, 0.4 ) << QgsPointXY( 0.2, 0.3 ) << QgsPointXY( 1.0, 1.0 ) );
// remove valid point
src.removeControlPoint( 0.1, 0.4 );
QCOMPARE( src.controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.2, 0.3 ) << QgsPointXY( 1.0, 1.0 ) );
// copy constructor
QgsCurveTransform dest( src );
QCOMPARE( dest.controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.2, 0.3 ) << QgsPointXY( 1.0, 1.0 ) );
// check a value to ensure that derivative matrix was copied OK
QGSCOMPARENEAR( dest.y( 0.5 ), 0.1, 0.638672 );
// assignment operator
QgsCurveTransform dest2;
dest2 = src;
QCOMPARE( dest2.controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.2, 0.3 ) << QgsPointXY( 1.0, 1.0 ) );
QGSCOMPARENEAR( dest2.y( 0.5 ), 0.1, 0.638672 );
// writing and reading from xml
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
QDomElement element = doc.createElement( "xform" );
QVERIFY( src.writeXml( element, doc ) );
QgsCurveTransform r1;
QVERIFY( r1.readXml( element, doc ) );
QCOMPARE( r1.controlPoints(), src.controlPoints() );
QGSCOMPARENEAR( dest2.y( 0.5 ), 0.1, 0.638672 );
}
void TestQgsProperty::asVariant()
{
QgsProperty original = QgsProperty::fromField( QStringLiteral( "field1" ), true );
//convert to and from a QVariant
QVariant var = QVariant::fromValue( original );
QVERIFY( var.isValid() );
QgsProperty fromVar = qvariant_cast<QgsProperty>( var );
QCOMPARE( fromVar.propertyType(), QgsProperty::FieldBasedProperty );
QVERIFY( fromVar.isActive() );
QCOMPARE( fromVar.field(), QStringLiteral( "field1" ) );
}
void TestQgsProperty::checkCurveResult( const QList<QgsPointXY> &controlPoints, const QVector<double> &x, const QVector<double> &y )
{
// build transform
QgsCurveTransform t( controlPoints );
// we check two approaches
for ( int i = 0; i < x.count(); ++i )
{
QGSCOMPARENEAR( t.y( x.at( i ) ), y.at( i ), 0.0001 );
}
QVector< double > results = t.y( x );
for ( int i = 0; i < y.count(); ++i )
{
QGSCOMPARENEAR( results.at( i ), y.at( i ), 0.0001 );
}
}
QGSTEST_MAIN( TestQgsProperty )
#include "testqgsproperty.moc"