diff --git a/src/app/qgsvectorlayerproperties.cpp b/src/app/qgsvectorlayerproperties.cpp index ea0cf06ca59f..30715944577a 100644 --- a/src/app/qgsvectorlayerproperties.cpp +++ b/src/app/qgsvectorlayerproperties.cpp @@ -950,14 +950,25 @@ void QgsVectorLayerProperties::on_pbnLoadStyle_clicked() { QSettings myQSettings; // where we keep last used filter in persistent state QString myLastUsedDir = myQSettings.value( "style/lastStyleDir", "." ).toString(); - QString myFileName = QFileDialog::getOpenFileName( this, tr( "Load layer properties from style file (.qml)" ), myLastUsedDir, tr( "QGIS Layer Style File (*.qml)" ) ); + QString myFileName = QFileDialog::getOpenFileName( this, tr( "Load layer properties from style file" ), myLastUsedDir, + tr( "QGIS Layer Style File (*.qml)" ) + ";;" + tr( "SLD File (*.sld)" ) ); if ( myFileName.isNull() ) { return; } + QString myMessage; bool defaultLoadedFlag = false; - QString myMessage = layer->loadNamedStyle( myFileName, defaultLoadedFlag ); + + if ( myFileName.endsWith( ".sld", Qt::CaseInsensitive ) ) + { + // load from SLD + myMessage = layer->loadSldStyle( myFileName, defaultLoadedFlag ); + } + else + { + myMessage = layer->loadNamedStyle( myFileName, defaultLoadedFlag ); + } //reset if the default style was loaded ok only if ( defaultLoadedFlag ) { @@ -966,7 +977,7 @@ void QgsVectorLayerProperties::on_pbnLoadStyle_clicked() else { //let the user know what went wrong - QMessageBox::information( this, tr( "Saved Style" ), myMessage ); + QMessageBox::information( this, tr( "Load Style" ), myMessage ); } QFileInfo myFI( myFileName ); @@ -979,7 +990,8 @@ void QgsVectorLayerProperties::on_pbnSaveStyleAs_clicked() { QSettings myQSettings; // where we keep last used filter in persistent state QString myLastUsedDir = myQSettings.value( "style/lastStyleDir", "." ).toString(); - QString myOutputFileName = QFileDialog::getSaveFileName( this, tr( "Save layer properties as style file (.qml)" ), myLastUsedDir, tr( "QGIS Layer Style File (*.qml)" ) ); + QString myOutputFileName = QFileDialog::getSaveFileName( this, tr( "Save layer properties as style file" ), myLastUsedDir, + tr( "QGIS Layer Style File (*.qml)" ) + ";;" + tr( "SLD File (*.sld)" ) ); if ( myOutputFileName.isNull() ) //dialog canceled { return; @@ -987,14 +999,25 @@ void QgsVectorLayerProperties::on_pbnSaveStyleAs_clicked() apply(); // make sure the qml to save is uptodate + QString myMessage; + bool defaultLoadedFlag = false; + //ensure the user never omitted the extension from the file name - if ( !myOutputFileName.endsWith( ".qml", Qt::CaseInsensitive ) ) + if ( myOutputFileName.endsWith( ".sld", Qt::CaseInsensitive ) ) + { + // convert to SLD + myMessage = layer->saveSldStyle( myOutputFileName, defaultLoadedFlag ); + } + else { - myOutputFileName += ".qml"; + if ( !myOutputFileName.endsWith( ".qml", Qt::CaseInsensitive ) ) + { + myOutputFileName += ".qml"; + } + + myMessage = layer->saveNamedStyle( myOutputFileName, defaultLoadedFlag ); } - bool defaultLoadedFlag = false; - QString myMessage = layer->saveNamedStyle( myOutputFileName, defaultLoadedFlag ); //reset if the default style was loaded ok only if ( defaultLoadedFlag ) { diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp index 5bb7959c7b79..7531f52fcbea 100644 --- a/src/core/qgsexpression.cpp +++ b/src/core/qgsexpression.cpp @@ -25,6 +25,7 @@ #include "qgsdistancearea.h" #include "qgsfeature.h" #include "qgsgeometry.h" +#include "qgslogger.h" // from parser extern QgsExpression::Node* parseExpression( const QString& str, QString& parserErrorMsg ); @@ -109,11 +110,27 @@ const char* QgsExpression::BinaryOperatorText[] = "||" }; +const char* QgsExpression::BinaryOgcOperatorText[] = +{ + "Or", "And", + "PropertyIsEqualTo", "PropertyIsNotEqualTo", + "PropertyIsGreaterThanOrEqualTo", "PropertyIsLessThanOrEqualTo", + "PropertyIsLessThan", "PropertyIsGreaterThan", + "", "PropertyIsLike", "", "", "", + "Add", "Sub", "Mul", "Div", "", "", + "" +}; + const char* QgsExpression::UnaryOperatorText[] = { "NOT", "-" }; +const char* QgsExpression::UnaryOgcOperatorText[] = +{ + "Not", "" +}; + /////////////////////////////////////////////// // functions @@ -546,6 +563,49 @@ QString QgsExpression::dump() const return mRootNode->dump(); } +void QgsExpression::toOgcFilter( QDomDocument &doc, QDomElement &element ) const +{ + if ( !mRootNode ) + return; + + mRootNode->toOgcFilter( doc, element ); +} + +QgsExpression* QgsExpression::createFromOgcFilter( QDomElement &element ) +{ + if ( element.isNull() || !element.hasChildNodes() ) + return NULL; + + QgsExpression *expr = new QgsExpression(); + + QDomElement childElem = element.firstChildElement(); + while ( !childElem.isNull() ) + { + QString errorMsg; + QgsExpression::Node *node = QgsExpression::Node::createFromOgcFilter( childElem, errorMsg ); + if ( !node ) + { + // invalid expression, parser error + expr->mParserErrorString = errorMsg; + return expr; + } + + // use the concat binary operator to append to the root node + if ( !expr->mRootNode ) + { + expr->mRootNode = node; + } + else + { + expr->mRootNode = new QgsExpression::NodeBinaryOperator( boConcat, expr->mRootNode, node ); + } + + childElem = childElem.nextSiblingElement(); + } + + return expr; +} + void QgsExpression::acceptVisitor( QgsExpression::Visitor& v ) { if ( mRootNode ) @@ -553,6 +613,116 @@ void QgsExpression::acceptVisitor( QgsExpression::Visitor& v ) } +QgsExpression::Node* QgsExpression::Node::createFromOgcFilter( QDomElement &element, QString &errorMessage ) +{ + if ( element.isNull() ) + return NULL; + + // check for unary operators + int unaryOpCount = sizeof( UnaryOgcOperatorText ) / sizeof( UnaryOgcOperatorText[0] ); + for ( int i = 0; i < unaryOpCount; i++ ) + { + QString ogcOperatorName = UnaryOgcOperatorText[ i ]; + if ( ogcOperatorName.isEmpty() ) + continue; + + if ( element.localName() == ogcOperatorName ) + { + QgsExpression::Node *node = QgsExpression::NodeUnaryOperator::createFromOgcFilter( element, errorMessage ); + if ( node ) + return node; + + return NULL; + } + } + + // check for binary operators + int binaryOpCount = sizeof( BinaryOgcOperatorText ) / sizeof( BinaryOgcOperatorText[0] ); + for ( int i = 0; i < binaryOpCount; i++ ) + { + QString ogcOperatorName = BinaryOgcOperatorText[ i ]; + if ( ogcOperatorName.isEmpty() ) + continue; + + if ( element.localName() == ogcOperatorName ) + { + QgsExpression::Node *node = QgsExpression::NodeBinaryOperator::createFromOgcFilter( element, errorMessage ); + if ( node ) + return node; + + return NULL; + } + } + + // check for other OGC operators, convert them to expressions + + if ( element.localName() == "PropertyIsNull" ) + { + return QgsExpression::NodeBinaryOperator::createFromOgcFilter( element, errorMessage ); + } + else if ( element.localName() == "Literal" ) + { + return QgsExpression::NodeLiteral::createFromOgcFilter( element, errorMessage ); + } + else if ( element.localName() == "Function") + { + return QgsExpression::NodeFunction::createFromOgcFilter( element, errorMessage ); + } + else if ( element.localName() == "PropertyName") + { + return QgsExpression::NodeColumnRef::createFromOgcFilter( element, errorMessage ); + } + else if ( element.localName() == "PropertyIsBetween" ) + { + // encode a Range check + QgsExpression::Node *operand = 0, *lowerBound = 0, *upperBound = 0; + + QDomElement operandElem = element.firstChildElement( "LowerBoundary" ); + if ( !operandElem.isNull() ) + lowerBound = createFromOgcFilter( operandElem, errorMessage ); + + operandElem = element.firstChildElement( "UpperBoundary" ); + if ( !operandElem.isNull() ) + upperBound = createFromOgcFilter( operandElem, errorMessage ); + + // + operandElem = element.firstChildElement(); + while ( !operandElem.isNull() ) + { + if ( operandElem.localName() != "LowerBoundary" && + operandElem.localName() != "UpperBoundary" ) + { + operand = createFromOgcFilter( operandElem, errorMessage ); + break; + } + + operandElem = operandElem.nextSiblingElement(); + } + + if ( !operand || !lowerBound || !upperBound ) + { + if ( operand ) + delete operand; + + if ( lowerBound ) + delete lowerBound; + + if ( upperBound ) + delete upperBound; + + errorMessage = "missing some required sub-elements in ogc:PropertyIsBetween"; + return NULL; + } + + QgsExpression::Node *geOperator = new QgsExpression::NodeBinaryOperator( boGE, operand, lowerBound ); + QgsExpression::Node *leOperator = new QgsExpression::NodeBinaryOperator( boLE, operand, upperBound ); + return new QgsExpression::NodeBinaryOperator( boAnd, geOperator, leOperator ); + } + + errorMessage += QString( "unable to convert '%1' element to a valid expression: it is not supported yet or it has invalid arguments" ).arg( element.tagName() ); + return NULL; +} + /////////////////////////////////////////////// // nodes @@ -567,6 +737,14 @@ QString QgsExpression::NodeList::dump() const return msg; } +void QgsExpression::NodeList::toOgcFilter( QDomDocument &doc, QDomElement &element ) const +{ + foreach( Node* n, mList ) + { + n->toOgcFilter( doc, element ); + } +} + // QVariant QgsExpression::NodeUnaryOperator::eval( QgsExpression* parent, QgsFeature* f ) @@ -607,6 +785,58 @@ QString QgsExpression::NodeUnaryOperator::dump() const return QString( "%1 %2" ).arg( UnaryOperatorText[mOp] ).arg( mOperand->dump() ); } +void QgsExpression::NodeUnaryOperator::toOgcFilter( QDomDocument &doc, QDomElement &element ) const +{ + QDomElement uoElem; + switch ( mOp ) + { + case uoMinus: + uoElem = doc.createElement( "ogc:Literal" ); + uoElem.appendChild( doc.createTextNode( "-" ) ); + break; + case uoNot: + uoElem = doc.createElement( "ogc:Not" ); + break; + + default: + element.appendChild( doc.createComment( QString( "Unary operator %1 not implemented yet" ).arg( UnaryOperatorText[mOp] ) ) ); + return; + } + mOperand->toOgcFilter( doc, uoElem ); + element.appendChild( uoElem ); +} + +QgsExpression::Node* QgsExpression::NodeUnaryOperator::createFromOgcFilter( QDomElement &element, QString &errorMessage ) +{ + if ( element.isNull() ) + return NULL; + + int unaryOpCount = sizeof( UnaryOgcOperatorText ) / sizeof( UnaryOgcOperatorText[0] ); + for ( int i = 0; i < unaryOpCount; i++ ) + { + QString ogcOperatorName = UnaryOgcOperatorText[ i ]; + if ( ogcOperatorName.isEmpty() ) + continue; + + if ( element.localName() != ogcOperatorName ) + continue; + + QDomElement operandElem = element.firstChildElement(); + QgsExpression::Node* operand = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage ); + if ( !operand ) + { + if ( errorMessage.isEmpty() ) + errorMessage = QString( "invalid operand for '%1' unary operator" ).arg( ogcOperatorName ); + return NULL; + } + + return new QgsExpression::NodeUnaryOperator( ( UnaryOperator ) i, operand ); + } + + errorMessage = QString( "%1 unary operator not supported." ).arg( element.tagName() ); + return NULL; +} + // QVariant QgsExpression::NodeBinaryOperator::eval( QgsExpression* parent, QgsFeature* f ) @@ -822,6 +1052,171 @@ QString QgsExpression::NodeBinaryOperator::dump() const return QString( "%1 %2 %3" ).arg( mOpLeft->dump() ).arg( BinaryOperatorText[mOp] ).arg( mOpRight->dump() ); } +void QgsExpression::NodeBinaryOperator::toOgcFilter( QDomDocument &doc, QDomElement &element ) const +{ + if ( mOp == boConcat ) + { + // the concat binary operator must only convert its operands + mOpLeft->toOgcFilter( doc, element ); + mOpRight->toOgcFilter( doc, element ); + return; + } + + if ( mOp == boIs || mOp == boIsNot ) + { + // check if one of the operands is NULL + QgsExpression::NodeLiteral *opLeftLiteral = dynamic_cast( mOpLeft ); + QgsExpression::NodeLiteral *opRightLiteral = dynamic_cast( mOpRight ); + + if ( opLeftLiteral && opLeftLiteral->value().isNull() && + opRightLiteral && opRightLiteral->value().isNull() ) + { + // why could anybody find useful to use NULL IS NULL??? + // BTW avoid issues by converting it to 1 = 1 + QDomElement eqElem = doc.createElement( "ogc:PropertyIsEqual" ); + + QDomElement literalElem = doc.createElement( "ogc:Literal" ); + literalElem.appendChild( doc.createTextNode( "1" ) ); + eqElem.appendChild( literalElem ); + + literalElem = doc.createElement( "ogc:Literal" ); + literalElem.appendChild( doc.createTextNode( "1" ) ); + eqElem.appendChild( literalElem ); + + element.appendChild( eqElem ); + } + else if ( ( opLeftLiteral && opLeftLiteral->value().isNull() ) || + ( opRightLiteral && opRightLiteral->value().isNull() ) ) + { + // at least one operand is NULL, use element + QDomElement isNullElem = doc.createElement( "ogc:PropertyIsNull" ); + QgsExpression::Node *operand = opLeftLiteral->value().isNull() ? mOpRight : mOpLeft; + operand->toOgcFilter( doc, isNullElem ); + + if ( mOp == boIsNot ) + { + // append to element if IS NOT operator was required + QDomElement notOpElem = doc.createElement( "ogc:Not" ); + notOpElem.appendChild( isNullElem ); + element.appendChild( notOpElem ); + } + else + { + element.appendChild( isNullElem ); + } + } + else + { + // both operands are not null, use element + QDomElement eqElem = doc.createElement( "ogc:PropertyIsEqual" ); + mOpLeft->toOgcFilter( doc, eqElem ); + mOpRight->toOgcFilter( doc, eqElem ); + element.appendChild( eqElem ); + } + return; + } + + if ( mOp == boILike ) + { + // XXX why ogc:PropertyIsLikeType extends ogc:ComparisonOpsType + // which has no matchCase attribute? Shouldn't it be better if + // would extend BinaryComparisonOpType which has that attribute + // and doesn't require to have a ogc:PropertyName as first parameter? + QgsExpression ilikeExpr( QString( "upper( %1 ) LIKE upper( %2 )" ).arg( mOpLeft->dump() ).arg( mOpRight->dump() ) ); + ilikeExpr.toOgcFilter( doc, element ); + return; + } + + QString opText = BinaryOgcOperatorText[mOp]; + if ( opText.isEmpty() ) + { + // not implemented binary operators + // TODO: regex, % (mod), ^ (pow) are not supported yet + element.appendChild( doc.createComment( QString( "Binary operator %1 not implemented yet" ).arg( BinaryOperatorText[mOp] ) ) ); + return; + } + + QDomElement boElem = doc.createElement( "ogc:" + opText ); + if ( mOp == boLike ) + { + // setup wildcards to + boElem.setAttribute( "wildCard", "%" ); + boElem.setAttribute( "singleChar", "?" ); + boElem.setAttribute( "escapeChar", "!" ); + } + + mOpLeft->toOgcFilter( doc, boElem ); + mOpRight->toOgcFilter( doc, boElem ); + element.appendChild( boElem ); +} + +QgsExpression::Node* QgsExpression::NodeBinaryOperator::createFromOgcFilter( QDomElement &element, QString &errorMessage ) +{ + if ( element.isNull() ) + return NULL; + + QgsExpression::Node* opLeft = 0; + QgsExpression::Node* opRight = 0; + + // convert ogc:PropertyIsNull to IS operator with NULL right operand + if ( element.localName() == "PropertyIsNull" ) + { + QDomElement operandElem = element.firstChildElement(); + opLeft = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage ); + if ( !opLeft ) + return NULL; + + opRight = new QgsExpression::NodeLiteral( QVariant() ); + return new QgsExpression::NodeBinaryOperator( boIs, opLeft, opRight ); + } + + // the other binary operators + int binaryOpCount = sizeof( BinaryOgcOperatorText ) / sizeof( BinaryOgcOperatorText[0] ); + for ( int i = 0; i < binaryOpCount; i++ ) + { + QString ogcOperatorName = BinaryOgcOperatorText[ i ]; + if ( ogcOperatorName.isEmpty() ) + continue; + + if ( element.localName() != ogcOperatorName ) + continue; + + QDomElement operandElem = element.firstChildElement(); + opLeft = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage ); + if ( !opLeft ) + { + if ( errorMessage.isEmpty() ) + errorMessage = QString( "invalid left operand for '%1' binary operator" ).arg( ogcOperatorName ); + break; + } + + operandElem = operandElem.nextSiblingElement(); + opRight = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage ); + if ( !opRight ) + { + if ( errorMessage.isEmpty() ) + errorMessage = QString( "invalid right operand for '%1' binary operator" ).arg( ogcOperatorName ); + break; + } + + return new QgsExpression::NodeBinaryOperator( ( BinaryOperator ) i, opLeft, opRight ); + } + + if ( !opLeft && !opRight ) + { + errorMessage = QString( "'%1' binary operator not supported." ).arg( element.tagName() ); + return NULL; + } + + if ( opLeft ) + delete opLeft; + + if ( opRight ) + delete opRight; + + return NULL; +} + // QVariant QgsExpression::NodeInOperator::eval( QgsExpression* parent, QgsFeature* f ) @@ -885,6 +1280,31 @@ QString QgsExpression::NodeInOperator::dump() const return QString( "%1 IN (%2)" ).arg( mNode->dump() ).arg( mList->dump() ); } +void QgsExpression::NodeInOperator::toOgcFilter( QDomDocument &doc, QDomElement &element ) const +{ + // XXX use a function instead of multiple comparations? + + QDomElement *parent = &element; + + QDomElement orElem; + if ( mList->list().size() > 1 ) + { + orElem = doc.createElement( "ogc:Or" ); + element.appendChild( orElem ); + + parent = &orElem; + } + + foreach( Node* n, mList->list() ) + { + QDomElement eqElem = doc.createElement( "ogc:PropertyIsEqualTo" ); + mNode->toOgcFilter( doc, eqElem ); + n->toOgcFilter( doc, eqElem ); + + parent->appendChild( eqElem ); + } +} + // QVariant QgsExpression::NodeFunction::eval( QgsExpression* parent, QgsFeature* f ) @@ -935,6 +1355,58 @@ QString QgsExpression::NodeFunction::dump() const return QString( "%1(%2)" ).arg( fd.mName ).arg( mArgs ? mArgs->dump() : QString() ); // function } +void QgsExpression::NodeFunction::toOgcFilter( QDomDocument &doc, QDomElement &element ) const +{ + const FunctionDef& fd = BuiltinFunctions()[mFnIndex]; + if ( fd.mParams == 0 ) + return; // TODO: special column + + QDomElement funcElem = doc.createElement( "ogc:Function" ); + funcElem.setAttribute( "name", fd.mName ); + mArgs->toOgcFilter( doc, funcElem ); + element.appendChild( funcElem ); +} + +QgsExpression::Node* QgsExpression::NodeFunction::createFromOgcFilter( QDomElement &element, QString &errorMessage ) +{ + if ( element.isNull() ) + return NULL; + + if ( element.localName() != "Function" ) + { + errorMessage = QString( "ogc:Function expected, got %1" ).arg( element.tagName() ); + return NULL; + } + + for ( int i = 0; i < BuiltinFunctions().size(); i++ ) + { + QgsExpression::FunctionDef funcDef = BuiltinFunctions()[i]; + + if ( element.attribute( "name" ) != funcDef.mName ) + continue; + + QgsExpression::NodeList *args = new QgsExpression::NodeList(); + + QDomElement operandElem = element.firstChildElement(); + while ( !operandElem.isNull() ) + { + QgsExpression::Node* op = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage ); + if ( !op ) + { + delete args; + return NULL; + } + args->append( op ); + + operandElem = operandElem.nextSiblingElement(); + } + + return new QgsExpression::NodeFunction( i, args ); + } + + return NULL; +} + // QVariant QgsExpression::NodeLiteral::eval( QgsExpression* , QgsFeature* ) @@ -962,6 +1434,92 @@ QString QgsExpression::NodeLiteral::dump() const } } +void QgsExpression::NodeLiteral::toOgcFilter( QDomDocument &doc, QDomElement &element ) const +{ + QString value; + if ( !mValue.isNull() ) + { + switch ( mValue.type() ) + { + case QVariant::Int: + value = QString::number( mValue.toInt() ); + break; + case QVariant::Double: + value = QString::number( mValue.toDouble() ); + break; + case QVariant::String: + value = mValue.toString(); + break; + default: + break; + } + } + QDomElement litElem = doc.createElement( "ogc:Literal" ); + litElem.appendChild( doc.createTextNode( value ) ); + element.appendChild( litElem ); +} + +QgsExpression::Node* QgsExpression::NodeLiteral::createFromOgcFilter( QDomElement &element, QString &errorMessage ) +{ + if ( element.isNull() ) + return NULL; + + if ( element.localName() != "Literal" ) + { + errorMessage = QString( "ogc:Literal expected, got %1" ).arg( element.tagName() ); + return NULL; + } + + QgsExpression::Node *root = 0; + + // the literal content can have more children (e.g. CDATA section, text, ...) + QDomNode childNode = element.firstChild(); + while ( !childNode.isNull() ) + { + QgsExpression::Node* operand = 0; + + if ( childNode.nodeType() == QDomNode::ElementNode ) + { + // found a element node (e.g. PropertyName), convert it + QDomElement operandElem = childNode.toElement(); + operand = QgsExpression::Node::createFromOgcFilter( operandElem, errorMessage ); + if ( !operand ) + { + if ( root ) + delete root; + + errorMessage = QString( "'%1' is an invalid or not supported content for ogc:Literal" ).arg( operandElem.tagName() ); + return NULL; + } + } + else + { + // probably a text/CDATA node, convert its content to string + operand = new QgsExpression::NodeLiteral( childNode.nodeValue() ); + } + + if ( !operand ) + continue; + + // use the concat operator to merge the ogc:Literal children + if ( !root ) + { + root = operand; + } + else + { + root = new QgsExpression::NodeBinaryOperator( boConcat, root, operand ); + } + + childNode = childNode.nextSibling(); + } + + if ( root ) + return root; + + return NULL; +} + // QVariant QgsExpression::NodeColumnRef::eval( QgsExpression* /*parent*/, QgsFeature* f ) @@ -989,6 +1547,27 @@ QString QgsExpression::NodeColumnRef::dump() const return mName; } +void QgsExpression::NodeColumnRef::toOgcFilter( QDomDocument &doc, QDomElement &element ) const +{ + QDomElement propElem = doc.createElement( "ogc:PropertyName" ); + propElem.appendChild( doc.createTextNode( mName ) ); + element.appendChild( propElem ); +} + +QgsExpression::Node* QgsExpression::NodeColumnRef::createFromOgcFilter( QDomElement &element, QString &errorMessage ) +{ + if ( element.isNull() ) + return NULL; + + if ( element.localName() != "PropertyName" ) + { + errorMessage = QString( "ogc:PropertyName expected, got %1" ).arg( element.tagName() ); + return NULL; + } + + return new QgsExpression::NodeColumnRef( element.firstChild().nodeValue() ); +} + // QVariant QgsExpression::NodeCondition::eval( QgsExpression* parent, QgsFeature* f ) @@ -1045,6 +1624,12 @@ QString QgsExpression::NodeCondition::dump() const return msg; } +void QgsExpression::NodeCondition::toOgcFilter( QDomDocument &doc, QDomElement &element ) const +{ + // TODO: if(cond) ... [else if (cond2) ...]* [else ...] + element.appendChild( doc.createComment( "CASE operator not implemented yet" ) ); +} + QStringList QgsExpression::NodeCondition::referencedColumns() const { QStringList lst; diff --git a/src/core/qgsexpression.h b/src/core/qgsexpression.h index 4f64f823d9c5..617ae6d9d042 100644 --- a/src/core/qgsexpression.h +++ b/src/core/qgsexpression.h @@ -170,6 +170,8 @@ class CORE_EXPORT QgsExpression static const char* BinaryOperatorText[]; static const char* UnaryOperatorText[]; + static const char* BinaryOgcOperatorText[]; + static const char* UnaryOgcOperatorText[]; typedef QVariant( *FcnEval )( const QVariantList& values, QgsFeature* f, QgsExpression* parent ); @@ -226,6 +228,9 @@ class CORE_EXPORT QgsExpression virtual QString dump() const = 0; + virtual void toOgcFilter( QDomDocument &doc, QDomElement &element ) const { Q_UNUSED( doc ); Q_UNUSED( element ); } + static QgsExpression::Node* createFromOgcFilter( QDomElement &element, QString &errorMessage ); + virtual QStringList referencedColumns() const = 0; virtual bool needsGeometry() const = 0; @@ -243,6 +248,8 @@ class CORE_EXPORT QgsExpression QList list() { return mList; } virtual QString dump() const; + virtual void toOgcFilter( QDomDocument &doc, QDomElement &element ) const; + protected: QList mList; }; @@ -259,6 +266,10 @@ class CORE_EXPORT QgsExpression virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; + + virtual void toOgcFilter( QDomDocument &doc, QDomElement &element ) const; + static QgsExpression::Node* createFromOgcFilter( QDomElement &element, QString &errorMessage ); + virtual QStringList referencedColumns() const { return mOperand->referencedColumns(); } virtual bool needsGeometry() const { return mOperand->needsGeometry(); } virtual void accept( Visitor& v ) { v.visit( this ); } @@ -281,6 +292,10 @@ class CORE_EXPORT QgsExpression virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; + + virtual void toOgcFilter( QDomDocument &doc, QDomElement &element ) const; + static QgsExpression::Node* createFromOgcFilter( QDomElement &element, QString &errorMessage ); + virtual QStringList referencedColumns() const { return mOpLeft->referencedColumns() + mOpRight->referencedColumns(); } virtual bool needsGeometry() const { return mOpLeft->needsGeometry() || mOpRight->needsGeometry(); } virtual void accept( Visitor& v ) { v.visit( this ); } @@ -308,6 +323,9 @@ class CORE_EXPORT QgsExpression virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; + + virtual void toOgcFilter( QDomDocument &doc, QDomElement &element ) const; + virtual QStringList referencedColumns() const { QStringList lst( mNode->referencedColumns() ); foreach( Node* n, mList->list() ) lst.append( n->referencedColumns() ); return lst; } virtual bool needsGeometry() const { bool needs = false; foreach( Node* n, mList->list() ) needs |= n->needsGeometry(); return needs; } virtual void accept( Visitor& v ) { v.visit( this ); } @@ -331,6 +349,10 @@ class CORE_EXPORT QgsExpression virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; + + virtual void toOgcFilter( QDomDocument &doc, QDomElement &element ) const; + static QgsExpression::Node* createFromOgcFilter( QDomElement &element, QString &errorMessage ); + virtual QStringList referencedColumns() const { QStringList lst; if ( !mArgs ) return lst; foreach( Node* n, mArgs->list() ) lst.append( n->referencedColumns() ); return lst; } virtual bool needsGeometry() const { bool needs = BuiltinFunctions()[mFnIndex].mUsesGeometry; if ( mArgs ) { foreach( Node* n, mArgs->list() ) needs |= n->needsGeometry(); } return needs; } virtual void accept( Visitor& v ) { v.visit( this ); } @@ -351,6 +373,10 @@ class CORE_EXPORT QgsExpression virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; + + virtual void toOgcFilter( QDomDocument &doc, QDomElement &element ) const; + static QgsExpression::Node* createFromOgcFilter( QDomElement &element, QString &errorMessage ); + virtual QStringList referencedColumns() const { return QStringList(); } virtual bool needsGeometry() const { return false; } virtual void accept( Visitor& v ) { v.visit( this ); } @@ -369,6 +395,10 @@ class CORE_EXPORT QgsExpression virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual QString dump() const; + + virtual void toOgcFilter( QDomDocument &doc, QDomElement &element ) const; + static QgsExpression::Node* createFromOgcFilter( QDomElement &element, QString &errorMessage ); + virtual QStringList referencedColumns() const { return QStringList( mName ); } virtual bool needsGeometry() const { return false; } virtual void accept( Visitor& v ) { v.visit( this ); } @@ -399,6 +429,9 @@ class CORE_EXPORT QgsExpression virtual QVariant eval( QgsExpression* parent, QgsFeature* f ); virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields ); virtual QString dump() const; + + virtual void toOgcFilter( QDomDocument &doc, QDomElement &element ) const; + virtual QStringList referencedColumns() const; virtual bool needsGeometry() const; virtual void accept( Visitor& v ) { v.visit( this ); } @@ -428,7 +461,13 @@ class CORE_EXPORT QgsExpression /** entry function for the visitor pattern */ void acceptVisitor( Visitor& v ); + // convert from/to OGC Filter + void toOgcFilter( QDomDocument &doc, QDomElement &element ) const; + static QgsExpression* createFromOgcFilter( QDomElement &element ); + protected: + // internally used to create an empty expression + QgsExpression() : mRootNode( NULL ), mRowNumber( 0 ), mCalc( NULL ) {} QString mExpression; Node* mRootNode; diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index dbe7f232b4aa..3db1cea8d6a6 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -857,7 +857,146 @@ QString QgsMapLayer::saveNamedStyle( const QString theURI, bool & theResultFlag return myErrorMessage; } +QString QgsMapLayer::saveSldStyle( const QString theURI, bool & theResultFlag ) +{ + QDomDocument myDocument = QDomDocument(); + + QDomNode header = myDocument.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ); + myDocument.appendChild( header ); + + // Create the root element + QDomElement root = myDocument.createElementNS( "http://www.opengis.net/sld", "StyledLayerDescriptor" ); + root.setAttribute( "version", "1.1.0" ); + root.setAttribute( "xsi:schemaLocation", "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" ); + root.setAttribute( "xmlns:ogc", "http://www.opengis.net/ogc" ); + root.setAttribute( "xmlns:se", "http://www.opengis.net/se" ); + root.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" ); + root.setAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" ); + myDocument.appendChild( root ); + + // Create the NamedLayer element + QDomElement namedLayerNode = myDocument.createElement( "NamedLayer" ); + root.appendChild( namedLayerNode ); + + QString errorMsg; + QgsVectorLayer *vlayer = qobject_cast( this ); + if ( !vlayer ) + { + theResultFlag = false; + return tr( "Could not save symbology because:\n%1" ).arg( "Non-vector layers not supported yet" ); + } + + if ( !vlayer->writeSld( namedLayerNode, myDocument, errorMsg ) ) + { + theResultFlag = false; + return tr( "Could not save symbology because:\n%1" ).arg( errorMsg ); + } + + // check if the uri is a file or ends with .sld, + // which indicates that it should become one + QString filename; + if ( vlayer->providerType() == "ogr" ) + { + QStringList theURIParts = theURI.split( "|" ); + filename = theURIParts[0]; + } + else if ( vlayer->providerType() == "delimitedtext" ) + { + filename = QUrl::fromEncoded( theURI.toAscii() ).toLocalFile(); + } + else + { + filename = theURI; + } + + QFileInfo myFileInfo( filename ); + if ( myFileInfo.exists() || filename.endsWith( ".sld", Qt::CaseInsensitive ) ) + { + QFileInfo myDirInfo( myFileInfo.path() ); //excludes file name + if ( !myDirInfo.isWritable() ) + { + return tr( "The directory containing your dataset needs to be writable!" ); + } + + // now construct the file name for our .sld style file + QString myFileName = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + ".sld"; + + QFile myFile( myFileName ); + if ( myFile.open( QFile::WriteOnly | QFile::Truncate ) ) + { + QTextStream myFileStream( &myFile ); + // save as utf-8 with 2 spaces for indents + myDocument.save( myFileStream, 2 ); + myFile.close(); + theResultFlag = true; + return tr( "Created default style file as %1" ).arg( myFileName ); + } + } + theResultFlag = false; + return tr( "ERROR: Failed to created SLD style file as %1. Check file permissions and retry." ).arg( filename ); +} + +QString QgsMapLayer::loadSldStyle( const QString theURI, bool &theResultFlag ) +{ + QgsDebugMsg( "Entered." ); + + theResultFlag = false; + + QDomDocument myDocument; + + // location of problem associated with errorMsg + int line, column; + QString myErrorMessage; + + QFile myFile( theURI ); + if ( myFile.open( QFile::ReadOnly ) ) + { + // read file + theResultFlag = myDocument.setContent( &myFile, true, &myErrorMessage, &line, &column ); + if ( !theResultFlag ) + myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column ); + myFile.close(); + } + else + { + myErrorMessage = tr( "Unable to open file %1" ).arg( theURI ); + } + + if ( !theResultFlag ) + { + return myErrorMessage; + } + + // check for root SLD element + QDomElement myRoot = myDocument.firstChildElement( "StyledLayerDescriptor" ); + if ( myRoot.isNull() ) + { + myErrorMessage = QString( "Error: StyledLayerDescriptor element not found in %1" ).arg( theURI ); + theResultFlag = false; + return myErrorMessage; + } + + // now get the style node out and pass it over to the layer + // to deserialise... + QDomElement namedLayerElem = myRoot.firstChildElement( "NamedLayer" ); + if ( namedLayerElem.isNull() ) + { + myErrorMessage = QString( "Info: NamedLayer element not found." ); + theResultFlag = false; + return myErrorMessage; + } + + QString errorMsg; + theResultFlag = readSld( namedLayerElem, errorMsg ); + if ( !theResultFlag ) + { + myErrorMessage = tr( "Loading style file %1 failed because:\n%2" ).arg( theURI ).arg( errorMsg ); + return myErrorMessage; + } + + return ""; +} QUndoStack* QgsMapLayer::undoStack() diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index 76f964c2f799..eb164559ae35 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -289,6 +289,13 @@ class CORE_EXPORT QgsMapLayer : public QObject */ virtual QString saveNamedStyle( const QString theURI, bool & theResultFlag ); + virtual QString saveSldStyle( const QString theURI, bool & theResultFlag ); + virtual QString loadSldStyle( const QString theURI, bool &theResultFlag ); + + virtual bool readSld( const QDomNode& node, QString& errorMessage ) + { Q_UNUSED( node ); errorMessage = QString( "Layer type %1 not supported" ).arg( type() ); return false; } + + /** Read the symbology for the current layer from the Dom node supplied. * @param node node that will contain the symbology definition for this layer. * @param errorMessage reference to string that will be updated with any error messages diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index cc8a6d8ce498..2c91091f08f6 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -3393,6 +3393,53 @@ bool QgsVectorLayer::writeSymbology( QDomNode& node, QDomDocument& doc, QString& return true; } +bool QgsVectorLayer::readSld( const QDomNode& node, QString& errorMessage ) +{ + // get the Name element + QDomElement nameElem = node.firstChildElement( "Name" ); + if ( nameElem.isNull() ) + { + errorMessage = "Warning: Name element not found within NamedLayer while it's required."; + } + + if ( hasGeometryType() ) + { + setUsingRendererV2( true ); + + QgsFeatureRendererV2* r = QgsFeatureRendererV2::loadSld( node, geometryType(), errorMessage ); + if ( !r ) + return false; + + setRendererV2( r ); + } + return true; +} + + +bool QgsVectorLayer::writeSld( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const +{ + Q_UNUSED( errorMessage ); + + // store the Name element + QDomElement nameNode = doc.createElement( "se:Name" ); + nameNode.appendChild( doc.createTextNode( name() ) ); + node.appendChild( nameNode ); + + if ( hasGeometryType() ) + { + if ( mUsingRendererV2 ) + { + node.appendChild( mRendererV2->writeSld( doc, *this ) ); + } + else + { + node.appendChild( doc.createComment( "Old Renderer not supported yet" ) ); + return false; + } + } + return true; +} + bool QgsVectorLayer::changeGeometry( QgsFeatureId fid, QgsGeometry* geom ) { diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index eca5cfff1eb5..a06c59e2aeae 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -292,6 +292,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer */ bool writeSymbology( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const; + bool writeSld( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const; + bool readSld( const QDomNode& node, QString& errorMessage ); /** * Number of features in the layer. This is necessary if features are diff --git a/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp b/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp index c355cc057c95..ea9d01441b7d 100644 --- a/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp +++ b/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp @@ -68,6 +68,37 @@ QString QgsRendererCategoryV2::dump() return QString( "%1::%2::%3\n" ).arg( mValue.toString() ).arg( mLabel ).arg( mSymbol->dump() ); } +void QgsRendererCategoryV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + if ( !mSymbol || props.value( "attribute", "" ).isEmpty() ) + return; + + QDomElement ruleElem = doc.createElement( "se:Rule" ); + element.appendChild( ruleElem ); + + QString valueStr = QString( "value: %1" ).arg( mValue.toString() ); + + QDomElement nameElem = doc.createElement( "se:Name" ); + nameElem.appendChild( doc.createTextNode( !mLabel.isEmpty() ? mLabel : valueStr ) ); + ruleElem.appendChild( nameElem ); + + QString descrName = props.value( "version", "1.1" ) < "1.1" ? "Abstract" : "se:Description"; + QString descrValue = QString( "Categorized symbol rendererV2 - %1" ).arg( valueStr ); + + QDomElement descrElem = doc.createElement( descrName ); + descrElem.appendChild( doc.createTextNode( descrValue ) ); + ruleElem.appendChild( descrElem ); + + // create the ogc:Filter for the range + QDomElement filterElem = doc.createElement( "ogc:Filter" ); + + QString filterFunc = QString( "%1 = '%2'" ) + .arg( props[ "attribute" ] ).arg( mValue.toString().replace( "'", "''" ) ); + QgsSymbolLayerV2Utils::createFunctionElement( doc, filterElem, filterFunc ); + + mSymbol->toSld( doc, ruleElem, props ); +} + /////////////////// QgsCategorizedSymbolRendererV2::QgsCategorizedSymbolRendererV2( QString attrName, QgsCategoryList categories ) @@ -331,6 +362,23 @@ QgsFeatureRendererV2* QgsCategorizedSymbolRendererV2::clone() return r; } +void QgsCategorizedSymbolRendererV2::toSld( QDomDocument &doc, QDomElement &element ) const +{ + QgsStringMap props; + props[ "attribute" ] = mAttrName; + if ( !mRotationField.isEmpty() ) + props[ "angle" ] = QString( mRotationField ).append( "\"" ).prepend( "\"" ); + if ( !mSizeScaleField.isEmpty() ) + props[ "scale" ] = QString( mSizeScaleField ).append( "\"" ).prepend( "\"" ); + + // create a Rule for each range + for ( QgsCategoryList::const_iterator it = mCategories.constBegin(); it != mCategories.constEnd(); it++ ) + { + QgsStringMap catProps( props ); + it->toSld( doc, element, catProps ); + } +} + QgsSymbolV2List QgsCategorizedSymbolRendererV2::symbols() { QgsSymbolV2List lst; diff --git a/src/core/symbology-ng/qgscategorizedsymbolrendererv2.h b/src/core/symbology-ng/qgscategorizedsymbolrendererv2.h index d9068edc75d3..706be9ae729f 100644 --- a/src/core/symbology-ng/qgscategorizedsymbolrendererv2.h +++ b/src/core/symbology-ng/qgscategorizedsymbolrendererv2.h @@ -1,6 +1,7 @@ #ifndef QGSCATEGORIZEDSYMBOLRENDERERV2_H #define QGSCATEGORIZEDSYMBOLRENDERERV2_H +#include "qgssymbolv2.h" #include "qgsrendererv2.h" #include @@ -32,6 +33,8 @@ class CORE_EXPORT QgsRendererCategoryV2 // debugging QString dump(); + void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ) const; + protected: QVariant mValue; QgsSymbolV2* mSymbol; @@ -60,6 +63,8 @@ class CORE_EXPORT QgsCategorizedSymbolRendererV2 : public QgsFeatureRendererV2 virtual QgsFeatureRendererV2* clone(); + virtual void toSld( QDomDocument& doc, QDomElement &element ) const; + //! returns bitwise OR-ed capabilities of the renderer //! \note added in 2.0 virtual int capabilities() { return SymbolLevels | RotationField; } diff --git a/src/core/symbology-ng/qgsellipsesymbollayerv2.cpp b/src/core/symbology-ng/qgsellipsesymbollayerv2.cpp index 8f7c515dc595..ef029bf10103 100644 --- a/src/core/symbology-ng/qgsellipsesymbollayerv2.cpp +++ b/src/core/symbology-ng/qgsellipsesymbollayerv2.cpp @@ -2,8 +2,12 @@ #include "qgsfeature.h" #include "qgsrendercontext.h" #include "qgsvectorlayer.h" +#include "qgslogger.h" + #include #include +#include +#include QgsEllipseSymbolLayerV2::QgsEllipseSymbolLayerV2(): mSymbolName( "circle" ), mSymbolWidth( 4 ), mSymbolHeight( 3 ), mFillColor( Qt::black ), mOutlineColor( Qt::white ), mOutlineWidth( 0 ) @@ -187,6 +191,115 @@ QgsSymbolLayerV2* QgsEllipseSymbolLayerV2::clone() const return QgsEllipseSymbolLayerV2::create( properties() ); } +void QgsEllipseSymbolLayerV2::toSld(QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + QDomElement symbolizerElem = doc.createElement( "se:PointSymbolizer" ); + if ( !props.value( "uom", "" ).isEmpty() ) + symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) ); + element.appendChild( symbolizerElem ); + + // + QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) ); + + writeSldMarker( doc, symbolizerElem, props ); +} + +void QgsEllipseSymbolLayerV2::writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + // + QDomElement graphicElem = doc.createElement( "se:Graphic" ); + element.appendChild( graphicElem ); + + QgsSymbolLayerV2Utils::wellKnownMarkerToSld( doc, graphicElem, mSymbolName, mFillColor, mOutlineColor, mOutlineWidth, mSymbolWidth ); + + // store w/h factor in a + double widthHeightFactor = mSymbolWidth / mSymbolHeight; + QDomElement factorElem = QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "widthHeightFactor", QString::number( widthHeightFactor ) ); + graphicElem.appendChild( factorElem ); + + // + QString angleFunc = props.value( "angle", "" ); + if ( angleFunc.isEmpty() ) // symbol has no angle set + { + if ( !mRotationField.isEmpty() ) + angleFunc = mRotationField; + else if ( !doubleNear( mAngle, 0.0 ) ) + angleFunc = QString::number( mAngle ); + } + else if ( !mRotationField.isEmpty() ) + { + // the symbol has an angle and the symbol layer have a rotation + // property set + angleFunc = QString( "%1 + %2" ).arg( angleFunc ).arg( mRotationField ); + } + else if ( !doubleNear( mAngle, 0.0 ) ) + { + // both the symbol and the symbol layer have angle value set + bool ok; + double angle = angleFunc.toDouble( &ok ); + if ( !ok ) + { + // its a string (probably a property name or a function) + angleFunc = QString( "%1 + %2" ).arg( angleFunc ).arg( mAngle ); + } + else if ( !doubleNear( angle + mAngle, 0.0 ) ) + { + // it's a double value + angleFunc = QString::number( angle + mAngle ); + } + } + QgsSymbolLayerV2Utils::createRotationElement( doc, graphicElem, angleFunc ); +} + +QgsSymbolLayerV2* QgsEllipseSymbolLayerV2::createFromSld( QDomElement &element ) +{ + QgsDebugMsg( "Entered." ); + + QDomElement graphicElem = element.firstChildElement( "Graphic" ); + if( graphicElem.isNull() ) + return NULL; + + QString name = "circle"; + QColor color, borderColor; + double borderWidth, size; + double widthHeightFactor = 1.0; + + QgsStringMap vendorOptions = QgsSymbolLayerV2Utils::getVendorOptionList( graphicElem ); + for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it ) + { + if ( it.key() == "widthHeightFactor" ) + { + bool ok; + double v = it.value().toDouble( &ok ); + if ( ok && !doubleNear( v, 0.0 ) && v > 0 ) + widthHeightFactor = v; + } + } + + if ( !QgsSymbolLayerV2Utils::wellKnownMarkerFromSld( graphicElem, name, color, borderColor, borderWidth, size ) ) + return NULL; + + double angle = 0.0; + QString angleFunc; + if ( QgsSymbolLayerV2Utils::rotationFromSldElement( graphicElem, angleFunc ) ) + { + bool ok; + double d = angleFunc.toDouble( &ok ); + if ( ok ) + angle = d; + } + + QgsEllipseSymbolLayerV2 *m = new QgsEllipseSymbolLayerV2(); + m->setSymbolName( name ); + m->setColor( color ); + m->setOutlineColor( borderColor ); + m->setOutlineWidth( borderWidth ); + m->setSymbolWidth( size ); + m->setSymbolHeight( size / widthHeightFactor ); + m->setAngle( angle ); + return m; +} + QgsStringMap QgsEllipseSymbolLayerV2::properties() const { QgsStringMap map; diff --git a/src/core/symbology-ng/qgsellipsesymbollayerv2.h b/src/core/symbology-ng/qgsellipsesymbollayerv2.h index c234e3bb67e6..8552f0bedb0f 100644 --- a/src/core/symbology-ng/qgsellipsesymbollayerv2.h +++ b/src/core/symbology-ng/qgsellipsesymbollayerv2.h @@ -12,6 +12,7 @@ class CORE_EXPORT QgsEllipseSymbolLayerV2: public QgsMarkerSymbolLayerV2 ~QgsEllipseSymbolLayerV2(); static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); void renderPoint( const QPointF& point, QgsSymbolV2RenderContext& context ); QString layerType() const; @@ -20,6 +21,9 @@ class CORE_EXPORT QgsEllipseSymbolLayerV2: public QgsMarkerSymbolLayerV2 QgsSymbolLayerV2* clone() const; QgsStringMap properties() const; + void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ) const; + void writeSldMarker( QDomDocument& doc, QDomElement &element, QgsStringMap props ) const; + void setSymbolName( const QString& name ) { mSymbolName = name; } QString symbolName() const { return mSymbolName; } diff --git a/src/core/symbology-ng/qgsfillsymbollayerv2.cpp b/src/core/symbology-ng/qgsfillsymbollayerv2.cpp index df309fcad031..a85816d7be1f 100644 --- a/src/core/symbology-ng/qgsfillsymbollayerv2.cpp +++ b/src/core/symbology-ng/qgsfillsymbollayerv2.cpp @@ -5,10 +5,13 @@ #include "qgsrendercontext.h" #include "qgsproject.h" #include "qgssvgcache.h" +#include "qgslogger.h" #include #include #include +#include +#include QgsSimpleFillSymbolLayerV2::QgsSimpleFillSymbolLayerV2( QColor color, Qt::BrushStyle style, QColor borderColor, Qt::PenStyle borderStyle, double borderWidth ) : mBrushStyle( style ), mBorderColor( borderColor ), mBorderStyle( borderStyle ), mBorderWidth( borderWidth ) @@ -117,6 +120,63 @@ QgsSymbolLayerV2* QgsSimpleFillSymbolLayerV2::clone() const return sl; } +void QgsSimpleFillSymbolLayerV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + if ( mBrushStyle == Qt::NoBrush && mBorderStyle == Qt::NoPen ) + return; + + QDomElement symbolizerElem = doc.createElement( "se:PolygonSymbolizer" ); + if ( !props.value( "uom", "" ).isEmpty() ) + symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) ); + element.appendChild( symbolizerElem ); + + // + QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) ); + + if ( mBrushStyle != Qt::NoBrush ) + { + // + QDomElement fillElem = doc.createElement( "se:Fill" ); + symbolizerElem.appendChild( fillElem ); + QgsSymbolLayerV2Utils::fillToSld( doc, fillElem, mBrushStyle, mColor ); + } + + if ( mBorderStyle != Qt::NoPen ) + { + // + QDomElement strokeElem = doc.createElement( "se:Stroke" ); + symbolizerElem.appendChild( strokeElem ); + QgsSymbolLayerV2Utils::lineToSld( doc, strokeElem, mBorderStyle, mBorderColor, mBorderWidth ); + } + + // + QgsSymbolLayerV2Utils::createDisplacementElement( doc, symbolizerElem, mOffset ); +} + +QgsSymbolLayerV2* QgsSimpleFillSymbolLayerV2::createFromSld( QDomElement &element ) +{ + QgsDebugMsg( "Entered." ); + + QColor color, borderColor; + Qt::BrushStyle fillStyle; + Qt::PenStyle borderStyle; + double borderWidth; + + QDomElement fillElem = element.firstChildElement( "Fill" ); + QgsSymbolLayerV2Utils::fillFromSld( fillElem, fillStyle, color ); + + QDomElement strokeElem = element.firstChildElement( "Stroke" ); + QgsSymbolLayerV2Utils::lineFromSld( strokeElem, borderStyle, borderColor, borderWidth ); + + QPointF offset; + QgsSymbolLayerV2Utils::displacementFromSldElement( element, offset ); + + QgsSimpleFillSymbolLayerV2* sl = new QgsSimpleFillSymbolLayerV2( color, fillStyle, borderColor, borderStyle, borderWidth ); + sl->setOffset( offset ); + return sl; +} + + //QgsImageFillSymbolLayer QgsImageFillSymbolLayer::QgsImageFillSymbolLayer(): mOutlineWidth( 0.0 ), mOutline( 0 ) @@ -380,6 +440,123 @@ QgsSymbolLayerV2* QgsSVGFillSymbolLayer::clone() const return clonedLayer; } +void QgsSVGFillSymbolLayer::toSld(QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + QDomElement symbolizerElem = doc.createElement( "se:PolygonSymbolizer" ); + if ( !props.value( "uom", "" ).isEmpty() ) + symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) ); + element.appendChild( symbolizerElem ); + + QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) ); + + QDomElement fillElem = doc.createElement( "se:Fill" ); + symbolizerElem.appendChild( fillElem ); + + QDomElement graphicFillElem = doc.createElement( "se:GraphicFill" ); + fillElem.appendChild( graphicFillElem ); + + QDomElement graphicElem = doc.createElement( "se:Graphic" ); + graphicFillElem.appendChild( graphicElem ); + + if ( !mSvgFilePath.isEmpty() ) + { + QgsSymbolLayerV2Utils::externalGraphicToSld( doc, graphicElem, mSvgFilePath, "image/svg+xml", mSvgFillColor, mPatternWidth ); + } + else + { + // TODO: create svg from data + // + symbolizerElem.appendChild( doc.createComment( "SVG from data not implemented yet" ) ); + } + + if ( mSvgOutlineColor.isValid() || mSvgOutlineWidth >= 0 ) + { + QgsSymbolLayerV2Utils::lineToSld( doc, graphicElem, Qt::SolidLine, mSvgOutlineColor, mSvgOutlineWidth ); + } + + // + QString angleFunc; + bool ok; + double angle = props.value( "angle", "0" ).toDouble( &ok ); + if ( !ok ) + { + angleFunc = QString( "%1 + %2" ).arg( props.value( "angle", "0" ) ).arg( mAngle ); + } + else if ( angle + mAngle != 0 ) + { + angleFunc = QString::number( angle + mAngle ); + } + QgsSymbolLayerV2Utils::createRotationElement( doc, graphicElem, angleFunc ); + + if ( mOutline ) + { + // the outline sub symbol should be stored within the Stroke element, + // but it will be stored in a separated LineSymbolizer because it could + // have more than one layer + mOutline->toSld( doc, element, props ); + } +} + +QgsSymbolLayerV2* QgsSVGFillSymbolLayer::createFromSld( QDomElement &element ) +{ + QgsDebugMsg( "Entered." ); + + QString path, mimeType; + QColor fillColor, borderColor; + Qt::PenStyle penStyle; + double size, borderWidth; + + QDomElement fillElem = element.firstChildElement( "Fill" ); + if( fillElem.isNull() ) + return NULL; + + QDomElement graphicFillElem = fillElem.firstChildElement( "GraphicFill" ); + if( graphicFillElem.isNull() ) + return NULL; + + QDomElement graphicElem = graphicFillElem.firstChildElement( "Graphic" ); + if( graphicElem.isNull() ) + return NULL; + + if ( !QgsSymbolLayerV2Utils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) ) + return NULL; + + if ( mimeType != "image/svg+xml" ) + return NULL; + + QgsSymbolLayerV2Utils::lineFromSld( graphicElem, penStyle, borderColor, borderWidth ); + + double angle = 0.0; + QString angleFunc; + if ( QgsSymbolLayerV2Utils::rotationFromSldElement( graphicElem, angleFunc ) ) + { + bool ok; + double d = angleFunc.toDouble( &ok ); + if ( ok ) + angle = d; + } + + QgsSVGFillSymbolLayer* sl = new QgsSVGFillSymbolLayer( path, size, angle ); + sl->setSvgFillColor( fillColor ); + sl->setSvgOutlineColor( borderColor ); + sl->setSvgOutlineWidth( borderWidth ); + + // try to get the outline + QDomElement strokeElem = element.firstChildElement( "Stroke" ); + if ( !strokeElem.isNull() ) + { + QgsSymbolLayerV2 *l = QgsSymbolLayerV2Utils::createLineLayerFromSld( strokeElem ); + if ( l ) + { + QgsSymbolLayerV2List layers; + layers.append( l ); + sl->setSubSymbol( new QgsLineSymbolV2( layers ) ); + } + } + + return sl; +} + void QgsSVGFillSymbolLayer::storeViewBox() { if ( !mSvgData.isEmpty() ) @@ -630,6 +807,52 @@ QgsSymbolLayerV2* QgsLinePatternFillSymbolLayer::clone() const return clonedLayer; } +void QgsLinePatternFillSymbolLayer::toSld(QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + QDomElement symbolizerElem = doc.createElement( "se:PolygonSymbolizer" ); + if ( !props.value( "uom", "" ).isEmpty() ) + symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) ); + element.appendChild( symbolizerElem ); + + // + QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) ); + + QDomElement fillElem = doc.createElement( "se:Fill" ); + symbolizerElem.appendChild( fillElem ); + + QDomElement graphicFillElem = doc.createElement( "se:GraphicFill" ); + fillElem.appendChild( graphicFillElem ); + + QDomElement graphicElem = doc.createElement( "se:Graphic" ); + graphicFillElem.appendChild( graphicElem ); + + QgsSymbolLayerV2Utils::wellKnownMarkerToSld( doc, graphicElem, "horline", QColor(), mColor, mLineWidth, mDistance ); + + // + QString angleFunc; + bool ok; + double angle = props.value( "angle", "0" ).toDouble( &ok ); + if ( !ok ) + { + angleFunc = QString( "%1 + %2" ).arg( props.value( "angle", "0" ) ).arg( mLineAngle ); + } + else if ( angle + mLineAngle != 0 ) + { + angleFunc = QString::number( angle + mLineAngle ); + } + QgsSymbolLayerV2Utils::createRotationElement( doc, graphicElem, angleFunc ); + + // + QPointF lineOffset( qSin( mLineAngle ) * mOffset, qCos( mLineAngle ) * mOffset ); + QgsSymbolLayerV2Utils::createDisplacementElement( doc, graphicElem, lineOffset ); +} + +QgsSymbolLayerV2* QgsLinePatternFillSymbolLayer::createFromSld( QDomElement &element ) +{ + Q_UNUSED( element ); + return NULL; +} + //////////////////////// QgsPointPatternFillSymbolLayer::QgsPointPatternFillSymbolLayer(): QgsImageFillSymbolLayer(), mMarkerSymbol( 0 ), mDistanceX( 15 ), @@ -765,6 +988,49 @@ QgsSymbolLayerV2* QgsPointPatternFillSymbolLayer::clone() const return clonedLayer; } +void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + for ( int i = 0; i < mMarkerSymbol->symbolLayerCount(); i++ ) + { + QDomElement symbolizerElem = doc.createElement( "se:PolygonSymbolizer" ); + if ( !props.value( "uom", "" ).isEmpty() ) + symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) ); + element.appendChild( symbolizerElem ); + + // + QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) ); + + QDomElement fillElem = doc.createElement( "se:Fill" ); + symbolizerElem.appendChild( fillElem ); + + QDomElement graphicFillElem = doc.createElement( "se:GraphicFill" ); + fillElem.appendChild( graphicFillElem ); + + // store distanceX, distanceY, displacementX, displacementY in a + QString dist = QgsSymbolLayerV2Utils::encodePoint( QPointF( mDistanceX, mDistanceY ) ); + QDomElement distanceElem = QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "distance", dist ); + symbolizerElem.appendChild( distanceElem ); + + QgsSymbolLayerV2 *layer = mMarkerSymbol->symbolLayer( i ); + QgsMarkerSymbolLayerV2 *markerLayer = static_cast( layer ); + if ( !markerLayer ) + { + QString errorMsg = QString( "MarkerSymbolLayerV2 expected, %1 found. Skip it." ).arg( layer->layerType() ); + graphicFillElem.appendChild( doc.createComment( errorMsg ) ); + } + else + { + markerLayer->writeSldMarker( doc, graphicFillElem, props ); + } + } +} + +QgsSymbolLayerV2* QgsPointPatternFillSymbolLayer::createFromSld( QDomElement &element ) +{ + Q_UNUSED( element ); + return NULL; +} + bool QgsPointPatternFillSymbolLayer::setSubSymbol( QgsSymbolV2* symbol ) { if ( !symbol ) @@ -859,6 +1125,31 @@ QgsSymbolLayerV2* QgsCentroidFillSymbolLayerV2::clone() const return x; } +void QgsCentroidFillSymbolLayerV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + // SLD 1.0 specs says: "if a line, polygon, or raster geometry is + // used with PointSymbolizer, then the semantic is to use the centroid + // of the geometry, or any similar representative point. + mMarker->toSld( doc, element, props ); +} + +QgsSymbolLayerV2* QgsCentroidFillSymbolLayerV2::createFromSld( QDomElement &element ) +{ + QgsDebugMsg( "Entered." ); + + QgsSymbolLayerV2 *l = QgsSymbolLayerV2Utils::createMarkerLayerFromSld( element ); + if ( !l ) + return NULL; + + QgsSymbolLayerV2List layers; + layers.append( l ); + QgsMarkerSymbolV2 *marker = new QgsMarkerSymbolV2( layers ); + + QgsCentroidFillSymbolLayerV2* x = new QgsCentroidFillSymbolLayerV2(); + x->setSubSymbol( marker ); + return x; +} + QgsSymbolV2* QgsCentroidFillSymbolLayerV2::subSymbol() { diff --git a/src/core/symbology-ng/qgsfillsymbollayerv2.h b/src/core/symbology-ng/qgsfillsymbollayerv2.h index 022d0310985e..2adad7de9fbb 100644 --- a/src/core/symbology-ng/qgsfillsymbollayerv2.h +++ b/src/core/symbology-ng/qgsfillsymbollayerv2.h @@ -25,6 +25,7 @@ class CORE_EXPORT QgsSimpleFillSymbolLayerV2 : public QgsFillSymbolLayerV2 // static stuff static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); // implemented from base classes @@ -40,6 +41,8 @@ class CORE_EXPORT QgsSimpleFillSymbolLayerV2 : public QgsFillSymbolLayerV2 QgsSymbolLayerV2* clone() const; + void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + Qt::BrushStyle brushStyle() const { return mBrushStyle; } void setBrushStyle( Qt::BrushStyle style ) { mBrushStyle = style; } @@ -97,6 +100,7 @@ class CORE_EXPORT QgsSVGFillSymbolLayer: public QgsImageFillSymbolLayer ~QgsSVGFillSymbolLayer(); static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); // implemented from base classes @@ -109,6 +113,8 @@ class CORE_EXPORT QgsSVGFillSymbolLayer: public QgsImageFillSymbolLayer QgsSymbolLayerV2* clone() const; + void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + //getters and setters void setSvgFilePath( const QString& svgPath ); QString svgFilePath() const { return mSvgFilePath; } @@ -151,6 +157,7 @@ class CORE_EXPORT QgsLinePatternFillSymbolLayer: public QgsImageFillSymbolLayer ~QgsLinePatternFillSymbolLayer(); static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); QString layerType() const; @@ -162,6 +169,8 @@ class CORE_EXPORT QgsLinePatternFillSymbolLayer: public QgsImageFillSymbolLayer QgsSymbolLayerV2* clone() const; + void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + //getters and setters void setLineAngle( double a ) { mLineAngle = a; } double lineAngle() const { return mLineAngle; } @@ -193,6 +202,8 @@ class CORE_EXPORT QgsPointPatternFillSymbolLayer: public QgsImageFillSymbolLayer ~QgsPointPatternFillSymbolLayer(); static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); + QString layerType() const; void startRender( QgsSymbolV2RenderContext& context ); @@ -203,6 +214,8 @@ class CORE_EXPORT QgsPointPatternFillSymbolLayer: public QgsImageFillSymbolLayer QgsSymbolLayerV2* clone() const; + void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + //getters and setters double distanceX() const { return mDistanceX; } void setDistanceX( double d ) { mDistanceX = d; } @@ -236,6 +249,7 @@ class CORE_EXPORT QgsCentroidFillSymbolLayerV2 : public QgsFillSymbolLayerV2 // static stuff static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); // implemented from base classes @@ -251,6 +265,8 @@ class CORE_EXPORT QgsCentroidFillSymbolLayerV2 : public QgsFillSymbolLayerV2 QgsSymbolLayerV2* clone() const; + void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + void setColor( const QColor& color ); QgsSymbolV2* subSymbol(); diff --git a/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp b/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp index 1f7691d1dfee..190c95b43ce5 100644 --- a/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp +++ b/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp @@ -98,6 +98,38 @@ QString QgsRendererRangeV2::dump() return QString( "%1 - %2::%3::%4\n" ).arg( mLowerValue ).arg( mUpperValue ).arg( mLabel ).arg( mSymbol->dump() ); } +void QgsRendererRangeV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + if ( !mSymbol || props.value( "attribute", "" ).isEmpty() ) + return; + + QDomElement ruleElem = doc.createElement( "se:Rule" ); + element.appendChild( ruleElem ); + + QString valueStr = QString( "range: %1 - %2" ).arg( mLowerValue ).arg( mUpperValue ); + + QDomElement nameElem = doc.createElement( "se:Name" ); + nameElem.appendChild( doc.createTextNode( !mLabel.isEmpty() ? mLabel : valueStr ) ); + ruleElem.appendChild( nameElem ); + + QString descrName = props.value( "version", "1.1" ) < "1.1" ? "Abstract" : "se:Description"; + QString descrValue = QString( "Graduated symbol rendererV2 - %1" ).arg( valueStr ); + + QDomElement descrElem = doc.createElement( descrName ); + descrElem.appendChild( doc.createTextNode( descrValue ) ); + ruleElem.appendChild( descrElem ); + + // create the ogc:Filter for the range + QDomElement filterElem = doc.createElement( "ogc:Filter" ); + + QString filterFunc = QString( "%1 > %2 AND %1 <= %3" ) + .arg( props[ "attribute" ] ) + .arg( mLowerValue ).arg( mUpperValue ); + QgsSymbolLayerV2Utils::createFunctionElement( doc, filterElem, filterFunc ); + + mSymbol->toSld( doc, ruleElem, props ); +} + /////////// QgsGraduatedSymbolRendererV2::QgsGraduatedSymbolRendererV2( QString attrName, QgsRangeList ranges ) @@ -302,6 +334,23 @@ QgsFeatureRendererV2* QgsGraduatedSymbolRendererV2::clone() return r; } +void QgsGraduatedSymbolRendererV2::toSld( QDomDocument& doc, QDomElement &element ) const +{ + QgsStringMap props; + props[ "attribute" ] = mAttrName; + if ( !mRotationField.isEmpty() ) + props[ "angle" ] = QString( mRotationField ).append( "\"" ).prepend( "\"" ); + if ( !mSizeScaleField.isEmpty() ) + props[ "scale" ] = QString( mSizeScaleField ).append( "\"" ).prepend( "\"" ); + + // create a Rule for each range + for ( QgsRangeList::const_iterator it = mRanges.constBegin(); it != mRanges.constEnd(); it++ ) + { + QgsStringMap catProps( props ); + it->toSld( doc, element, catProps ); + } +} + QgsSymbolV2List QgsGraduatedSymbolRendererV2::symbols() { QgsSymbolV2List lst; diff --git a/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h b/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h index de0f4c6af0f5..a10da2b25617 100644 --- a/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h +++ b/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.h @@ -1,6 +1,7 @@ #ifndef QGSGRADUATEDSYMBOLRENDERERV2_H #define QGSGRADUATEDSYMBOLRENDERERV2_H +#include "qgssymbolv2.h" #include "qgsrendererv2.h" class CORE_EXPORT QgsRendererRangeV2 @@ -25,6 +26,8 @@ class CORE_EXPORT QgsRendererRangeV2 // debugging QString dump(); + void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ) const; + protected: double mLowerValue, mUpperValue; QgsSymbolV2* mSymbol; @@ -55,6 +58,8 @@ class CORE_EXPORT QgsGraduatedSymbolRendererV2 : public QgsFeatureRendererV2 virtual QgsFeatureRendererV2* clone(); + virtual void toSld( QDomDocument& doc, QDomElement &element ) const; + //! returns bitwise OR-ed capabilities of the renderer //! \note added in 2.0 virtual int capabilities() { return SymbolLevels | RotationField; } diff --git a/src/core/symbology-ng/qgslinesymbollayerv2.cpp b/src/core/symbology-ng/qgslinesymbollayerv2.cpp index ff6ef8b9a078..b4ec8e0029cb 100644 --- a/src/core/symbology-ng/qgslinesymbollayerv2.cpp +++ b/src/core/symbology-ng/qgslinesymbollayerv2.cpp @@ -3,8 +3,11 @@ #include "qgssymbollayerv2utils.h" #include "qgsrendercontext.h" +#include "qgslogger.h" #include +#include +#include #include @@ -148,6 +151,77 @@ QgsSymbolLayerV2* QgsSimpleLineSymbolLayerV2::clone() const return l; } +void QgsSimpleLineSymbolLayerV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + if ( mPenStyle == Qt::NoPen ) + return; + + QDomElement symbolizerElem = doc.createElement( "se:LineSymbolizer" ); + if ( !props.value( "uom", "" ).isEmpty() ) + symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) ); + element.appendChild( symbolizerElem ); + + // + QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) ); + + // + QDomElement strokeElem = doc.createElement( "se:Stroke" ); + symbolizerElem.appendChild( strokeElem ); + + Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle; + QgsSymbolLayerV2Utils::lineToSld( doc, strokeElem, penStyle, mColor, mWidth, + &mPenJoinStyle, &mPenCapStyle, &mCustomDashVector ); + + // + if ( mOffset != 0 ) + { + QDomElement perpOffsetElem = doc.createElement( "se:PerpendicularOffset" ); + perpOffsetElem.appendChild( doc.createTextNode( QString::number( mOffset ) ) ); + symbolizerElem.appendChild( perpOffsetElem ); + } +} + +QgsSymbolLayerV2* QgsSimpleLineSymbolLayerV2::createFromSld( QDomElement &element ) +{ + QgsDebugMsg( "Entered." ); + + QDomElement strokeElem = element.firstChildElement( "Stroke" ); + if ( strokeElem.isNull() ) + return NULL; + + Qt::PenStyle penStyle; + QColor color; + double width; + Qt::PenJoinStyle penJoinStyle; + Qt::PenCapStyle penCapStyle; + QVector customDashVector; + + if ( !QgsSymbolLayerV2Utils::lineFromSld( strokeElem, penStyle, + color, width, + &penJoinStyle, &penCapStyle, + &customDashVector ) ) + return NULL; + + double offset = 0.0; + QDomElement perpOffsetElem = element.firstChildElement( "PerpendicularOffset" ); + if ( !perpOffsetElem.isNull() ) + { + bool ok; + double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok ); + if ( ok ) + offset = d; + } + + QgsSimpleLineSymbolLayerV2* l = new QgsSimpleLineSymbolLayerV2( color, width, penStyle ); + l->setOffset( offset ); + l->setPenJoinStyle( penJoinStyle ); + l->setPenCapStyle( penCapStyle ); + l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine ); + l->setCustomDashVector( customDashVector ); + return l; +} + + ///////// @@ -574,6 +648,154 @@ QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::clone() const return x; } +void QgsMarkerLineSymbolLayerV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + for ( int i = 0; i < mMarker->symbolLayerCount(); i++ ) + { + QDomElement symbolizerElem = doc.createElement( "se:LineSymbolizer" ); + if ( !props.value( "uom", "" ).isEmpty() ) + symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) ); + element.appendChild( symbolizerElem ); + + // + QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) ); + + QString gap; + switch( mPlacement ) + { + case FirstVertex: + symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "firstPoint" ) ); + break; + case LastVertex: + symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "lastPoint" ) ); + break; + case CentralPoint: + symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "centralPoint" ) ); + break; + case Vertex: + // no way to get line/polygon's vertices, use a VendorOption + symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "points" ) ); + break; + default: + gap = QString::number( mInterval ); + break; + } + + if( !mRotateMarker ) + { + // markers in LineSymbolizer must be drawn following the line orientation, + // use a VendorOption when no marker rotation + symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "rotateMarker", "0" ) ); + } + + // + QDomElement strokeElem = doc.createElement( "se:Stroke" ); + symbolizerElem.appendChild( strokeElem ); + + // + QDomElement graphicStrokeElem = doc.createElement( "se:GraphicStroke" ); + strokeElem.appendChild( graphicStrokeElem ); + + QgsSymbolLayerV2 *layer = mMarker->symbolLayer( i ); + QgsMarkerSymbolLayerV2 *markerLayer = static_cast( layer ); + if ( !markerLayer ) + { + graphicStrokeElem.appendChild( doc.createComment( QString( "MarkerSymbolLayerV2 expected, %1 found. Skip it." ).arg( markerLayer->layerType() ) ) ); + } + else + { + markerLayer->writeSldMarker( doc, graphicStrokeElem, props ); + } + + if ( !gap.isEmpty() ) + { + QDomElement gapElem = doc.createElement( "se:Gap" ); + QgsSymbolLayerV2Utils::createFunctionElement( doc, gapElem, gap ); + graphicStrokeElem.appendChild( gapElem ); + } + + if ( !doubleNear( mOffset, 0.0 ) ) + { + QDomElement perpOffsetElem = doc.createElement( "se:PerpendicularOffset" ); + perpOffsetElem.appendChild( doc.createTextNode( QString::number( mOffset ) ) ); + symbolizerElem.appendChild( perpOffsetElem ); + } + } +} + +QgsSymbolLayerV2* QgsMarkerLineSymbolLayerV2::createFromSld( QDomElement &element ) +{ + QgsDebugMsg( "Entered." ); + + QDomElement strokeElem = element.firstChildElement( "Stroke" ); + if ( strokeElem.isNull() ) + return NULL; + + QDomElement graphicStrokeElem = strokeElem.firstChildElement( "GraphicStroke" ); + if ( graphicStrokeElem.isNull() ) + return NULL; + + // retrieve vendor options + bool rotateMarker = true; + Placement placement = Interval; + + QgsStringMap vendorOptions = QgsSymbolLayerV2Utils::getVendorOptionList( element ); + for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it ) + { + if ( it.key() == "placement" ) + { + if ( it.value() == "points" ) placement = Vertex; + else if ( it.value() == "firstPoint" ) placement = FirstVertex; + else if ( it.value() == "lastPoint" ) placement = LastVertex; + else if ( it.value() == "centralPoint" ) placement = CentralPoint; + } + else if ( it.value() == "rotateMarker" ) + { + rotateMarker = it.value() == "0"; + } + } + + QgsMarkerSymbolV2 *marker = 0; + + QgsSymbolLayerV2 *l = QgsSymbolLayerV2Utils::createMarkerLayerFromSld( graphicStrokeElem ); + if ( l ) + { + QgsSymbolLayerV2List layers; + layers.append( l ); + marker = new QgsMarkerSymbolV2( layers ); + } + + double interval = 0.0; + QDomElement gapElem = element.firstChildElement( "Gap" ); + if ( !gapElem.isNull() ) + { + bool ok; + double d = gapElem.firstChild().nodeValue().toDouble( &ok ); + if ( ok ) + interval = d; + } + + double offset = 0.0; + QDomElement perpOffsetElem = element.firstChildElement( "PerpendicularOffset" ); + if ( !perpOffsetElem.isNull() ) + { + bool ok; + double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok ); + if ( ok ) + offset = d; + } + + QgsMarkerLineSymbolLayerV2* x = new QgsMarkerLineSymbolLayerV2( rotateMarker ); + x->setPlacement( placement ); + if ( !doubleNear( interval, 0.0 ) && interval > 0 ) + x->setInterval( interval ); + if ( marker ) + x->setSubSymbol( marker ); + if ( !doubleNear( offset, 0.0 ) ) + x->setOffset( offset ); + return x; +} + void QgsMarkerLineSymbolLayerV2::setWidth( double width ) { mMarker->setSize( width ); @@ -683,3 +905,34 @@ QgsSymbolLayerV2* QgsLineDecorationSymbolLayerV2::clone() const { return new QgsLineDecorationSymbolLayerV2( mColor, mWidth ); } + +void QgsLineDecorationSymbolLayerV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + QDomElement symbolizerElem = doc.createElement( "se:LineSymbolizer" ); + if ( !props.value( "uom", "" ).isEmpty() ) + symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) ); + element.appendChild( symbolizerElem ); + + QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom" , "" ) ); + + // + QDomElement strokeElem = doc.createElement( "se:Stroke" ); + symbolizerElem.appendChild( strokeElem ); + + // + QDomElement graphicStrokeElem = doc.createElement( "se:GraphicStroke" ); + strokeElem.appendChild( graphicStrokeElem ); + + // + QDomElement graphicElem = doc.createElement( "se:Graphic" ); + graphicStrokeElem.appendChild( graphicElem ); + + // + QgsSymbolLayerV2Utils::wellKnownMarkerToSld( doc, graphicElem, "arrowhead", QColor(), mColor, mWidth, mWidth*8 ); + + // + QgsSymbolLayerV2Utils::createRotationElement( doc, graphicElem, props.value( "angle", "" ) ); + + // use to draw the decoration at end of the line + symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "lastPoint" ) ); +} diff --git a/src/core/symbology-ng/qgslinesymbollayerv2.h b/src/core/symbology-ng/qgslinesymbollayerv2.h index 8d99357e053c..d7502c6967c4 100644 --- a/src/core/symbology-ng/qgslinesymbollayerv2.h +++ b/src/core/symbology-ng/qgslinesymbollayerv2.h @@ -24,6 +24,7 @@ class CORE_EXPORT QgsSimpleLineSymbolLayerV2 : public QgsLineSymbolLayerV2 // static stuff static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); // implemented from base classes @@ -39,6 +40,8 @@ class CORE_EXPORT QgsSimpleLineSymbolLayerV2 : public QgsLineSymbolLayerV2 QgsSymbolLayerV2* clone() const; + void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + // new stuff Qt::PenStyle penStyle() const { return mPenStyle; } @@ -97,6 +100,7 @@ class CORE_EXPORT QgsMarkerLineSymbolLayerV2 : public QgsLineSymbolLayerV2 // static stuff static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); // implemented from base classes @@ -112,6 +116,8 @@ class CORE_EXPORT QgsMarkerLineSymbolLayerV2 : public QgsLineSymbolLayerV2 QgsSymbolLayerV2* clone() const; + void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + void setColor( const QColor& color ); QgsSymbolV2* subSymbol(); @@ -163,6 +169,7 @@ class CORE_EXPORT QgsLineDecorationSymbolLayerV2 : public QgsLineSymbolLayerV2 // static stuff static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); // implemented from base classes @@ -178,6 +185,8 @@ class CORE_EXPORT QgsLineDecorationSymbolLayerV2 : public QgsLineSymbolLayerV2 QgsSymbolLayerV2* clone() const; + void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + protected: QPen mPen; QPen mSelPen; diff --git a/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp b/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp index 31df9bf4cbd3..69fa68351982 100644 --- a/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp +++ b/src/core/symbology-ng/qgsmarkersymbollayerv2.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include @@ -34,6 +36,8 @@ static QPointF _rotatedOffset( const QPointF& offset, double angle ) QgsSimpleMarkerSymbolLayerV2::QgsSimpleMarkerSymbolLayerV2( QString name, QColor color, QColor borderColor, double size, double angle ) { + if ( name == "rectangle" ) + name = "square"; mName = name; mColor = color; mBorderColor = borderColor; @@ -222,7 +226,7 @@ bool QgsSimpleMarkerSymbolLayerV2::prepareShape() { mPolygon.clear(); - if ( mName == "rectangle" ) + if ( mName == "square" ) { mPolygon = QPolygonF( QRectF( QPointF( -1, -1 ), QPointF( 1, 1 ) ) ); return true; @@ -324,7 +328,7 @@ bool QgsSimpleMarkerSymbolLayerV2::preparePath() mPath.lineTo( 0, 1 ); // vertical return true; } - else if ( mName == "cross2" ) + else if ( mName == "x" || mName == "cross2" ) { mPath.moveTo( -1, -1 ); mPath.lineTo( 1, 1 ); @@ -426,6 +430,66 @@ QgsSymbolLayerV2* QgsSimpleMarkerSymbolLayerV2::clone() const return m; } +void QgsSimpleMarkerSymbolLayerV2::writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + // + QDomElement graphicElem = doc.createElement( "se:Graphic" ); + element.appendChild( graphicElem ); + + QgsSymbolLayerV2Utils::wellKnownMarkerToSld( doc, graphicElem, mName, mColor, mBorderColor, -1, mSize ); + + // + QString angleFunc; + bool ok; + double angle = props.value( "angle", "0" ).toDouble( &ok ); + if ( !ok ) + { + angleFunc = QString( "%1 + %2" ).arg( props.value( "angle", "0" ) ).arg( mAngle ); + } + else if ( angle + mAngle != 0 ) + { + angleFunc = QString::number( angle + mAngle ); + } + QgsSymbolLayerV2Utils::createRotationElement( doc, graphicElem, angleFunc ); + + // + QgsSymbolLayerV2Utils::createDisplacementElement( doc, graphicElem, mOffset ); +} + +QgsSymbolLayerV2* QgsSimpleMarkerSymbolLayerV2::createFromSld( QDomElement &element ) +{ + QgsDebugMsg( "Entered." ); + + QDomElement graphicElem = element.firstChildElement( "Graphic" ); + if( graphicElem.isNull() ) + return NULL; + + QString name = "square"; + QColor color, borderColor; + double borderWidth, size; + + if ( !QgsSymbolLayerV2Utils::wellKnownMarkerFromSld( graphicElem, name, color, borderColor, borderWidth, size ) ) + return NULL; + + double angle = 0.0; + QString angleFunc; + if ( QgsSymbolLayerV2Utils::rotationFromSldElement( graphicElem, angleFunc ) ) + { + bool ok; + double d = angleFunc.toDouble( &ok ); + if ( ok ) + angle = d; + } + + QPointF offset; + QgsSymbolLayerV2Utils::displacementFromSldElement( graphicElem, offset ); + + QgsMarkerSymbolLayerV2 *m = new QgsSimpleMarkerSymbolLayerV2( name, color, borderColor, size ); + m->setAngle( angle ); + m->setOffset( offset ); + return m; +} + void QgsSimpleMarkerSymbolLayerV2::drawMarker( QPainter* p, QgsSymbolV2RenderContext& context ) { Q_UNUSED( context ); @@ -627,6 +691,73 @@ QgsSymbolLayerV2* QgsSvgMarkerSymbolLayerV2::clone() const return m; } +void QgsSvgMarkerSymbolLayerV2::writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + // + QDomElement graphicElem = doc.createElement( "se:Graphic" ); + element.appendChild( graphicElem ); + + QgsSymbolLayerV2Utils::externalGraphicToSld( doc, graphicElem, mPath, "image/svg+xml", mFillColor, mSize ); + + // + QString angleFunc; + bool ok; + double angle = props.value( "angle", "0" ).toDouble( &ok ); + if ( !ok ) + { + angleFunc = QString( "%1 + %2" ).arg( props.value( "angle", "0" ) ).arg( mAngle ); + } + else if ( angle + mAngle != 0 ) + { + angleFunc = QString::number( angle + mAngle ); + } + + QgsSymbolLayerV2Utils::createRotationElement( doc, graphicElem, angleFunc ); + + // + QgsSymbolLayerV2Utils::createDisplacementElement( doc, graphicElem, mOffset ); +} + +QgsSymbolLayerV2* QgsSvgMarkerSymbolLayerV2::createFromSld( QDomElement &element ) +{ + QgsDebugMsg( "Entered." ); + + QDomElement graphicElem = element.firstChildElement( "Graphic" ); + if( graphicElem.isNull() ) + return NULL; + + QString path, mimeType; + QColor fillColor; + double size; + + if ( !QgsSymbolLayerV2Utils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) ) + return NULL; + + if ( mimeType != "image/svg+xml" ) + return NULL; + + double angle = 0.0; + QString angleFunc; + if ( QgsSymbolLayerV2Utils::rotationFromSldElement( graphicElem, angleFunc ) ) + { + bool ok; + double d = angleFunc.toDouble( &ok ); + if ( ok ) + angle = d; + } + + QPointF offset; + QgsSymbolLayerV2Utils::displacementFromSldElement( graphicElem, offset ); + + QgsSvgMarkerSymbolLayerV2* m = new QgsSvgMarkerSymbolLayerV2( path, size ); + m->setFillColor( fillColor ); + //m->setOutlineColor( outlineColor ); + //m->setOutlineWidth( outlineWidth ); + m->setAngle( angle ); + m->setOffset( offset ); + return m; +} + QStringList QgsSvgMarkerSymbolLayerV2::listSvgFiles() { @@ -829,3 +960,71 @@ QgsSymbolLayerV2* QgsFontMarkerSymbolLayerV2::clone() const m->setOffset( mOffset ); return m; } + +void QgsFontMarkerSymbolLayerV2::writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + // + QDomElement graphicElem = doc.createElement( "se:Graphic" ); + element.appendChild( graphicElem ); + + QString fontPath = QString( "ttf://%1" ).arg( mFontFamily ); + int markIndex = mChr.unicode(); + QgsSymbolLayerV2Utils::externalMarkerToSld( doc, graphicElem, fontPath, "ttf", &markIndex, mColor, mSize ); + + // + QString angleFunc; + bool ok; + double angle = props.value( "angle", "0" ).toDouble( &ok ); + if ( !ok ) + { + angleFunc = QString( "%1 + %2" ).arg( props.value( "angle", "0" ) ).arg( mAngle ); + } + else if ( angle + mAngle != 0 ) + { + angleFunc = QString::number( angle + mAngle ); + } + QgsSymbolLayerV2Utils::createRotationElement( doc, graphicElem, angleFunc ); + + // + QgsSymbolLayerV2Utils::createDisplacementElement( doc, graphicElem, mOffset ); +} + +QgsSymbolLayerV2* QgsFontMarkerSymbolLayerV2::createFromSld( QDomElement &element ) +{ + QgsDebugMsg( "Entered." ); + + QDomElement graphicElem = element.firstChildElement( "Graphic" ); + if( graphicElem.isNull() ) + return NULL; + + QString name, format; + QColor color; + double size; + int chr; + + if ( !QgsSymbolLayerV2Utils::externalMarkerFromSld( graphicElem, name, format, chr, color, size ) ) + return NULL; + + if ( !name.startsWith( "ttf://" ) || format != "ttf" ) + return NULL; + + QString fontFamily = name.mid( 6 ); + + double angle = 0.0; + QString angleFunc; + if ( QgsSymbolLayerV2Utils::rotationFromSldElement( graphicElem, angleFunc ) ) + { + bool ok; + double d = angleFunc.toDouble( &ok ); + if ( ok ) + angle = d; + } + + QPointF offset; + QgsSymbolLayerV2Utils::displacementFromSldElement( graphicElem, offset ); + + QgsMarkerSymbolLayerV2 *m = new QgsFontMarkerSymbolLayerV2( fontFamily, chr, size, color ); + m->setAngle( angle ); + m->setOffset( offset ); + return m; +} diff --git a/src/core/symbology-ng/qgsmarkersymbollayerv2.h b/src/core/symbology-ng/qgsmarkersymbollayerv2.h index 705721cacc3e..3cbec0a65b00 100644 --- a/src/core/symbology-ng/qgsmarkersymbollayerv2.h +++ b/src/core/symbology-ng/qgsmarkersymbollayerv2.h @@ -28,6 +28,7 @@ class CORE_EXPORT QgsSimpleMarkerSymbolLayerV2 : public QgsMarkerSymbolLayerV2 // static stuff static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); // implemented from base classes @@ -43,6 +44,8 @@ class CORE_EXPORT QgsSimpleMarkerSymbolLayerV2 : public QgsMarkerSymbolLayerV2 QgsSymbolLayerV2* clone() const; + void writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + QString name() const { return mName; } void setName( QString name ) { mName = name; } @@ -87,6 +90,7 @@ class CORE_EXPORT QgsSvgMarkerSymbolLayerV2 : public QgsMarkerSymbolLayerV2 // static stuff static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); //! Return a list of all available svg files static QStringList listSvgFiles(); @@ -111,6 +115,8 @@ class CORE_EXPORT QgsSvgMarkerSymbolLayerV2 : public QgsMarkerSymbolLayerV2 QgsSymbolLayerV2* clone() const; + void writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + QString path() const { return mPath; } void setPath( QString path ); @@ -161,6 +167,7 @@ class CORE_EXPORT QgsFontMarkerSymbolLayerV2 : public QgsMarkerSymbolLayerV2 // static stuff static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); // implemented from base classes @@ -176,6 +183,8 @@ class CORE_EXPORT QgsFontMarkerSymbolLayerV2 : public QgsMarkerSymbolLayerV2 QgsSymbolLayerV2* clone() const; + void writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + // new methods QString fontFamily() const { return mFontFamily; } diff --git a/src/core/symbology-ng/qgspointdisplacementrenderer.cpp b/src/core/symbology-ng/qgspointdisplacementrenderer.cpp index a8227704e5e2..affcddda2b08 100644 --- a/src/core/symbology-ng/qgspointdisplacementrenderer.cpp +++ b/src/core/symbology-ng/qgspointdisplacementrenderer.cpp @@ -22,8 +22,10 @@ #include "qgssymbolv2.h" #include "qgssymbollayerv2utils.h" #include "qgsvectorlayer.h" + #include #include + #include QgsPointDisplacementRenderer::QgsPointDisplacementRenderer( const QString& labelAttributeName ) @@ -66,6 +68,12 @@ QgsFeatureRendererV2* QgsPointDisplacementRenderer::clone() return r; } +void QgsPointDisplacementRenderer::toSld( QDomDocument& doc, QDomElement &element ) const +{ + mRenderer->toSld( doc, element ); +} + + bool QgsPointDisplacementRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker ) { Q_UNUSED( drawVertexMarker ); diff --git a/src/core/symbology-ng/qgspointdisplacementrenderer.h b/src/core/symbology-ng/qgspointdisplacementrenderer.h index a8f9270ca100..856d5197e71d 100644 --- a/src/core/symbology-ng/qgspointdisplacementrenderer.h +++ b/src/core/symbology-ng/qgspointdisplacementrenderer.h @@ -36,6 +36,8 @@ class CORE_EXPORT QgsPointDisplacementRenderer: public QgsFeatureRendererV2 QgsFeatureRendererV2* clone(); + virtual void toSld( QDomDocument& doc, QDomElement &element ) const; + /**Reimplemented from QgsFeatureRendererV2*/ bool renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer = -1, bool selected = false, bool drawVertexMarker = false ); diff --git a/src/core/symbology-ng/qgsrendererv2.cpp b/src/core/symbology-ng/qgsrendererv2.cpp index be5ab2bd0e47..36faa5f361d0 100644 --- a/src/core/symbology-ng/qgsrendererv2.cpp +++ b/src/core/symbology-ng/qgsrendererv2.cpp @@ -372,6 +372,110 @@ QDomElement QgsFeatureRendererV2::save( QDomDocument& doc ) return doc.createElement( RENDERER_TAG_NAME ); } +QgsFeatureRendererV2* QgsFeatureRendererV2::loadSld( const QDomNode &node, QGis::GeometryType geomType, QString &errorMessage ) +{ + QDomElement element = node.toElement(); + if ( element.isNull() ) + return NULL; + + // get the UserStyle element + QDomElement userStyleElem = element.firstChildElement( "UserStyle" ); + if ( userStyleElem.isNull() ) + { + // UserStyle element not found, nothing will be rendered + errorMessage = "Info: UserStyle element not found."; + return NULL; + } + + // get the FeatureTypeStyle element + QDomElement featTypeStyleElem = userStyleElem.firstChildElement( "FeatureTypeStyle" ); + if ( featTypeStyleElem.isNull() ) + { + errorMessage = "Info: FeatureTypeStyle element not found."; + return NULL; + } + + // use the RuleRenderer when more rules are present or the rule + // has filters or min/max scale denominators set, + // otherwise use the SingleSymbol renderer + bool needRuleRenderer = false; + int ruleCount = 0; + + QDomElement ruleElem = featTypeStyleElem.firstChildElement( "Rule" ); + while( !ruleElem.isNull() ) + { + ruleCount++; + + // more rules present, use the RuleRenderer + if ( ruleCount > 1 ) + { + QgsDebugMsg( "more Rule elements found: need a RuleRenderer" ); + needRuleRenderer = true; + break; + } + + QDomElement ruleChildElem = ruleElem.firstChildElement(); + while( !ruleChildElem.isNull() ) + { + // rule has filter or min/max scale denominator, use the RuleRenderer + if ( ruleChildElem.localName() == "Filter" || + ruleChildElem.localName() == "MinScaleDenominator" || + ruleChildElem.localName() == "MaxScaleDenominator" ) + { + QgsDebugMsg( "Filter or Min/MaxScaleDenominator element found: need a RuleRenderer" ); + needRuleRenderer = true; + break; + } + + ruleChildElem = ruleChildElem.nextSiblingElement(); + } + + if ( needRuleRenderer ) + { + break; + } + + ruleElem = ruleElem.nextSiblingElement( "Rule" ); + } + + QString rendererType; + if ( needRuleRenderer ) + { + rendererType = "RuleRenderer"; + } + else + { + rendererType = "singleSymbol"; + } + QgsDebugMsg( QString( "Instantiating a '%1' renderer..." ).arg( rendererType ) ); + + // create the renderer and return it + QgsRendererV2AbstractMetadata* m = QgsRendererV2Registry::instance()->rendererMetadata( rendererType ); + if ( m == NULL ) + { + errorMessage = QString( "Error: Unable to get metadata for '%1' renderer." ).arg( rendererType ); + return NULL; + } + + QgsFeatureRendererV2* r = m->createRendererFromSld( featTypeStyleElem, geomType ); + return r; +} + +QDomElement QgsFeatureRendererV2::writeSld( QDomDocument& doc, const QgsVectorLayer &layer ) const +{ + QDomElement userStyleElem = doc.createElement( "UserStyle" ); + + QDomElement nameElem = doc.createElement( "se:Name" ); + nameElem.appendChild( doc.createTextNode( layer.name() ) ); + userStyleElem.appendChild( nameElem ); + + QDomElement featureTypeStyleElem = doc.createElement( "se:FeatureTypeStyle" ); + toSld( doc, featureTypeStyleElem ); + userStyleElem.appendChild( featureTypeStyleElem ); + + return userStyleElem; +} + QgsLegendSymbologyList QgsFeatureRendererV2::legendSymbologyItems( QSize iconSize ) { Q_UNUSED( iconSize ); diff --git a/src/core/symbology-ng/qgsrendererv2.h b/src/core/symbology-ng/qgsrendererv2.h index 968e8e8f9c1e..4d9f1ef541a0 100644 --- a/src/core/symbology-ng/qgsrendererv2.h +++ b/src/core/symbology-ng/qgsrendererv2.h @@ -9,15 +9,16 @@ #include #include #include - -class QDomDocument; -class QDomElement; +#include +#include class QgsSymbolV2; class QgsRenderContext; class QgsFeature; class QgsVectorLayer; +typedef QMap QgsStringMap; + typedef QList QgsSymbolV2List; typedef QMap QgsSymbolV2Map; @@ -101,6 +102,28 @@ class CORE_EXPORT QgsFeatureRendererV2 //! store renderer info to XML element virtual QDomElement save( QDomDocument& doc ); + //! create the SLD UserStyle element following the SLD v1.1 specs + //! @note added in 1.9 + virtual QDomElement writeSld( QDomDocument& doc, const QgsVectorLayer &layer ) const; + + /** create a new renderer according to the information contained in + * the UserStyle element of a SLD style document + * @param node the node in the SLD document whose the UserStyle element + * is a child + * @param geomType the geometry type of the features, used to convert + * Symbolizer elements + * @param errorMessage it will contain the error message if something + * went wrong + * @return the renderer + * @note added in 1.9 + */ + static QgsFeatureRendererV2* loadSld( const QDomNode &node, QGis::GeometryType geomType, QString &errorMessage ); + + //! used from subclasses to create SLD Rule elements following SLD v1.1 specs + //! @note added in 1.9 + virtual void toSld( QDomDocument& doc, QDomElement &element ) const + { element.appendChild( doc.createComment( QString( "FeatureRendererV2 %1 not implemented yet" ).arg( type() ) ) ); } + //! return a list of symbology items for the legend virtual QgsLegendSymbologyList legendSymbologyItems( QSize iconSize ); @@ -162,4 +185,4 @@ class CORE_EXPORT QgsFeatureRendererV2 }; -#endif +#endif // QGSRENDERERV2_H diff --git a/src/core/symbology-ng/qgsrendererv2registry.cpp b/src/core/symbology-ng/qgsrendererv2registry.cpp index eb4bf3aa85c4..2ae673d2388c 100644 --- a/src/core/symbology-ng/qgsrendererv2registry.cpp +++ b/src/core/symbology-ng/qgsrendererv2registry.cpp @@ -14,7 +14,9 @@ QgsRendererV2Registry::QgsRendererV2Registry() // add default renderers addRenderer( new QgsRendererV2Metadata( "singleSymbol", QObject::tr( "Single Symbol" ), - QgsSingleSymbolRendererV2::create ) ); + QgsSingleSymbolRendererV2::create, + QgsSingleSymbolRendererV2::createFromSld ) ); + addRenderer( new QgsRendererV2Metadata( "categorizedSymbol", QObject::tr( "Categorized" ), QgsCategorizedSymbolRendererV2::create ) ); @@ -24,7 +26,9 @@ QgsRendererV2Registry::QgsRendererV2Registry() addRenderer( new QgsRendererV2Metadata( "RuleRenderer", QObject::tr( "Rule-based" ), - QgsRuleBasedRendererV2::create ) ); + QgsRuleBasedRendererV2::create, + QgsRuleBasedRendererV2::createFromSld ) ); + addRenderer( new QgsRendererV2Metadata( "pointDisplacement", QObject::tr( "Point displacement" ), QgsPointDisplacementRenderer::create ) ); diff --git a/src/core/symbology-ng/qgsrendererv2registry.h b/src/core/symbology-ng/qgsrendererv2registry.h index 535ec4bfd74c..ed274c9acc18 100644 --- a/src/core/symbology-ng/qgsrendererv2registry.h +++ b/src/core/symbology-ng/qgsrendererv2registry.h @@ -5,6 +5,8 @@ #include #include +#include "qgis.h" + class QgsFeatureRendererV2; class QDomElement; class QgsVectorLayer; @@ -37,6 +39,9 @@ class CORE_EXPORT QgsRendererV2AbstractMetadata virtual QgsRendererV2Widget* createRendererWidget( QgsVectorLayer* layer, QgsStyleV2* style, QgsFeatureRendererV2* renderer ) { Q_UNUSED( layer ); Q_UNUSED( style ); Q_UNUSED( renderer ); return NULL; } + virtual QgsFeatureRendererV2* createRendererFromSld( QDomElement& elem, QGis::GeometryType geomType ) + { Q_UNUSED( elem ); Q_UNUSED( geomType ); return NULL; } + protected: //! name used within QGIS for identification (the same what renderer's type() returns) QString mName; @@ -49,6 +54,7 @@ class CORE_EXPORT QgsRendererV2AbstractMetadata typedef QgsFeatureRendererV2*( *QgsRendererV2CreateFunc )( QDomElement& ); typedef QgsRendererV2Widget*( *QgsRendererV2WidgetFunc )( QgsVectorLayer*, QgsStyleV2*, QgsFeatureRendererV2* ); +typedef QgsFeatureRendererV2*( *QgsRendererV2CreateFromSldFunc )( QDomElement&, QGis::GeometryType geomType ); /** Convenience metadata class that uses static functions to create renderer and its widget. @@ -63,14 +69,28 @@ class CORE_EXPORT QgsRendererV2Metadata : public QgsRendererV2AbstractMetadata QgsRendererV2CreateFunc pfCreate, QIcon icon = QIcon(), QgsRendererV2WidgetFunc pfWidget = NULL ) - : QgsRendererV2AbstractMetadata( name, visibleName, icon ), mCreateFunc( pfCreate ), mWidgetFunc( pfWidget ) {} + : QgsRendererV2AbstractMetadata( name, visibleName, icon ), + mCreateFunc( pfCreate ), mWidgetFunc( pfWidget ), mCreateFromSldFunc( NULL ) {} + + QgsRendererV2Metadata( QString name, + QString visibleName, + QgsRendererV2CreateFunc pfCreate, + QgsRendererV2CreateFromSldFunc pfCreateFromSld, + QIcon icon = QIcon(), + QgsRendererV2WidgetFunc pfWidget = NULL ) + : QgsRendererV2AbstractMetadata( name, visibleName, icon ), + mCreateFunc( pfCreate ), mWidgetFunc( pfWidget ), mCreateFromSldFunc( pfCreateFromSld ) {} virtual QgsFeatureRendererV2* createRenderer( QDomElement& elem ) { return mCreateFunc ? mCreateFunc( elem ) : NULL; } virtual QgsRendererV2Widget* createRendererWidget( QgsVectorLayer* layer, QgsStyleV2* style, QgsFeatureRendererV2* renderer ) { return mWidgetFunc ? mWidgetFunc( layer, style, renderer ) : NULL; } + virtual QgsFeatureRendererV2* createRendererFromSld( QDomElement& elem, QGis::GeometryType geomType ) + { return mCreateFromSldFunc ? mCreateFromSldFunc( elem, geomType ) : NULL; } + QgsRendererV2CreateFunc createFunction() const { return mCreateFunc; } QgsRendererV2WidgetFunc widgetFunction() const { return mWidgetFunc; } + QgsRendererV2CreateFromSldFunc createFromSldFunction() const { return mCreateFromSldFunc; } void setWidgetFunction( QgsRendererV2WidgetFunc f ) { mWidgetFunc = f; } @@ -79,6 +99,8 @@ class CORE_EXPORT QgsRendererV2Metadata : public QgsRendererV2AbstractMetadata QgsRendererV2CreateFunc mCreateFunc; //! pointer to function that creates a widget for configuration of renderer's params QgsRendererV2WidgetFunc mWidgetFunc; + //! pointer to function that creates an instance of the renderer from SLD + QgsRendererV2CreateFromSldFunc mCreateFromSldFunc; }; /** diff --git a/src/core/symbology-ng/qgsrulebasedrendererv2.cpp b/src/core/symbology-ng/qgsrulebasedrendererv2.cpp index 665ec9dfeff4..fa75d74abe78 100644 --- a/src/core/symbology-ng/qgsrulebasedrendererv2.cpp +++ b/src/core/symbology-ng/qgsrulebasedrendererv2.cpp @@ -188,6 +188,88 @@ QDomElement QgsRuleBasedRendererV2::Rule::save( QDomDocument& doc, QgsSymbolV2Ma return ruleElem; } +void QgsRuleBasedRendererV2::Rule::toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ) +{ + // do not convert this rule if there are no symbols + if ( symbols().isEmpty() ) + return; + + if ( !mFilterExp.isEmpty() ) + { + if ( !props.value( "filter", "" ).isEmpty() ) + props[ "filter" ] += " AND "; + props[ "filter" ] += mFilterExp; + } + + if ( mScaleMinDenom != 0 ) + { + bool ok; + int parentScaleMinDenom = props.value( "scaleMinDenom", "0" ).toInt( &ok ); + if ( !ok || parentScaleMinDenom <= 0 ) + props[ "scaleMinDenom" ] = QString::number( mScaleMinDenom ); + else + props[ "scaleMinDenom" ] = QString::number( qMax( parentScaleMinDenom, mScaleMinDenom ) ); + } + + if ( mScaleMaxDenom != 0 ) + { + bool ok; + int parentScaleMaxDenom = props.value( "scaleMaxDenom", "0" ).toInt( &ok ); + if ( !ok || parentScaleMaxDenom <= 0 ) + props[ "scaleMaxDenom" ] = QString::number( mScaleMaxDenom ); + else + props[ "scaleMaxDenom" ] = QString::number( qMin( parentScaleMaxDenom, mScaleMaxDenom ) ); + } + + if ( mSymbol ) + { + QDomElement ruleElem = doc.createElement( "se:Rule" ); + element.appendChild( ruleElem ); + + QDomElement nameElem = doc.createElement( "se:Name" ); + nameElem.appendChild( doc.createTextNode( mLabel ) ); + ruleElem.appendChild( nameElem ); + + if ( !mDescription.isEmpty() ) + { + QDomElement descrElem = doc.createElement( "se:Description" ); + QDomElement abstractElem = doc.createElement( "se:Abstract" ); + abstractElem.appendChild( doc.createTextNode( mDescription ) ); + descrElem.appendChild( abstractElem ); + ruleElem.appendChild( descrElem ); + } + + if ( !props.value( "filter", "" ).isEmpty() ) + { + QDomElement filterElem = doc.createElement( "ogc:Filter" ); + QgsSymbolLayerV2Utils::createFunctionElement( doc, filterElem, props.value( "filter", "" ) ); + ruleElem.appendChild( filterElem ); + } + + if ( !props.value( "scaleMinDenom", "" ).isEmpty() ) + { + QDomElement scaleMinDenomElem = doc.createElement( "se:MinScaleDenominator" ); + scaleMinDenomElem.appendChild( doc.createTextNode( props.value( "scaleMinDenom", "" ) ) ); + ruleElem.appendChild( scaleMinDenomElem ); + } + + if ( !props.value( "scaleMaxDenom", "" ).isEmpty() ) + { + QDomElement scaleMaxDenomElem = doc.createElement( "se:MaxScaleDenominator" ); + scaleMaxDenomElem.appendChild( doc.createTextNode( props.value( "scaleMaxDenom", "" ) ) ); + ruleElem.appendChild( scaleMaxDenomElem ); + } + + mSymbol->toSld( doc, ruleElem, props ); + } + + // loop into childern rule list + for ( RuleList::iterator it = mChildren.begin(); it != mChildren.end(); ++it ) + { + ( *it )->toSld( doc, element, props ); + } +} + bool QgsRuleBasedRendererV2::Rule::startRender( QgsRenderContext& context, const QgsVectorLayer *vlayer ) { mActiveChildren.clear(); @@ -377,6 +459,119 @@ QgsRuleBasedRendererV2::Rule* QgsRuleBasedRendererV2::Rule::create( QDomElement& return rule; } +QgsRuleBasedRendererV2::Rule* QgsRuleBasedRendererV2::Rule::createFromSld( QDomElement& ruleElem, QGis::GeometryType geomType ) +{ + if ( ruleElem.localName() != "Rule" ) + { + QgsDebugMsg( QString( "invalid element: Rule element expected, %1 found!" ).arg( ruleElem.tagName() ) ); + return NULL; + } + + QString label, description, filterExp; + int scaleMinDenom = 0, scaleMaxDenom = 0; + QgsSymbolLayerV2List layers; + + // retrieve the Rule element child nodes + QDomElement childElem = ruleElem.firstChildElement(); + while( !childElem.isNull() ) + { + if ( childElem.localName() == "Name" ) + { + label = childElem.firstChild().nodeValue(); + } + else if ( childElem.localName() == "Description" ) + { + // can contains a title and an abstract + // prefer Abstract if available + QDomElement abstractElem = childElem.firstChildElement( "Abstract" ); + QDomElement titleElem = childElem.firstChildElement( "Title" ); + if ( !abstractElem.isNull() ) + { + description = abstractElem.firstChild().nodeValue(); + } + else if ( !titleElem.isNull() && description.isEmpty() ) + { + description = titleElem.firstChild().nodeValue(); + } + } + else if ( childElem.localName() == "Abstract" ) + { + // + description = childElem.firstChild().nodeValue(); + } + else if ( childElem.localName() == "Title" ) + { + // + if ( description.isEmpty() ) + description = childElem.firstChild().nodeValue(); + } + else if ( childElem.localName() == "Filter" ) + { + QgsExpression *filter = QgsExpression::createFromOgcFilter( childElem ); + if ( filter ) + { + if ( filter->hasParserError() ) + { + QgsDebugMsg( "parser error: " + filter->parserErrorString() ); + } + else + { + filterExp = filter->dump(); + } + delete filter; + } + } + else if ( childElem.localName() == "MinScaleDenominator" ) + { + bool ok; + int v = childElem.firstChild().nodeValue().toInt( &ok ); + if ( ok ) + scaleMinDenom = v; + } + else if ( childElem.localName() == "MaxScaleDenominator" ) + { + bool ok; + int v = childElem.firstChild().nodeValue().toInt( &ok ); + if ( ok ) + scaleMaxDenom = v; + } + else if ( childElem.localName().endsWith( "Symbolizer" ) ) + { + // create symbol layers for this symbolizer + QgsSymbolLayerV2Utils::createSymbolLayerV2ListFromSld( childElem, geomType, layers ); + } + + childElem = childElem.nextSiblingElement(); + } + + // now create the symbol + QgsSymbolV2 *symbol = 0; + if ( layers.size() > 0 ) + { + switch( geomType ) + { + case QGis::Line: + symbol = new QgsLineSymbolV2( layers ); + break; + + case QGis::Polygon: + symbol = new QgsFillSymbolV2( layers ); + break; + + case QGis::Point: + symbol = new QgsMarkerSymbolV2( layers ); + break; + + default: + QgsDebugMsg( QString( "invalid geometry type: found %1" ).arg( geomType ) ); + return NULL; + } + } + + // and then create and return the new rule + return new Rule( symbol, scaleMinDenom, scaleMaxDenom, filterExp, label, description ); +} + ///////////////////// @@ -497,6 +692,11 @@ QgsFeatureRendererV2* QgsRuleBasedRendererV2::clone() return r; } +void QgsRuleBasedRendererV2::toSld( QDomDocument& doc, QDomElement &element ) const +{ + mRootRule->toSld( doc, element, QgsStringMap() ); +} + // TODO: ideally this function should be removed in favor of legendSymbol(ogy)Items QgsSymbolV2List QgsRuleBasedRendererV2::symbols() { @@ -521,7 +721,6 @@ QDomElement QgsRuleBasedRendererV2::save( QDomDocument& doc ) return rendererElem; } - QgsLegendSymbologyList QgsRuleBasedRendererV2::legendSymbologyItems( QSize iconSize ) { QgsLegendSymbologyList lst; @@ -564,6 +763,36 @@ QgsFeatureRendererV2* QgsRuleBasedRendererV2::create( QDomElement& element ) return r; } +QgsFeatureRendererV2* QgsRuleBasedRendererV2::createFromSld( QDomElement& element, QGis::GeometryType geomType ) +{ + // retrieve child rules + Rule* root = 0; + + QDomElement ruleElem = element.firstChildElement( "Rule" ); + while ( !ruleElem.isNull() ) + { + Rule *child = Rule::createFromSld( ruleElem, geomType ); + if ( child ) + { + // create the root rule if not done before + if ( !root ) + root = new Rule( 0 ); + + root->appendChild( child ); + } + + ruleElem = ruleElem.nextSiblingElement( "Rule" ); + } + + if ( !root ) + { + // no valid rules was found + return NULL; + } + + // create and return the new renderer + return new QgsRuleBasedRendererV2( root ); +} #include "qgscategorizedsymbolrendererv2.h" #include "qgsgraduatedsymbolrendererv2.h" diff --git a/src/core/symbology-ng/qgsrulebasedrendererv2.h b/src/core/symbology-ng/qgsrulebasedrendererv2.h index 6a773edfb738..c35c01318a3f 100644 --- a/src/core/symbology-ng/qgsrulebasedrendererv2.h +++ b/src/core/symbology-ng/qgsrulebasedrendererv2.h @@ -18,6 +18,7 @@ #include "qgsfield.h" #include "qgsfeature.h" +#include "qgis.h" #include "qgsrendererv2.h" @@ -116,6 +117,9 @@ class CORE_EXPORT QgsRuleBasedRendererV2 : public QgsFeatureRendererV2 //! clone this rule, return new instance Rule* clone() const; + void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ); + static Rule* createFromSld( QDomElement& element, QGis::GeometryType geomType ); + QDomElement save( QDomDocument& doc, QgsSymbolV2Map& symbolMap ); //! prepare the rule for rendering and its children (build active children array) @@ -197,6 +201,10 @@ class CORE_EXPORT QgsRuleBasedRendererV2 : public QgsFeatureRendererV2 virtual QgsFeatureRendererV2* clone(); + virtual void toSld( QDomDocument& doc, QDomElement &element ) const; + + static QgsFeatureRendererV2* createFromSld( QDomElement& element, QGis::GeometryType geomType ); + virtual QgsSymbolV2List symbols(); //! store renderer info to XML element diff --git a/src/core/symbology-ng/qgssinglesymbolrendererv2.cpp b/src/core/symbology-ng/qgssinglesymbolrendererv2.cpp index 7ebc20c97342..d302a85466e7 100644 --- a/src/core/symbology-ng/qgssinglesymbolrendererv2.cpp +++ b/src/core/symbology-ng/qgssinglesymbolrendererv2.cpp @@ -7,6 +7,7 @@ #include "qgslogger.h" #include "qgsfeature.h" #include "qgsvectorlayer.h" +#include "qgssymbollayerv2.h" #include #include @@ -171,6 +172,24 @@ QgsFeatureRendererV2* QgsSingleSymbolRendererV2::clone() return r; } +void QgsSingleSymbolRendererV2::toSld( QDomDocument& doc, QDomElement &element ) const +{ + QgsStringMap props; + if ( !mRotationField.isEmpty() ) + props[ "angle" ] = QString( mRotationField ).append( "\"" ).prepend( "\"" ); + if ( !mSizeScaleField.isEmpty() ) + props[ "scale" ] = QString( mSizeScaleField ).append( "\"" ).prepend( "\"" ); + + QDomElement ruleElem = doc.createElement( "se:Rule" ); + element.appendChild( ruleElem ); + + QDomElement nameElem = doc.createElement( "se:Name" ); + nameElem.appendChild( doc.createTextNode( "Single symbol" ) ); + ruleElem.appendChild( nameElem ); + + mSymbol->toSld( doc, ruleElem, props ); +} + QgsSymbolV2List QgsSingleSymbolRendererV2::symbols() { QgsSymbolV2List lst; @@ -206,6 +225,70 @@ QgsFeatureRendererV2* QgsSingleSymbolRendererV2::create( QDomElement& element ) return r; } +QgsFeatureRendererV2* QgsSingleSymbolRendererV2::createFromSld( QDomElement& element, QGis::GeometryType geomType ) +{ + // XXX this renderer can handle only one Rule! + + // get the first Rule element + QDomElement ruleElem = element.firstChildElement( "Rule" ); + if ( ruleElem.isNull() ) + { + QgsDebugMsg( "no Rule elements found!" ); + return NULL; + } + + QString label, description; + QgsSymbolLayerV2List layers; + + // retrieve the Rule element child nodes + QDomElement childElem = ruleElem.firstChildElement(); + while( !childElem.isNull() ) + { + if ( childElem.localName() == "Name" ) + { + label = childElem.firstChild().nodeValue(); + } + else if ( childElem.localName() == "Description" || childElem.localName() == "Abstract" ) + { + description = childElem.firstChild().nodeValue(); + } + else if ( childElem.localName().endsWith( "Symbolizer" ) ) + { + // create symbol layers for this symbolizer + QgsSymbolLayerV2Utils::createSymbolLayerV2ListFromSld( childElem, geomType, layers ); + } + + childElem = childElem.nextSiblingElement(); + } + + // now create the symbol + QgsSymbolV2 *symbol = 0; + if ( layers.size() > 0 ) + { + switch( geomType ) + { + case QGis::Line: + symbol = new QgsLineSymbolV2( layers ); + break; + + case QGis::Polygon: + symbol = new QgsFillSymbolV2( layers ); + break; + + case QGis::Point: + symbol = new QgsMarkerSymbolV2( layers ); + break; + + default: + QgsDebugMsg( QString( "invalid geometry type: found %1" ).arg( geomType ) ); + return NULL; + } + } + + // and finally return the new renderer + return new QgsSingleSymbolRendererV2( symbol ); +} + QDomElement QgsSingleSymbolRendererV2::save( QDomDocument& doc ) { QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME ); diff --git a/src/core/symbology-ng/qgssinglesymbolrendererv2.h b/src/core/symbology-ng/qgssinglesymbolrendererv2.h index 49052bc62a7a..de83591168ab 100644 --- a/src/core/symbology-ng/qgssinglesymbolrendererv2.h +++ b/src/core/symbology-ng/qgssinglesymbolrendererv2.h @@ -1,6 +1,7 @@ #ifndef QGSSINGLESYMBOLRENDERERV2_H #define QGSSINGLESYMBOLRENDERERV2_H +#include "qgis.h" #include "qgsrendererv2.h" class CORE_EXPORT QgsSingleSymbolRendererV2 : public QgsFeatureRendererV2 @@ -36,6 +37,9 @@ class CORE_EXPORT QgsSingleSymbolRendererV2 : public QgsFeatureRendererV2 virtual QgsFeatureRendererV2* clone(); + virtual void toSld( QDomDocument& doc, QDomElement &element ) const; + static QgsFeatureRendererV2* createFromSld( QDomElement& element, QGis::GeometryType geomType ); + //! returns bitwise OR-ed capabilities of the renderer //! \note added in 2.0 virtual int capabilities() { return SymbolLevels | RotationField; } diff --git a/src/core/symbology-ng/qgssymbollayerv2.cpp b/src/core/symbology-ng/qgssymbollayerv2.cpp index e936ab7e8213..02ee0674a76f 100644 --- a/src/core/symbology-ng/qgssymbollayerv2.cpp +++ b/src/core/symbology-ng/qgssymbollayerv2.cpp @@ -93,3 +93,16 @@ void QgsFillSymbolLayerV2::_renderPolygon( QPainter* p, const QPolygonF& points, p->drawPath( path ); } } + +void QgsMarkerSymbolLayerV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + QDomElement symbolizerElem = doc.createElement( "se:PointSymbolizer" ); + if ( !props.value( "uom", "" ).isEmpty() ) + symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) ); + element.appendChild( symbolizerElem ); + + // + QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) ); + + writeSldMarker( doc, symbolizerElem, props ); +} diff --git a/src/core/symbology-ng/qgssymbollayerv2.h b/src/core/symbology-ng/qgssymbollayerv2.h index 0c798b9009d2..400d47983b86 100644 --- a/src/core/symbology-ng/qgssymbollayerv2.h +++ b/src/core/symbology-ng/qgssymbollayerv2.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "qgssymbolv2.h" @@ -17,8 +19,6 @@ class QSize; class QPolygonF; class QgsRenderContext; -class QgsSymbolV2; - class CORE_EXPORT QgsSymbolLayerV2 { @@ -37,6 +37,9 @@ class CORE_EXPORT QgsSymbolLayerV2 virtual QgsSymbolLayerV2* clone() const = 0; + virtual void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const + { Q_UNUSED( props ); element.appendChild( doc.createComment( QString( "SymbolLayerV2 %1 not implemented yet" ).arg( layerType() ) ) ); } + virtual QgsStringMap properties() const = 0; virtual void drawPreviewIcon( QgsSymbolV2RenderContext& context, QSize size ) = 0; @@ -91,6 +94,11 @@ class CORE_EXPORT QgsMarkerSymbolLayerV2 : public QgsSymbolLayerV2 void setOffset( QPointF offset ) { mOffset = offset; } QPointF offset() { return mOffset; } + virtual void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + + virtual void writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const + { Q_UNUSED( props ); element.appendChild( doc.createComment( QString( "QgsMarkerSymbolLayerV2 %1 not implemented yet" ).arg( layerType() ) ) ); } + protected: QgsMarkerSymbolLayerV2( bool locked = false ); diff --git a/src/core/symbology-ng/qgssymbollayerv2registry.cpp b/src/core/symbology-ng/qgssymbollayerv2registry.cpp index 8c07b392fb45..91eb517ad2eb 100644 --- a/src/core/symbology-ng/qgssymbollayerv2registry.cpp +++ b/src/core/symbology-ng/qgssymbollayerv2registry.cpp @@ -13,33 +13,33 @@ QgsSymbolLayerV2Registry::QgsSymbolLayerV2Registry() { // init registry with known symbol layers addSymbolLayerType( new QgsSymbolLayerV2Metadata( "SimpleLine", QObject::tr( "Simple line" ), QgsSymbolV2::Line, - QgsSimpleLineSymbolLayerV2::create ) ); + QgsSimpleLineSymbolLayerV2::create, QgsSimpleLineSymbolLayerV2::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "MarkerLine", QObject::tr( "Marker line" ), QgsSymbolV2::Line, - QgsMarkerLineSymbolLayerV2::create ) ); + QgsMarkerLineSymbolLayerV2::create, QgsMarkerLineSymbolLayerV2::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "LineDecoration", QObject::tr( "Line decoration" ), QgsSymbolV2::Line, QgsLineDecorationSymbolLayerV2::create ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "SimpleMarker", QObject::tr( "Simple marker" ), QgsSymbolV2::Marker, - QgsSimpleMarkerSymbolLayerV2::create ) ); + QgsSimpleMarkerSymbolLayerV2::create, QgsSimpleMarkerSymbolLayerV2::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "SvgMarker", QObject::tr( "SVG marker" ), QgsSymbolV2::Marker, - QgsSvgMarkerSymbolLayerV2::create ) ); + QgsSvgMarkerSymbolLayerV2::create, QgsSvgMarkerSymbolLayerV2::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "FontMarker", QObject::tr( "Font marker" ), QgsSymbolV2::Marker, - QgsFontMarkerSymbolLayerV2::create ) ); + QgsFontMarkerSymbolLayerV2::create, QgsFontMarkerSymbolLayerV2::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "EllipseMarker", QObject::tr( "Ellipse marker" ), QgsSymbolV2::Marker, - QgsEllipseSymbolLayerV2::create ) ); + QgsEllipseSymbolLayerV2::create, QgsEllipseSymbolLayerV2::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "VectorField", QObject::tr( "Vector Field marker" ), QgsSymbolV2::Marker, QgsVectorFieldSymbolLayer::create ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "SimpleFill", QObject::tr( "Simple fill" ), QgsSymbolV2::Fill, - QgsSimpleFillSymbolLayerV2::create ) ); + QgsSimpleFillSymbolLayerV2::create, QgsSimpleFillSymbolLayerV2::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "SVGFill", QObject::tr( "SVG fill" ), QgsSymbolV2::Fill, - QgsSVGFillSymbolLayer::create ) ); + QgsSVGFillSymbolLayer::create, QgsSVGFillSymbolLayer::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "CentroidFill", QObject::tr( "Centroid fill" ), QgsSymbolV2::Fill, - QgsCentroidFillSymbolLayerV2::create ) ); + QgsCentroidFillSymbolLayerV2::create, QgsCentroidFillSymbolLayerV2::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "LinePatternFill", QObject::tr( "Line pattern fill" ), QgsSymbolV2::Fill, - QgsLinePatternFillSymbolLayer::create ) ); + QgsLinePatternFillSymbolLayer::create, QgsLinePatternFillSymbolLayer::createFromSld ) ); addSymbolLayerType( new QgsSymbolLayerV2Metadata( "PointPatternFill", QObject::tr( "Point pattern fill" ), QgsSymbolV2::Fill, - QgsPointPatternFillSymbolLayer::create ) ); + QgsPointPatternFillSymbolLayer::create, QgsPointPatternFillSymbolLayer::createFromSld ) ); } QgsSymbolLayerV2Registry::~QgsSymbolLayerV2Registry() @@ -101,6 +101,14 @@ QgsSymbolLayerV2* QgsSymbolLayerV2Registry::createSymbolLayer( QString name, con return mMetadata[name]->createSymbolLayer( properties ); } +QgsSymbolLayerV2* QgsSymbolLayerV2Registry::createSymbolLayerFromSld( QString name, QDomElement& element ) const +{ + if ( !mMetadata.contains( name ) ) + return NULL; + + return mMetadata[name]->createSymbolLayerFromSld( element ); +} + QStringList QgsSymbolLayerV2Registry::symbolLayersForType( QgsSymbolV2::SymbolType type ) { QStringList lst; diff --git a/src/core/symbology-ng/qgssymbollayerv2registry.h b/src/core/symbology-ng/qgssymbollayerv2registry.h index d3e7a8a30f71..521718637d48 100644 --- a/src/core/symbology-ng/qgssymbollayerv2registry.h +++ b/src/core/symbology-ng/qgssymbollayerv2registry.h @@ -27,6 +27,9 @@ class CORE_EXPORT QgsSymbolLayerV2AbstractMetadata virtual QgsSymbolLayerV2* createSymbolLayer( const QgsStringMap& map ) = 0; /** create widget for symbol layer of this type. Can return NULL if there's no GUI */ virtual QgsSymbolLayerV2Widget* createSymbolLayerWidget( const QgsVectorLayer * ) { return NULL; } + /** create a symbol layer of this type given the map of properties. */ + virtual QgsSymbolLayerV2* createSymbolLayerFromSld( QDomElement & ) { return NULL; } + protected: QString mName; @@ -36,6 +39,7 @@ class CORE_EXPORT QgsSymbolLayerV2AbstractMetadata typedef QgsSymbolLayerV2*( *QgsSymbolLayerV2CreateFunc )( const QgsStringMap& ); typedef QgsSymbolLayerV2Widget*( *QgsSymbolLayerV2WidgetFunc )( const QgsVectorLayer* ); +typedef QgsSymbolLayerV2*( *QgsSymbolLayerV2CreateFromSldFunc )( QDomElement& ); /** Convenience metadata class that uses static functions to create symbol layer and its widget. @@ -47,19 +51,31 @@ class CORE_EXPORT QgsSymbolLayerV2Metadata : public QgsSymbolLayerV2AbstractMeta QgsSymbolV2::SymbolType type, QgsSymbolLayerV2CreateFunc pfCreate, QgsSymbolLayerV2WidgetFunc pfWidget = NULL ) - : QgsSymbolLayerV2AbstractMetadata( name, visibleName, type ), mCreateFunc( pfCreate ), mWidgetFunc( pfWidget ) {} + : QgsSymbolLayerV2AbstractMetadata( name, visibleName, type ), + mCreateFunc( pfCreate ), mWidgetFunc( pfWidget ), mCreateFromSldFunc( NULL ) {} + + QgsSymbolLayerV2Metadata( QString name, QString visibleName, + QgsSymbolV2::SymbolType type, + QgsSymbolLayerV2CreateFunc pfCreate, + QgsSymbolLayerV2CreateFromSldFunc pfCreateFromSld, + QgsSymbolLayerV2WidgetFunc pfWidget = NULL ) + : QgsSymbolLayerV2AbstractMetadata( name, visibleName, type ), + mCreateFunc( pfCreate ), mWidgetFunc ( pfWidget ), mCreateFromSldFunc( pfCreateFromSld ) {} QgsSymbolLayerV2CreateFunc createFunction() const { return mCreateFunc; } QgsSymbolLayerV2WidgetFunc widgetFunction() const { return mWidgetFunc; } + QgsSymbolLayerV2CreateFromSldFunc createFromSldFunction() const { return mCreateFromSldFunc; } void setWidgetFunction( QgsSymbolLayerV2WidgetFunc f ) { mWidgetFunc = f; } virtual QgsSymbolLayerV2* createSymbolLayer( const QgsStringMap& map ) { return mCreateFunc ? mCreateFunc( map ) : NULL; } virtual QgsSymbolLayerV2Widget* createSymbolLayerWidget( const QgsVectorLayer* vl ) { return mWidgetFunc ? mWidgetFunc( vl ) : NULL; } + virtual QgsSymbolLayerV2* createSymbolLayerFromSld( QDomElement& elem ) { return mCreateFromSldFunc ? mCreateFromSldFunc( elem ) : NULL; } protected: QgsSymbolLayerV2CreateFunc mCreateFunc; QgsSymbolLayerV2WidgetFunc mWidgetFunc; + QgsSymbolLayerV2CreateFromSldFunc mCreateFromSldFunc; }; @@ -83,6 +99,9 @@ class CORE_EXPORT QgsSymbolLayerV2Registry //! create a new instance of symbol layer given symbol layer name and properties QgsSymbolLayerV2* createSymbolLayer( QString name, const QgsStringMap& properties = QgsStringMap() ) const; + //! create a new instance of symbol layer given symbol layer name and SLD + QgsSymbolLayerV2* createSymbolLayerFromSld( QString name, QDomElement &element ) const; + //! return a list of available symbol layers for a specified symbol type QStringList symbolLayersForType( QgsSymbolV2::SymbolType type ); diff --git a/src/core/symbology-ng/qgssymbollayerv2utils.cpp b/src/core/symbology-ng/qgssymbollayerv2utils.cpp index b8ab5bd0b88d..f6da2242f891 100644 --- a/src/core/symbology-ng/qgssymbollayerv2utils.cpp +++ b/src/core/symbology-ng/qgssymbollayerv2utils.cpp @@ -5,11 +5,14 @@ #include "qgssymbollayerv2registry.h" #include "qgssymbolv2.h" #include "qgsvectorcolorrampv2.h" +#include "qgsexpression.h" #include "qgslogger.h" #include "qgsrendercontext.h" #include +#include +#include #include #include #include @@ -39,6 +42,66 @@ QColor QgsSymbolLayerV2Utils::decodeColor( QString str ) return QColor( red, green, blue, alpha ); } +QString QgsSymbolLayerV2Utils::encodeSldAlpha( int alpha ) +{ + return QString::number( alpha / 255.0, 'f', 2 ); +} + +int QgsSymbolLayerV2Utils::decodeSldAlpha( QString str ) +{ + bool ok; + double alpha = str.toDouble( &ok ); + if ( !ok || alpha > 1 ) + alpha = 255; + else if ( alpha < 0 ) + alpha = 0; + return alpha * 255; +} + +QString QgsSymbolLayerV2Utils::encodeSldFontStyle( QFont::Style style ) +{ + switch ( style ) + { + case QFont::StyleNormal: return "normal"; + case QFont::StyleItalic: return "italic"; + case QFont::StyleOblique: return "oblique"; + default: return ""; + } +} + +QFont::Style QgsSymbolLayerV2Utils::decodeSldFontStyle( QString str ) +{ + if ( str == "normal" ) return QFont::StyleNormal; + if ( str == "italic" ) return QFont::StyleItalic; + if ( str == "oblique" ) return QFont::StyleOblique; + return QFont::StyleNormal; +} + +QString QgsSymbolLayerV2Utils::encodeSldFontWeight( int weight ) +{ + if ( weight == 50 ) return "normal"; + if ( weight == 75 ) return "bold"; + + // QFont::Weight is between 0 and 99 + // CSS font-weight is between 100 and 900 + if ( weight < 0 ) return "100"; + if ( weight > 99 ) return "900"; + return QString::number( weight * 800 / 99 + 100 ); +} + +int QgsSymbolLayerV2Utils::decodeSldFontWeight( QString str ) +{ + bool ok; + int weight = str.toInt( &ok ); + if ( !ok ) return ( int ) QFont::Normal; + + // CSS font-weight is between 100 and 900 + // QFont::Weight is between 0 and 99 + if ( weight > 900 ) return 99; + if ( weight < 100 ) return 0; + return ( weight - 100 ) * 99 / 800; +} + QString QgsSymbolLayerV2Utils::encodePenStyle( Qt::PenStyle style ) { switch ( style ) @@ -83,6 +146,25 @@ Qt::PenJoinStyle QgsSymbolLayerV2Utils::decodePenJoinStyle( QString str ) return Qt::BevelJoin; } +QString QgsSymbolLayerV2Utils::encodeSldLineJoinStyle( Qt::PenJoinStyle style ) +{ + switch ( style ) + { + case Qt::BevelJoin: return "bevel"; + case Qt::MiterJoin: return "mitre"; + case Qt::RoundJoin: return "round"; + default: return ""; + } +} + +Qt::PenJoinStyle QgsSymbolLayerV2Utils::decodeSldLineJoinStyle( QString str ) +{ + if ( str == "bevel" ) return Qt::BevelJoin; + if ( str == "mitre" ) return Qt::MiterJoin; + if ( str == "round" ) return Qt::RoundJoin; + return Qt::BevelJoin; +} + QString QgsSymbolLayerV2Utils::encodePenCapStyle( Qt::PenCapStyle style ) { switch ( style ) @@ -102,6 +184,24 @@ Qt::PenCapStyle QgsSymbolLayerV2Utils::decodePenCapStyle( QString str ) return Qt::SquareCap; } +QString QgsSymbolLayerV2Utils::encodeSldLineCapStyle( Qt::PenCapStyle style ) +{ + switch ( style ) + { + case Qt::SquareCap: return "square"; + case Qt::FlatCap: return "butt"; + case Qt::RoundCap: return "round"; + default: return ""; + } +} + +Qt::PenCapStyle QgsSymbolLayerV2Utils::decodeSldLineCapStyle( QString str ) +{ + if ( str == "square" ) return Qt::SquareCap; + if ( str == "butt" ) return Qt::FlatCap; + if ( str == "round" ) return Qt::RoundCap; + return Qt::SquareCap; +} QString QgsSymbolLayerV2Utils::encodeBrushStyle( Qt::BrushStyle style ) { @@ -146,6 +246,52 @@ Qt::BrushStyle QgsSymbolLayerV2Utils::decodeBrushStyle( QString str ) return Qt::SolidPattern; } +QString QgsSymbolLayerV2Utils::encodeSldBrushStyle( Qt::BrushStyle style ) +{ + switch( style ) + { + case Qt::CrossPattern: return "cross"; + case Qt::DiagCrossPattern: return "x"; + + /* The following names are taken from the presentation "GeoServer + * Cartographic Rendering" by Andrea Aime at the FOSS4G 2010. + * (see http://2010.foss4g.org/presentations/3588.pdf) + */ + case Qt::HorPattern: return "horline"; + case Qt::VerPattern: return "line"; + case Qt::BDiagPattern: return "slash"; + case Qt::FDiagPattern: return "backslash"; + + /* define the other names following the same pattern used above */ + case Qt::Dense1Pattern: + case Qt::Dense2Pattern: + case Qt::Dense3Pattern: + case Qt::Dense4Pattern: + case Qt::Dense5Pattern: + case Qt::Dense6Pattern: + case Qt::Dense7Pattern: + return QString( "brush://%1" ).arg( encodeBrushStyle( style ) ); + + default: + return QString(); + } +} + +Qt::BrushStyle QgsSymbolLayerV2Utils::decodeSldBrushStyle( QString str ) +{ + if ( str == "horline" ) return Qt::HorPattern; + if ( str == "line" ) return Qt::VerPattern; + if ( str == "cross" ) return Qt::CrossPattern; + if ( str == "slash" ) return Qt::BDiagPattern; + if ( str == "backshash" ) return Qt::FDiagPattern; + if ( str == "x" ) return Qt::DiagCrossPattern; + + if ( str.startsWith( "brush://" ) ) + return decodeBrushStyle( str.mid( 8 ) ); + + return Qt::NoBrush; +} + QString QgsSymbolLayerV2Utils::encodePoint( QPointF point ) { return QString( "%1,%2" ).arg( point.x() ).arg( point.y() ); @@ -187,6 +333,49 @@ QgsSymbolV2::OutputUnit QgsSymbolLayerV2Utils::decodeOutputUnit( QString str ) return QgsSymbolV2::MM; } +QString QgsSymbolLayerV2Utils::encodeSldUom( QgsSymbolV2::OutputUnit unit, double *scaleFactor ) +{ + switch ( unit ) + { + case QgsSymbolV2::MapUnit: + if ( scaleFactor ) + *scaleFactor = 0.001; // from millimeters to meters + return "http://www.opengeospatial.org/se/units/metre"; + + case QgsSymbolV2::MM: + default: + // pixel is the SLD default uom. The "standardized rendering pixel + // size" is defined to be 0.28mm × 0.28mm (millimeters). + if ( scaleFactor ) + *scaleFactor = 0.28; // from millimeters to pixels + + // http://www.opengeospatial.org/sld/units/pixel + return QString(); + } +} + +QgsSymbolV2::OutputUnit QgsSymbolLayerV2Utils::decodeSldUom( QString str, double *scaleFactor ) +{ + if ( str == "http://www.opengeospatial.org/se/units/metre" ) + { + if ( scaleFactor ) + *scaleFactor = 1000.0; // from meters to millimeters + return QgsSymbolV2::MapUnit; + } + else if ( str == "http://www.opengeospatial.org/se/units/foot" ) + { + if ( scaleFactor ) + *scaleFactor = 304.8; // from feet to meters + return QgsSymbolV2::MapUnit; + } + + // pixel is the SLD default uom. The "standardized rendering pixel + // size" is defined to be 0.28mm x 0.28mm (millimeters). + if ( scaleFactor ) + *scaleFactor = 1/0.00028; // from pixels to millimeters + return QgsSymbolV2::MM; +} + QString QgsSymbolLayerV2Utils::encodeRealVector( const QVector& v ) { QString vectorString; @@ -216,6 +405,35 @@ QVector QgsSymbolLayerV2Utils::decodeRealVector( const QString& s ) return resultVector; } +QString QgsSymbolLayerV2Utils::encodeSldRealVector( const QVector& v ) +{ + QString vectorString; + QVector::const_iterator it = v.constBegin(); + for ( ; it != v.constEnd(); ++it ) + { + if ( it != v.constBegin() ) + { + vectorString.append( " " ); + } + vectorString.append( QString::number( *it ) ); + } + return vectorString; +} + +QVector QgsSymbolLayerV2Utils::decodeSldRealVector( const QString& s ) +{ + QVector resultVector; + + QStringList realList = s.split( " " ); + QStringList::const_iterator it = realList.constBegin(); + for ( ; it != realList.constEnd(); ++it ) + { + resultVector.append( it->toDouble() ); + } + + return resultVector; +} + QIcon QgsSymbolLayerV2Utils::symbolPreviewIcon( QgsSymbolV2* symbol, QSize size ) { return QIcon( symbolPreviewPixmap( symbol, size ) ); @@ -506,6 +724,1412 @@ QDomElement QgsSymbolLayerV2Utils::saveSymbol( QString name, QgsSymbolV2* symbol } +bool QgsSymbolLayerV2Utils::createSymbolLayerV2ListFromSld( QDomElement& element, + QGis::GeometryType geomType, + QgsSymbolLayerV2List &layers ) +{ + QgsDebugMsg( "Entered." ); + + if ( element.isNull() ) + return false; + + QgsSymbolLayerV2 *l = 0; + + QString symbolizerName = element.localName(); + + if ( symbolizerName == "PointSymbolizer" ) + { + // first check for Graphic element, nothing will be rendered if not found + QDomElement graphicElem = element.firstChildElement( "Graphic" ); + if ( graphicElem.isNull() ) + { + QgsDebugMsg( "Graphic element not found in PointSymbolizer" ); + } + else + { + switch( geomType ) + { + case QGis::Polygon: + // polygon layer and point symbolizer: draw poligon centroid + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "CentroidFill", element ); + if ( l ) + layers.append( l ); + + break; + + case QGis::Point: + // point layer and point symbolizer: use markers + l = createMarkerLayerFromSld( element ); + if ( l ) + layers.append( l ); + + break; + + case QGis::Line: + // line layer and point symbolizer: draw central point + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "SimpleMarker", element ); + if ( l ) + layers.append( l ); + + break; + + default: + break; + } + } + } + + if ( symbolizerName == "LineSymbolizer" ) + { + // check for Stroke element, nothing will be rendered if not found + QDomElement strokeElem = element.firstChildElement( "Stroke" ); + if ( strokeElem.isNull() ) + { + QgsDebugMsg( "Stroke element not found in LineSymbolizer" ); + } + else + { + switch( geomType ) + { + case QGis::Polygon: + case QGis::Line: + // polygon layer and line symbolizer: draw polygon outline + // line layer and line symbolizer: draw line + l = createLineLayerFromSld( element ); + if ( l ) + layers.append( l ); + + break; + + case QGis::Point: + // point layer and line symbolizer: draw a little line marker + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "MarkerLine", element ); + if ( l ) + layers.append( l ); + + default: + break; + } + } + } + + if ( symbolizerName == "PolygonSymbolizer" ) + { + // get Fill and Stroke elements, nothing will be rendered if both are missing + QDomElement fillElem = element.firstChildElement( "Fill" ); + QDomElement strokeElem = element.firstChildElement( "Stroke" ); + if ( fillElem.isNull() && strokeElem.isNull() ) + { + QgsDebugMsg( "neither Fill nor Stroke element not found in PolygonSymbolizer" ); + } + else + { + QgsSymbolLayerV2 *l = 0; + + switch( geomType ) + { + case QGis::Polygon: + // polygon layer and polygon symbolizer: draw fill + + l = createFillLayerFromSld( element ); + if ( l ) + { + layers.append( l ); + + // SVGFill and SimpleFill symbolLayerV2 supports outline internally, + // so don't go forward to create a different symbolLayerV2 for outline + if ( l->layerType() == "SimpleFill" || l->layerType() == "SVGFill" ) + break; + } + + // now create polygon outline + // polygon layer and polygon symbolizer: draw polygon outline + l = createLineLayerFromSld( element ); + if ( l ) + layers.append( l ); + + break; + + case QGis::Line: + // line layer and polygon symbolizer: draw line + l = createLineLayerFromSld( element ); + if ( l ) + layers.append( l ); + + break; + + case QGis::Point: + // point layer and polygon symbolizer: draw a square marker + convertPolygonSymbolizerToPointMarker( element, layers ); + break; + + default: + break; + } + } + } + + return true; +} + +QgsSymbolLayerV2* QgsSymbolLayerV2Utils::createFillLayerFromSld( QDomElement &element ) +{ + QDomElement fillElem = element.firstChildElement( "Fill" ); + if ( fillElem.isNull() ) + { + QgsDebugMsg( "Fill element not found" ); + return NULL; + } + + QgsSymbolLayerV2 *l = 0; + + if ( needLinePatternFill( element ) ) + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "LinePatternFill", element ); + else if ( needPointPatternFill( element ) ) + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "PointPatternFill", element ); + else if ( needSvgFill( element ) ) + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "SVGFill", element ); + else + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "SimpleFill", element ); + + return l; +} + +QgsSymbolLayerV2* QgsSymbolLayerV2Utils::createLineLayerFromSld( QDomElement &element ) +{ + QDomElement strokeElem = element.firstChildElement( "Stroke" ); + if ( strokeElem.isNull() ) + { + QgsDebugMsg( "Stroke element not found" ); + return NULL; + } + + QgsSymbolLayerV2 *l = 0; + + if ( needMarkerLine( element ) ) + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "MarkerLine", element ); + else + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "SimpleLine", element ); + + return l; +} + +QgsSymbolLayerV2* QgsSymbolLayerV2Utils::createMarkerLayerFromSld( QDomElement &element ) +{ + QDomElement graphicElem = element.firstChildElement( "Graphic" ); + if ( graphicElem.isNull() ) + { + QgsDebugMsg( "Graphic element not found" ); + return NULL; + } + + QgsSymbolLayerV2 *l = 0; + + if ( needFontMarker( element ) ) + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "FontMarker", element ); + else if ( needSvgMarker( element ) ) + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "SvgMarker", element ); + else if ( needEllipseMarker( element ) ) + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "EllipseMarker", element ); + else + l = QgsSymbolLayerV2Registry::instance()->createSymbolLayerFromSld( "SimpleMarker", element ); + + return l; +} + +bool QgsSymbolLayerV2Utils::hasExternalGraphic( QDomElement &element ) +{ + QDomElement graphicElem = element.firstChildElement( "Graphic" ); + if( graphicElem.isNull() ) + return false; + + QDomElement externalGraphicElem = graphicElem.firstChildElement( "ExternalGraphic" ); + if ( externalGraphicElem.isNull() ) + return false; + + // check for format + QDomElement formatElem = externalGraphicElem.firstChildElement( "Format" ); + if ( formatElem.isNull() ) + return false; + + QString format = formatElem.firstChild().nodeValue(); + if ( format != "image/svg+xml" ) + { + QgsDebugMsg( "unsupported External Graphic format found: " + format ); + return false; + } + + // check for a valid content + QDomElement onlineResourceElem = externalGraphicElem.firstChildElement( "OnlineResource" ); + QDomElement inlineContentElem = externalGraphicElem.firstChildElement( "InlineContent" ); + if ( !onlineResourceElem.isNull() ) + { + return true; + } + else if ( !inlineContentElem.isNull() ) + { + return false; // not implemented yet + } + else + { + return false; + } + + return false; +} + +bool QgsSymbolLayerV2Utils::hasWellKnownMark( QDomElement &element ) +{ + QDomElement graphicElem = element.firstChildElement( "Graphic" ); + if( graphicElem.isNull() ) + return false; + + QDomElement markElem = graphicElem.firstChildElement( "Mark" ); + if ( markElem.isNull() ) + return false; + + QDomElement wellKnownNameElem = markElem.firstChildElement( "WellKnownName" ); + if ( wellKnownNameElem.isNull() ) + return false; + + return true; +} + + +bool QgsSymbolLayerV2Utils::needFontMarker( QDomElement &element ) +{ + QDomElement graphicElem = element.firstChildElement( "Graphic" ); + if( graphicElem.isNull() ) + return false; + + QDomElement markElem = graphicElem.firstChildElement( "Mark" ); + if ( markElem.isNull() ) + return false; + + // check for format + QDomElement formatElem = markElem.firstChildElement( "Format" ); + if ( formatElem.isNull() ) + return false; + + QString format = formatElem.firstChild().nodeValue(); + if ( format != "ttf" ) + { + QgsDebugMsg( "unsupported Graphic Mark format found: " + format ); + return false; + } + + // check for a valid content + QDomElement onlineResourceElem = markElem.firstChildElement( "OnlineResource" ); + QDomElement inlineContentElem = markElem.firstChildElement( "InlineContent" ); + if ( !onlineResourceElem.isNull() ) + { + // mark with ttf format has a markIndex element + QDomElement markIndexElem = markElem.firstChildElement( "MarkIndex" ); + if ( !markIndexElem.isNull() ) + return true; + } + else if ( !inlineContentElem.isNull() ) + { + return false; // not implemented yet + } + + return false; +} + +bool QgsSymbolLayerV2Utils::needSvgMarker( QDomElement &element ) +{ + return hasExternalGraphic( element ); +} + +bool QgsSymbolLayerV2Utils::needEllipseMarker( QDomElement &element ) +{ + QDomElement graphicElem = element.firstChildElement( "Graphic" ); + if( graphicElem.isNull() ) + return false; + + QgsStringMap vendorOptions = QgsSymbolLayerV2Utils::getVendorOptionList( graphicElem ); + for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it ) + { + if ( it.key() == "widthHeightFactor" ) + { + return true; + } + } + + return false; +} + +bool QgsSymbolLayerV2Utils::needMarkerLine( QDomElement &element ) +{ + QDomElement strokeElem = element.firstChildElement( "Stroke" ); + if( strokeElem.isNull() ) + return false; + + QDomElement graphicStrokeElem = strokeElem.firstChildElement( "GraphicStroke" ); + if ( graphicStrokeElem.isNull() ) + return false; + + return hasWellKnownMark( graphicStrokeElem ); +} + +bool QgsSymbolLayerV2Utils::needLinePatternFill( QDomElement &element ) { Q_UNUSED( element ); return false; } +bool QgsSymbolLayerV2Utils::needPointPatternFill( QDomElement &element ) { Q_UNUSED( element ); return false; } + +bool QgsSymbolLayerV2Utils::needSvgFill( QDomElement &element ) +{ + QDomElement fillElem = element.firstChildElement( "Fill" ); + if( fillElem.isNull() ) + return false; + + QDomElement graphicFillElem = fillElem.firstChildElement( "GraphicFill" ); + if ( graphicFillElem.isNull() ) + return false; + + return hasExternalGraphic( graphicFillElem ); +} + + +bool QgsSymbolLayerV2Utils::convertPolygonSymbolizerToPointMarker( QDomElement &element, QgsSymbolLayerV2List &layerList ) +{ + QgsDebugMsg( "Entered." ); + + /* SE 1.1 says about PolygonSymbolizer: + if a point geometry is referenced instead of a polygon, + then a small, square, ortho-normal polygon should be + constructed for rendering. + */ + + QgsSymbolLayerV2List layers; + + // retrieve both Fill and Stroke elements + QDomElement fillElem = element.firstChildElement( "Fill" ); + QDomElement strokeElem = element.firstChildElement( "Stroke" ); + + // first symbol layer + { + bool validFill = false, validBorder = false; + + // check for simple fill + // Fill element can contain some SvgParameter elements + QColor fillColor; + Qt::BrushStyle fillStyle; + + if ( fillFromSld( fillElem, fillStyle, fillColor ) ) + validFill = true; + + // check for simple outline + // Stroke element can contain some SvgParameter elements + QColor borderColor; + Qt::PenStyle borderStyle; + double borderWidth = 1.0, dashOffset = 0.0; + QVector customDashPattern; + + if ( lineFromSld( strokeElem, borderStyle, borderColor, borderWidth, + 0, 0, &customDashPattern, &dashOffset ) ) + validBorder = true; + + if ( validFill || validBorder ) + { + QgsStringMap map; + map["name"] = "square"; + map["color"] = QgsSymbolLayerV2Utils::encodeColor( validFill ? fillColor : Qt::transparent ); + map["color_border"] = QgsSymbolLayerV2Utils::encodeColor( validBorder ? borderColor : Qt::transparent ); + map["size"] = QString::number( 6 ); + map["angle"] = QString::number( 0 ); + map["offset"] = QgsSymbolLayerV2Utils::encodePoint( QPointF( 0, 0 ) ); + layers.append( QgsSymbolLayerV2Registry::instance()->createSymbolLayer( "SimpleMarker", map ) ); + } + } + + // second symbol layer + { + bool validFill = false, validBorder = false; + + // check for graphic fill + QString name, format; + int markIndex = -1; + QColor fillColor, borderColor; + double borderWidth = 1, size, angle = 0.0; + QPointF anchor, offset; + + // Fill element can contain a GraphicFill element + QDomElement graphicFillElem = fillElem.firstChildElement( "GraphicFill" ); + if ( !graphicFillElem.isNull() ) + { + // GraphicFill element must contain a Graphic element + QDomElement graphicElem = graphicFillElem.firstChildElement( "Graphic" ); + if( !graphicElem.isNull() ) + { + // Graphic element can contains some ExternalGraphic and Mark element + // search for the first supported one and use it + bool found = false; + + QDomElement graphicChildElem = graphicElem.firstChildElement(); + while ( !graphicChildElem.isNull() ) + { + if ( graphicChildElem.localName() == "Mark" ) + { + // check for a well known name + QDomElement wellKnownNameElem = graphicChildElem.firstChildElement( "WellKnownName" ); + if ( !wellKnownNameElem.isNull() ) + { + name = wellKnownNameElem.firstChild().nodeValue(); + found = true; + break; + } + } + + if ( graphicChildElem.localName() == "ExternalGraphic" || graphicChildElem.localName() == "Mark" ) + { + // check for external graphic format + QDomElement formatElem = graphicChildElem.firstChildElement( "Format" ); + if ( formatElem.isNull() ) + continue; + + format = formatElem.firstChild().nodeValue(); + + // TODO: remove this check when more formats will be supported + // only SVG external graphics are supported in this moment + if ( graphicChildElem.localName() == "ExternalGraphic" && format != "image/svg+xml" ) + continue; + + // TODO: remove this check when more formats will be supported + // only ttf marks are supported in this moment + if ( graphicChildElem.localName() == "Mark" && format != "ttf" ) + continue; + + // check for a valid content + QDomElement onlineResourceElem = graphicChildElem.firstChildElement( "OnlineResource" ); + QDomElement inlineContentElem = graphicChildElem.firstChildElement( "InlineContent" ); + + if ( !onlineResourceElem.isNull() ) + { + name = onlineResourceElem.attributeNS( "http://www.w3.org/1999/xlink", "href" ); + + if ( graphicChildElem.localName() == "Mark" && format == "ttf" ) + { + // mark with ttf format may have a name like ttf://fontFamily + if ( name.startsWith( "ttf://" ) ) + name = name.mid( 6 ); + + // mark with ttf format has a markIndex element + QDomElement markIndexElem = graphicChildElem.firstChildElement( "MarkIndex" ); + if ( markIndexElem.isNull() ) + continue; + + bool ok; + int v = markIndexElem.firstChild().nodeValue().toInt( &ok ); + if ( !ok || v < 0 ) + continue; + + markIndex = v; + } + + found = true; + break; + } + else if ( !inlineContentElem.isNull() ) + continue; // TODO: not implemeneted yet + else + continue; + } + + // if Mark element is present but it doesn't contains neither + // WellKnownName nor OnlineResource nor InlineContent, + // use the default mark (square) + if ( graphicChildElem.localName() == "Mark" ) + { + name = "square"; + found = true; + break; + } + } + + // if found a valid Mark, check for its Fill and Stroke element + if ( found && graphicChildElem.localName() == "Mark" ) + { + // XXX: recursive definition!?! couldn't be dangerous??? + // to avoid recursion we handle only simple fill and simple stroke + + // check for simple fill + // Fill element can contain some SvgParameter elements + Qt::BrushStyle markFillStyle; + + QDomElement markFillElem = graphicChildElem.firstChildElement( "Fill" ); + if ( fillFromSld( markFillElem, markFillStyle, fillColor ) ) + validFill = true; + + // check for simple outline + // Stroke element can contain some SvgParameter elements + Qt::PenStyle borderStyle; + double borderWidth = 1.0, dashOffset = 0.0; + QVector customDashPattern; + + QDomElement markStrokeElem = graphicChildElem.firstChildElement( "Stroke" ); + if ( lineFromSld( markStrokeElem, borderStyle, borderColor, borderWidth, + 0, 0, &customDashPattern, &dashOffset ) ) + validBorder = true; + } + + if ( found ) + { + // check for Opacity, Size, Rotation, AnchorPoint, Displacement + QDomElement opacityElem = graphicElem.firstChildElement( "Opacity" ); + if ( !opacityElem.isNull() ) + fillColor.setAlpha( decodeSldAlpha( opacityElem.firstChild().nodeValue() ) ); + + QDomElement sizeElem = graphicElem.firstChildElement( "Size" ); + if ( !sizeElem.isNull() ) + { + bool ok; + double v = sizeElem.firstChild().nodeValue().toDouble( &ok ); + if ( ok && v > 0 ) + size = v; + } + + QString angleFunc; + if ( rotationFromSldElement( graphicElem, angleFunc ) && !angleFunc.isEmpty() ) + { + bool ok; + double v = angleFunc.toDouble( &ok ); + if ( ok ) + angle = v; + } + + displacementFromSldElement( graphicElem, offset ); + } + } + } + + if ( validFill || validBorder ) + { + if ( format == "image/svg+xml" ) + { + QgsStringMap map; + map["name"] = name; + map["fill"] = fillColor.name(); + map["outline"] = borderColor.name(); + map["outline-width"] = QString::number( borderWidth ); + if ( size > 0 ) + map["size"] = QString::number( size ); + if ( !doubleNear( angle, 0.0 ) ) + map["angle"] = QString::number( angle ); + if ( !offset.isNull() ) + map["offset"] = QgsSymbolLayerV2Utils::encodePoint( offset ); + layers.append( QgsSymbolLayerV2Registry::instance()->createSymbolLayer( "SvgMarker", map ) ); + } + else if ( format == "ttf" ) + { + QgsStringMap map; + map["font"] = name; + map["chr"] = markIndex; + map["color"] = QgsSymbolLayerV2Utils::encodeColor( validFill ? fillColor : Qt::transparent ); + if ( size > 0 ) + map["size"] = QString::number( size ); + if ( !doubleNear( angle, 0.0 ) ) + map["angle"] = QString::number( angle ); + if ( !offset.isNull() ) + map["offset"] = QgsSymbolLayerV2Utils::encodePoint( offset ); + layers.append( QgsSymbolLayerV2Registry::instance()->createSymbolLayer( "FontMarker", map ) ); + } + } + } + + if ( layers.isEmpty() ) + return false; + + layerList << layers; + layers.clear(); + return true; +} + +void QgsSymbolLayerV2Utils::fillToSld( QDomDocument &doc, QDomElement &element, Qt::BrushStyle brushStyle, QColor color ) +{ + QString patternName; + switch ( brushStyle ) + { + case Qt::NoBrush: + return; + + case Qt::SolidPattern: + if ( color.isValid() ) + { + element.appendChild( createSvgParameterElement( doc, "fill", color.name() ) ); + if ( color.alpha() < 255 ) + element.appendChild( createSvgParameterElement( doc, "fill-opacity", encodeSldAlpha( color.alpha() ) ) ); + } + return; + + case Qt::CrossPattern: + case Qt::DiagCrossPattern: + case Qt::HorPattern: + case Qt::VerPattern: + case Qt::BDiagPattern: + case Qt::FDiagPattern: + case Qt::Dense1Pattern: + case Qt::Dense2Pattern: + case Qt::Dense3Pattern: + case Qt::Dense4Pattern: + case Qt::Dense5Pattern: + case Qt::Dense6Pattern: + case Qt::Dense7Pattern: + patternName = encodeSldBrushStyle( brushStyle ); + break; + + default: + element.appendChild( doc.createComment( QString( "Qt::BrushStyle '%1'' not supported yet" ).arg( brushStyle ) ) ); + return; + } + + QDomElement graphicFillElem = doc.createElement( "se:GraphicFill" ); + element.appendChild( graphicFillElem ); + + QDomElement graphicElem = doc.createElement( "se:Graphic" ); + graphicFillElem.appendChild( graphicElem ); + + QColor fillColor = patternName.startsWith( "brush://" ) ? color : QColor(); + QColor borderColor = !patternName.startsWith( "brush://" ) ? color : QColor(); + + /* Use WellKnownName tag to handle QT brush styles. */ + wellKnownMarkerToSld( doc, graphicFillElem, patternName, fillColor, borderColor ); +} + +bool QgsSymbolLayerV2Utils::fillFromSld( QDomElement &element, Qt::BrushStyle &brushStyle, QColor &color ) +{ + QgsDebugMsg( "Entered." ); + + brushStyle = Qt::SolidPattern; + color = QColor( "#808080" ); + + if ( element.isNull() ) + { + brushStyle = Qt::NoBrush; + color = QColor(); + return true; + } + + QDomElement graphicFillElem = element.firstChildElement( "GraphicFill" ); + // if no GraphicFill element is found, it's a solid fill + if ( graphicFillElem.isNull() ) + { + QgsStringMap svgParams = getSvgParameterList( element ); + for ( QgsStringMap::iterator it = svgParams.begin(); it != svgParams.end(); ++it ) + { + QgsDebugMsg( QString( "found SvgParameter %1: %2" ).arg( it.key() ).arg( it.value() ) ); + + if ( it.key() == "fill" ) + color = QColor( it.value() ); + else if ( it.key() == "fill-opacity" ) + color.setAlpha( decodeSldAlpha( it.value() ) ); + } + } + else // wellKnown marker + { + QDomElement graphicElem = graphicFillElem.firstChildElement( "Graphic" ); + if ( graphicElem.isNull() ) + return false; // Graphic is required within GraphicFill + + QString patternName = "square"; + QColor fillColor, borderColor; + double borderWidth, size; + if ( !wellKnownMarkerFromSld( graphicFillElem, patternName, fillColor, borderColor, borderWidth, size ) ) + return false; + + brushStyle = decodeSldBrushStyle( patternName ); + if ( brushStyle == Qt::NoBrush ) + return false; // unable to decode brush style + + QColor c = patternName.startsWith( "brush://" ) ? fillColor : borderColor; + if ( c.isValid() ) + color = c; + } + + return true; +} + +void QgsSymbolLayerV2Utils::lineToSld( QDomDocument &doc, QDomElement &element, + Qt::PenStyle penStyle, QColor color, double width, + const Qt::PenJoinStyle *penJoinStyle, const Qt::PenCapStyle *penCapStyle, + const QVector *customDashPattern, double dashOffset ) +{ + QVector dashPattern; + if ( penStyle == Qt::CustomDashLine && !customDashPattern ) + { + element.appendChild( doc.createComment( "WARNING: Custom dash pattern required but not provided. Using default dash pattern." ) ); + penStyle = Qt::DashLine; + customDashPattern = &dashPattern; + } + + switch ( penStyle ) + { + case Qt::NoPen: + return; + + case Qt::SolidLine: + break; + + case Qt::DashLine: + dashPattern.push_back( 4.0 ); + dashPattern.push_back( 2.0 ); + break; + case Qt::DotLine: + dashPattern.push_back( 1.0 ); + dashPattern.push_back( 2.0 ); + break; + case Qt::DashDotLine: + dashPattern.push_back( 4.0 ); + dashPattern.push_back( 2.0 ); + dashPattern.push_back( 1.0 ); + dashPattern.push_back( 2.0 ); + break; + case Qt::DashDotDotLine: + dashPattern.push_back( 4.0 ); + dashPattern.push_back( 2.0 ); + dashPattern.push_back( 1.0 ); + dashPattern.push_back( 2.0 ); + dashPattern.push_back( 1.0 ); + dashPattern.push_back( 2.0 ); + break; + + case Qt::CustomDashLine: + Q_ASSERT( customDashPattern ); + break; + + default: + element.appendChild( doc.createComment( QString( "Qt::BrushStyle '%1'' not supported yet" ).arg( penStyle ) ) ); + return; + } + + if ( color.isValid() ) + { + element.appendChild( createSvgParameterElement( doc, "stroke", color.name() ) ); + if ( color.alpha() < 255 ) + element.appendChild( createSvgParameterElement( doc, "stroke-opacity", encodeSldAlpha( color.alpha() ) ) ); + } + if ( width > 0 ) + element.appendChild( createSvgParameterElement( doc, "stroke-width", QString::number( width ) ) ); + if ( penJoinStyle ) + element.appendChild( createSvgParameterElement( doc, "stroke-linejoin", encodeSldLineJoinStyle( *penJoinStyle ) ) ); + if ( penCapStyle ) + element.appendChild( createSvgParameterElement( doc, "stroke-linecap", encodeSldLineCapStyle( *penCapStyle ) ) ); + + if ( customDashPattern && customDashPattern->size() > 0 ) + { + element.appendChild( createSvgParameterElement( doc, "stroke-dasharray", encodeSldRealVector( *customDashPattern ) ) ); + if ( !doubleNear( dashOffset, 0.0 ) ) + element.appendChild( createSvgParameterElement( doc, "stroke-dashoffset", QString::number( dashOffset ) ) ); + } +} + + +bool QgsSymbolLayerV2Utils::lineFromSld( QDomElement &element, + Qt::PenStyle &penStyle, QColor &color, double &width, + Qt::PenJoinStyle *penJoinStyle, Qt::PenCapStyle *penCapStyle, + QVector *customDashPattern, double *dashOffset ) +{ + QgsDebugMsg( "Entered." ); + + penStyle = Qt::SolidLine; + color = QColor( "#000000" ); + width = 1; + if ( penJoinStyle ) + *penJoinStyle = Qt::BevelJoin; + if ( penCapStyle ) + *penCapStyle = Qt::SquareCap; + if ( customDashPattern ) + customDashPattern->clear(); + if ( dashOffset ) + *dashOffset = 0; + + if ( element.isNull() ) + { + penStyle = Qt::NoPen; + color = QColor(); + return true; + } + + QgsStringMap svgParams = getSvgParameterList( element ); + for ( QgsStringMap::iterator it = svgParams.begin(); it != svgParams.end(); ++it ) + { + QgsDebugMsg( QString( "found SvgParameter %1: %2" ).arg( it.key() ).arg( it.value() ) ); + + if ( it.key() == "stroke" ) + { + color = QColor( it.value() ); + } + else if ( it.key() == "stroke-opacity" ) + { + color.setAlpha( decodeSldAlpha( it.value() ) ); + } + else if ( it.key() == "stroke-width" ) + { + bool ok; + double w = it.value().toDouble( &ok ); + if ( ok ) + width = w; + } + else if ( it.key() == "stroke-linejoin" && penJoinStyle ) + { + *penJoinStyle = decodeSldLineJoinStyle( it.value() ); + } + else if ( it.key() == "stroke-linecap" && penCapStyle ) + { + *penCapStyle = decodeSldLineCapStyle( it.value() ); + } + else if ( it.key() == "stroke-dasharray" && customDashPattern ) + { + *customDashPattern = decodeSldRealVector( it.value() ); + if ( customDashPattern->size() > 0 ) + { + // convert the dasharray to one of the QT pen style, + // if no match is found then set pen style to CustomDashLine + bool dashPatternFound = false; + + if ( customDashPattern->count() == 2 ) + { + if ( customDashPattern->at( 0 ) == 4.0 && + customDashPattern->at( 1 ) == 2.0 ) + { + penStyle = Qt::DashLine; + dashPatternFound = true; + } + else if ( customDashPattern->at( 0 ) == 1.0 && + customDashPattern->at( 1 ) == 2.0 ) + { + penStyle = Qt::DotLine; + dashPatternFound = true; + } + } + else if ( customDashPattern->count() == 4 ) + { + if ( customDashPattern->at( 0 ) == 4.0 && + customDashPattern->at( 1 ) == 2.0 && + customDashPattern->at( 2 ) == 1.0 && + customDashPattern->at( 3 ) == 2.0 ) + { + penStyle = Qt::DashDotLine; + dashPatternFound = true; + } + } + else if ( customDashPattern->count() == 6 ) + { + if ( customDashPattern->at( 0 ) == 4.0 && + customDashPattern->at( 1 ) == 2.0 && + customDashPattern->at( 2 ) == 1.0 && + customDashPattern->at( 3 ) == 2.0 && + customDashPattern->at( 4 ) == 1.0 && + customDashPattern->at( 5 ) == 2.0 ) + { + penStyle = Qt::DashDotDotLine; + dashPatternFound = true; + } + } + + // default case: set pen style to CustomDashLine + if ( !dashPatternFound ) + { + penStyle = Qt::CustomDashLine; + } + } + } + else if ( it.key() == "stroke-dashoffset" && dashOffset ) + { + bool ok; + double d = it.value().toDouble( &ok ); + if ( ok ) + *dashOffset = d; + } + } + + return true; +} + +void QgsSymbolLayerV2Utils::externalGraphicToSld( QDomDocument &doc, QDomElement &element, + QString path, QString mime, + QColor color, double size ) +{ + QDomElement externalGraphicElem = doc.createElement( "se:ExternalGraphic" ); + element.appendChild( externalGraphicElem ); + + createOnlineResourceElement( doc, externalGraphicElem, path, mime ); + + //TODO: missing a way to handle svg color. Should use + Q_UNUSED( color ); + + if ( size >= 0 ) + { + QDomElement sizeElem = doc.createElement( "se:Size" ); + sizeElem.appendChild( doc.createTextNode( QString::number( size ) ) ); + element.appendChild( sizeElem ); + } +} + +bool QgsSymbolLayerV2Utils::externalGraphicFromSld( QDomElement &element, + QString &path, QString &mime, + QColor &color, double &size ) +{ + QgsDebugMsg( "Entered." ); + Q_UNUSED( color ); + + QDomElement externalGraphicElem = element.firstChildElement( "ExternalGraphic" ); + if ( externalGraphicElem.isNull() ) + return false; + + onlineResourceFromSldElement( externalGraphicElem, path, mime ); + + QDomElement sizeElem = element.firstChildElement( "Size" ); + if ( !sizeElem.isNull() ) + { + bool ok; + double s = sizeElem.firstChild().nodeValue().toDouble( &ok ); + if ( ok ) + size = s; + } + + return true; +} + +void QgsSymbolLayerV2Utils::externalMarkerToSld( QDomDocument &doc, QDomElement &element, + QString path, QString format, int *markIndex, + QColor color, double size ) +{ + QDomElement markElem = doc.createElement( "se:Mark" ); + element.appendChild( markElem ); + + createOnlineResourceElement( doc, markElem, path, format ); + + if ( markIndex ) + { + QDomElement markIndexElem = doc.createElement( "se:MarkIndex" ); + markIndexElem.appendChild( doc.createTextNode( QString::number( *markIndex ) ) ); + markElem.appendChild( markIndexElem ); + } + + // + QDomElement fillElem = doc.createElement( "se:Fill" ); + fillToSld( doc, fillElem, Qt::SolidPattern, color ); + markElem.appendChild( fillElem ); + + // + if ( !doubleNear( size, 0.0 ) && size > 0 ) + { + QDomElement sizeElem = doc.createElement( "se:Size" ); + sizeElem.appendChild( doc.createTextNode( QString::number( size ) ) ); + element.appendChild( sizeElem ); + } +} + +bool QgsSymbolLayerV2Utils::externalMarkerFromSld( QDomElement &element, + QString &path, QString &format, int &markIndex, + QColor &color, double &size ) +{ + QgsDebugMsg( "Entered." ); + + color = QColor(); + markIndex = -1; + size = -1; + + QDomElement markElem = element.firstChildElement( "Mark" ); + if ( markElem.isNull() ) + return false; + + onlineResourceFromSldElement( markElem, path, format ); + + QDomElement markIndexElem = markElem.firstChildElement( "MarkIndex" ); + if ( !markIndexElem.isNull() ) + { + bool ok; + int i = markIndexElem.firstChild().nodeValue().toInt( &ok ); + if ( ok ) + markIndex = i; + } + + // + QDomElement fillElem = markElem.firstChildElement( "Fill" ); + Qt::BrushStyle b = Qt::SolidPattern; + fillFromSld( fillElem, b, color ); + // ignore brush style, solid expected + + // + QDomElement sizeElem = element.firstChildElement( "Size" ); + if ( !sizeElem.isNull() ) + { + bool ok; + double s = sizeElem.firstChild().nodeValue().toDouble( &ok ); + if ( ok ) + size = s; + } + + return true; +} + +void QgsSymbolLayerV2Utils::wellKnownMarkerToSld( QDomDocument &doc, QDomElement &element, + QString name, QColor color, QColor borderColor, + double borderWidth, double size ) +{ + QDomElement markElem = doc.createElement( "se:Mark" ); + element.appendChild( markElem ); + + QDomElement wellKnownNameElem = doc.createElement( "se:WellKnownName" ); + wellKnownNameElem.appendChild( doc.createTextNode( name ) ); + markElem.appendChild( wellKnownNameElem ); + + // + if ( color.isValid() ) + { + QDomElement fillElem = doc.createElement( "se:Fill" ); + fillToSld( doc, fillElem, Qt::SolidPattern, color ); + markElem.appendChild( fillElem ); + } + + // + if ( borderColor.isValid() ) + { + QDomElement strokeElem = doc.createElement( "se:Stroke" ); + lineToSld( doc, strokeElem, Qt::SolidLine, borderColor, borderWidth ); + markElem.appendChild( strokeElem ); + } + + // + if ( !doubleNear( size, 0.0 ) && size > 0 ) + { + QDomElement sizeElem = doc.createElement( "se:Size" ); + sizeElem.appendChild( doc.createTextNode( QString::number( size ) ) ); + element.appendChild( sizeElem ); + } +} + +bool QgsSymbolLayerV2Utils::wellKnownMarkerFromSld( QDomElement &element, + QString &name, QColor &color, QColor &borderColor, + double &borderWidth, double &size ) +{ + QgsDebugMsg( "Entered." ); + + name = "square"; + color = QColor(); + borderColor = QColor( "#000000" ); + borderWidth = 1; + size = 6; + + QDomElement markElem = element.firstChildElement( "Mark" ); + if ( markElem.isNull() ) + return false; + + QDomElement wellKnownNameElem = markElem.firstChildElement( "WellKnownName" ); + if ( !wellKnownNameElem.isNull() ) + { + name = wellKnownNameElem.firstChild().nodeValue(); + QgsDebugMsg( "found Mark with well known name: " + name ); + } + + // + QDomElement fillElem = markElem.firstChildElement( "Fill" ); + Qt::BrushStyle b = Qt::SolidPattern; + fillFromSld( fillElem, b, color ); + // ignore brush style, solid expected + + // + QDomElement strokeElem = markElem.firstChildElement( "Stroke" ); + Qt::PenStyle p = Qt::SolidLine; + lineFromSld( strokeElem, p, borderColor, borderWidth ); + // ignore border style, solid expected + + // + QDomElement sizeElem = element.firstChildElement( "Size" ); + if ( !sizeElem.isNull() ) + { + bool ok; + double s = sizeElem.firstChild().nodeValue().toDouble( &ok ); + if ( ok ) + size = s; + } + + return true; +} + +void QgsSymbolLayerV2Utils::createRotationElement( QDomDocument &doc, QDomElement &element, QString rotationFunc ) +{ + if ( !rotationFunc.isEmpty() ) + { + QDomElement rotationElem = doc.createElement( "se:Rotation" ); + createFunctionElement( doc, rotationElem, rotationFunc ); + element.appendChild( rotationElem ); + } +} + +bool QgsSymbolLayerV2Utils::rotationFromSldElement( QDomElement &element, QString &rotationFunc ) +{ + QDomElement rotationElem = element.firstChildElement( "Rotation" ); + if ( !rotationElem.isNull() ) + { + functionFromSldElement( rotationElem, rotationFunc ); + } + return true; +} + + +void QgsSymbolLayerV2Utils::createOpacityElement( QDomDocument &doc, QDomElement &element, QString alphaFunc ) +{ + if ( !alphaFunc.isEmpty() ) + { + QDomElement opacityElem = doc.createElement( "se:Opacity" ); + createFunctionElement( doc, opacityElem, alphaFunc ); + element.appendChild( opacityElem ); + } +} + +bool QgsSymbolLayerV2Utils::opacityFromSldElement( QDomElement &element, QString &alphaFunc ) +{ + QDomElement opacityElem = element.firstChildElement( "Opacity" ); + if ( !opacityElem.isNull() ) + { + functionFromSldElement( opacityElem, alphaFunc ); + } + return true; +} + +void QgsSymbolLayerV2Utils::createDisplacementElement( QDomDocument &doc, QDomElement &element, QPointF offset ) +{ + if ( offset.isNull() ) + return; + + QDomElement displacementElem = doc.createElement( "se:Displacement" ); + element.appendChild( displacementElem ); + + QDomElement dispXElem = doc.createElement( "se:DisplacementX" ); + dispXElem.appendChild( doc.createTextNode( QString::number( offset.x() ) ) ); + + QDomElement dispYElem = doc.createElement( "se:DisplacementY" ); + dispYElem.appendChild( doc.createTextNode( QString::number( offset.y() ) ) ); + + displacementElem.appendChild( dispXElem ); + displacementElem.appendChild( dispYElem ); +} + +bool QgsSymbolLayerV2Utils::displacementFromSldElement( QDomElement &element, QPointF &offset ) +{ + offset = QPointF( 0, 0 ); + + QDomElement displacementElem = element.firstChildElement( "Displacement" ); + if ( displacementElem.isNull() ) + return true; + + QDomElement dispXElem = element.firstChildElement( "DisplacementX" ); + if ( !dispXElem.isNull() ) + { + bool ok; + double offsetX = dispXElem.firstChild().nodeValue().toDouble( &ok ); + if ( ok ) + offset.setX( offsetX ); + } + + QDomElement dispYElem = element.firstChildElement( "DisplacementY" ); + if ( !dispYElem.isNull() ) + { + bool ok; + double offsetY = dispYElem.firstChild().nodeValue().toDouble( &ok ); + if ( ok ) + offset.setY( offsetY ); + } + + return true; +} + +void QgsSymbolLayerV2Utils::labelTextToSld( QDomDocument &doc, QDomElement &element, + QString label, QFont font, + QColor color, double size ) +{ + QDomElement labelElem = doc.createElement( "se:Label" ); + labelElem.appendChild( doc.createTextNode( label ) ); + element.appendChild( labelElem ); + + QDomElement fontElem = doc.createElement( "se:Font" ); + element.appendChild( fontElem ); + + fontElem.appendChild( createSvgParameterElement( doc, "font-family", font.family() ) ); +#if 0 + fontElem.appendChild( createSldParameterElement( doc, "font-style", encodeSldFontStyle( font.style() ) ) ); + fontElem.appendChild( createSldParameterElement( doc, "font-weight", encodeSldFontWeight( font.weight() ) ) ); +#endif + fontElem.appendChild( createSvgParameterElement( doc, "font-size", QString::number( size ) ) ); + + // + if ( color.isValid() ) + { + QDomElement fillElem = doc.createElement( "Fill" ); + fillToSld( doc, fillElem, Qt::SolidPattern, color ); + element.appendChild( fillElem ); + } +} + +void QgsSymbolLayerV2Utils::createGeometryElement( QDomDocument &doc, QDomElement &element, QString geomFunc ) +{ + if ( geomFunc.isEmpty() ) + return; + + QDomElement geometryElem = doc.createElement( "Geometry" ); + element.appendChild( geometryElem ); + + /* About using a function withing the Geometry tag. + * + * The SLD specification <= 1.1 is vague: + * "In principle, a fixed geometry could be defined using GML or + * operators could be defined for computing the geometry from + * references or literals. However, using a feature property directly + * is by far the most commonly useful method." + * + * Even if it seems that specs should take care all the possible cases, + * looking at the XML schema fragment that encodes the Geometry element, + * it has to be a PropertyName element: + * + * + * + * + * + * + * + * + * Anyway we will use a ogc:Function to handle geometry transformations + * like offset, centroid, ... + */ + + createFunctionElement( doc, geometryElem, geomFunc ); +} + +bool QgsSymbolLayerV2Utils::geometryFromSldElement( QDomElement &element, QString &geomFunc ) +{ + QDomElement geometryElem = element.firstChildElement( "Geometry" ); + if ( geometryElem.isNull() ) + return true; + + return functionFromSldElement( geometryElem, geomFunc ); +} + +bool QgsSymbolLayerV2Utils::createFunctionElement( QDomDocument &doc, QDomElement &element, QString function ) +{ + // let's use QgsExpression to generate the SLD for the function + QgsExpression expr( function ); + if ( expr.hasParserError() ) + { + element.appendChild( doc.createComment( "Parser Error: " + expr.parserErrorString() + " - Expression was: " + function ) ); + return false; + } + expr.toOgcFilter( doc, element ); + return true; +} + +bool QgsSymbolLayerV2Utils::functionFromSldElement( QDomElement &element, QString &function ) +{ + QgsDebugMsg( "Entered." ); + + QgsExpression *expr = QgsExpression::createFromOgcFilter( element ); + if ( !expr ) + return false; + + bool valid = expr->hasParserError(); + if ( !valid ) + { + QgsDebugMsg( "parser error: " + expr->parserErrorString() ); + } + else + { + function = expr->dump(); + } + + delete expr; + return valid; +} + +void QgsSymbolLayerV2Utils::createOnlineResourceElement( QDomDocument &doc, QDomElement &element, + QString path, QString format ) +{ + QDomElement onlineResourceElem = doc.createElement( "OnlineResource" ); + onlineResourceElem.setAttribute( "xlink:type", "simple" ); + onlineResourceElem.setAttribute( "xlink:href", path ); + element.appendChild( onlineResourceElem ); + + QDomElement formatElem = doc.createElement( "Format" ); + formatElem.appendChild( doc.createTextNode( format ) ); + element.appendChild( formatElem ); +} + +bool QgsSymbolLayerV2Utils::onlineResourceFromSldElement( QDomElement &element, QString &path, QString &format ) +{ + QgsDebugMsg( "Entered." ); + + QDomElement onlineResourceElem = element.firstChildElement( "OnlineResource" ); + if ( onlineResourceElem.isNull() ) + return false; + + path = onlineResourceElem.attributeNS( "http://www.w3.org/1999/xlink", "href" ); + + QDomElement formatElem = element.firstChildElement( "Format" ); + if ( formatElem.isNull() ) + return false; // OnlineResource requires a Format sibling element + + format = formatElem.firstChild().nodeValue(); + return true; +} + + +QDomElement QgsSymbolLayerV2Utils::createSvgParameterElement( QDomDocument &doc, QString name, QString value ) +{ + QDomElement nodeElem = doc.createElement( "se:SvgParameter" ); + nodeElem.setAttribute( "name", name ); + nodeElem.appendChild( doc.createTextNode( value ) ); + return nodeElem; +} + +QgsStringMap QgsSymbolLayerV2Utils::getSvgParameterList( QDomElement &element ) +{ + QgsStringMap params; + + QDomElement paramElem = element.firstChildElement(); + while( !paramElem.isNull() ) + { + if ( paramElem.localName() == "SvgParameter" || paramElem.localName() == "CssParameter" ) + { + QString name = paramElem.attribute( "name" ); + QString value = paramElem.firstChild().nodeValue(); + + if ( !name.isEmpty() && !value.isEmpty() ) + params[ name ] = value; + } + + paramElem = paramElem.nextSiblingElement(); + } + + return params; +} + +QDomElement QgsSymbolLayerV2Utils::createVendorOptionElement( QDomDocument &doc, QString name, QString value ) +{ + QDomElement nodeElem = doc.createElement( "VendorOption" ); + nodeElem.setAttribute( "name", name ); + nodeElem.appendChild( doc.createTextNode( value ) ); + return nodeElem; +} + +QgsStringMap QgsSymbolLayerV2Utils::getVendorOptionList( QDomElement &element ) +{ + QgsStringMap params; + + QDomElement paramElem = element.firstChildElement( "VendorOption" ); + while( !paramElem.isNull() ) + { + QString name = paramElem.attribute( "name" ); + QString value = paramElem.firstChild().nodeValue(); + + if ( !name.isEmpty() && !value.isEmpty() ) + params[ name ] = value; + + paramElem = paramElem.nextSiblingElement( "VendorOption" ); + } + + return params; +} + + QgsStringMap QgsSymbolLayerV2Utils::parseProperties( QDomElement& element ) { QgsStringMap props; @@ -643,7 +2267,7 @@ QDomElement QgsSymbolLayerV2Utils::saveSymbols( QgsSymbolV2Map& symbols, QString void QgsSymbolLayerV2Utils::clearSymbolMap( QgsSymbolV2Map& symbols ) { foreach( QString name, symbols.keys() ) - delete symbols.value( name ); + delete symbols.value( name ); symbols.clear(); } diff --git a/src/core/symbology-ng/qgssymbollayerv2utils.h b/src/core/symbology-ng/qgssymbollayerv2utils.h index 4631e905a234..3f75cd007b8d 100644 --- a/src/core/symbology-ng/qgssymbollayerv2utils.h +++ b/src/core/symbology-ng/qgssymbollayerv2utils.h @@ -6,16 +6,17 @@ #include #include #include +#include +#include #include "qgssymbolv2.h" +#include "qgis.h" -class QgsSymbolV2; class QgsSymbolLayerV2; class QgsVectorColorRampV2; typedef QMap QgsStringMap; typedef QMap QgsSymbolV2Map; -class QColor; class QDomDocument; class QDomElement; class QIcon; @@ -30,6 +31,15 @@ class CORE_EXPORT QgsSymbolLayerV2Utils static QString encodeColor( QColor color ); static QColor decodeColor( QString str ); + static QString encodeSldAlpha( int alpha ); + static int decodeSldAlpha( QString str ); + + static QString encodeSldFontStyle( QFont::Style style ); + static QFont::Style decodeSldFontStyle( QString str ); + + static QString encodeSldFontWeight( int weight ); + static int decodeSldFontWeight( QString str ); + static QString encodePenStyle( Qt::PenStyle style ); static Qt::PenStyle decodePenStyle( QString str ); @@ -39,18 +49,33 @@ class CORE_EXPORT QgsSymbolLayerV2Utils static QString encodePenCapStyle( Qt::PenCapStyle style ); static Qt::PenCapStyle decodePenCapStyle( QString str ); + static QString encodeSldLineJoinStyle( Qt::PenJoinStyle style ); + static Qt::PenJoinStyle decodeSldLineJoinStyle( QString str ); + + static QString encodeSldLineCapStyle( Qt::PenCapStyle style ); + static Qt::PenCapStyle decodeSldLineCapStyle( QString str ); + static QString encodeBrushStyle( Qt::BrushStyle style ); static Qt::BrushStyle decodeBrushStyle( QString str ); + static QString encodeSldBrushStyle( Qt::BrushStyle style ); + static Qt::BrushStyle decodeSldBrushStyle( QString str ); + static QString encodePoint( QPointF point ); static QPointF decodePoint( QString str ); static QString encodeRealVector( const QVector& v ); static QVector decodeRealVector( const QString& s ); + static QString encodeSldRealVector( const QVector& v ); + static QVector decodeSldRealVector( const QString& s ); + static QString encodeOutputUnit( QgsSymbolV2::OutputUnit unit ); static QgsSymbolV2::OutputUnit decodeOutputUnit( QString str ); + static QString encodeSldUom( QgsSymbolV2::OutputUnit unit, double *scaleFactor ); + static QgsSymbolV2::OutputUnit decodeSldUom( QString str, double *scaleFactor ); + static QIcon symbolPreviewIcon( QgsSymbolV2* symbol, QSize size ); static QIcon symbolLayerPreviewIcon( QgsSymbolLayerV2* layer, QgsSymbolV2::OutputUnit u, QSize size ); static QIcon colorRampPreviewIcon( QgsVectorColorRampV2* ramp, QSize size ); @@ -62,6 +87,87 @@ class CORE_EXPORT QgsSymbolLayerV2Utils static QgsSymbolLayerV2* loadSymbolLayer( QDomElement& element ); static QDomElement saveSymbol( QString name, QgsSymbolV2* symbol, QDomDocument& doc, QgsSymbolV2Map* subSymbols = NULL ); + static bool createSymbolLayerV2ListFromSld( QDomElement& element, QGis::GeometryType geomType, QgsSymbolLayerV2List &layers ); + + static QgsSymbolLayerV2* createFillLayerFromSld( QDomElement &element ); + static QgsSymbolLayerV2* createLineLayerFromSld( QDomElement &element ); + static QgsSymbolLayerV2* createMarkerLayerFromSld( QDomElement &element ); + + static bool convertPolygonSymbolizerToPointMarker( QDomElement &element, QgsSymbolLayerV2List &layerList ); + static bool hasExternalGraphic( QDomElement &element ); + static bool hasWellKnownMark( QDomElement &element ); + + static bool needFontMarker( QDomElement &element ); + static bool needSvgMarker( QDomElement &element ); + static bool needEllipseMarker( QDomElement &element ); + static bool needMarkerLine( QDomElement &element ); + static bool needLinePatternFill( QDomElement &element ); + static bool needPointPatternFill( QDomElement &element ); + static bool needSvgFill( QDomElement &element ); + + static void fillToSld( QDomDocument &doc, QDomElement &element, + Qt::BrushStyle brushStyle, QColor color = QColor() ); + static bool fillFromSld( QDomElement &element, + Qt::BrushStyle &brushStyle, QColor &color ); + + static void lineToSld( QDomDocument &doc, QDomElement &element, + Qt::PenStyle penStyle, QColor color, double width = -1, + const Qt::PenJoinStyle *penJoinStyle = 0, const Qt::PenCapStyle *penCapStyle = 0, + const QVector *customDashPattern = 0, double dashOffset = 0.0 ); + static bool lineFromSld( QDomElement &element, + Qt::PenStyle &penStyle, QColor &color, double &width, + Qt::PenJoinStyle *penJoinStyle = 0, Qt::PenCapStyle *penCapStyle = 0, + QVector *customDashPattern = 0, double *dashOffset = 0 ); + + static void externalGraphicToSld( QDomDocument &doc, QDomElement &element, + QString path, QString mime, + QColor color, double size = -1 ); + static bool externalGraphicFromSld( QDomElement &element, + QString &path, QString &mime, + QColor &color, double &size ); + + static void wellKnownMarkerToSld( QDomDocument &doc, QDomElement &element, + QString name, QColor color, QColor borderColor = QColor(), + double borderWidth = -1, double size = -1 ); + static bool wellKnownMarkerFromSld( QDomElement &element, + QString &name, QColor &color, QColor &borderColor, + double &borderWidth, double &size ); + + static void externalMarkerToSld( QDomDocument &doc, QDomElement &element, + QString path, QString format, int *markIndex = 0, + QColor color = QColor(), double size = -1 ); + static bool externalMarkerFromSld( QDomElement &element, + QString &path, QString &format, int &markIndex, + QColor &color, double &size ); + + + static void labelTextToSld( QDomDocument &doc, QDomElement &element, QString label, + QFont font, QColor color = QColor(), double size = -1 ); + + static void createRotationElement( QDomDocument &doc, QDomElement &element, QString rotationFunc ); + static bool rotationFromSldElement( QDomElement &element, QString &rotationFunc ); + + static void createOpacityElement( QDomDocument &doc, QDomElement &element, QString alphaFunc ); + static bool opacityFromSldElement( QDomElement &element, QString &alphaFunc ); + + static void createDisplacementElement( QDomDocument &doc, QDomElement &element, QPointF offset ); + static bool displacementFromSldElement( QDomElement &element, QPointF &offset ); + + static void createOnlineResourceElement( QDomDocument &doc, QDomElement &element, QString path, QString format ); + static bool onlineResourceFromSldElement( QDomElement &element, QString &path, QString &format ); + + static void createGeometryElement( QDomDocument &doc, QDomElement &element, QString geomFunc ); + static bool geometryFromSldElement( QDomElement &element, QString &geomFunc ); + + static bool createFunctionElement( QDomDocument &doc, QDomElement &element, QString function ); + static bool functionFromSldElement( QDomElement &element, QString &function ); + + static QDomElement createSvgParameterElement( QDomDocument &doc, QString name, QString value ); + static QgsStringMap getSvgParameterList( QDomElement &element ); + + static QDomElement createVendorOptionElement( QDomDocument &doc, QString name, QString value ); + static QgsStringMap getVendorOptionList( QDomElement &element ); + static QgsStringMap parseProperties( QDomElement& element ); static void saveProperties( QgsStringMap props, QDomDocument& doc, QDomElement& element ); diff --git a/src/core/symbology-ng/qgssymbolv2.cpp b/src/core/symbology-ng/qgssymbolv2.cpp index b66f52835181..b8a83a48faf7 100644 --- a/src/core/symbology-ng/qgssymbolv2.cpp +++ b/src/core/symbology-ng/qgssymbolv2.cpp @@ -253,6 +253,19 @@ QString QgsSymbolV2::dump() return s; } +void QgsSymbolV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const +{ + props[ "alpha" ] = QString::number( alpha() ); + double scaleFactor = 1.0; + props[ "uom" ] = QgsSymbolLayerV2Utils::encodeSldUom( outputUnit(), &scaleFactor ); + props[ "uomScale" ] = scaleFactor != 1 ? QString::number( scaleFactor ) : ""; + + for ( QgsSymbolLayerV2List::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it ) + { + ( *it )->toSld( doc, element, props ); + } +} + QgsSymbolLayerV2List QgsSymbolV2::cloneLayers() const { QgsSymbolLayerV2List lst; diff --git a/src/core/symbology-ng/qgssymbolv2.h b/src/core/symbology-ng/qgssymbolv2.h index e9e6af11cd6e..0b96f4e83f70 100644 --- a/src/core/symbology-ng/qgssymbolv2.h +++ b/src/core/symbology-ng/qgssymbolv2.h @@ -12,6 +12,9 @@ class QPainter; class QSize; class QPointF; class QPolygonF; + +class QDomDocument; +class QDomElement; //class class QgsFeature; @@ -89,6 +92,8 @@ class CORE_EXPORT QgsSymbolV2 virtual QgsSymbolV2* clone() const = 0; + void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const; + OutputUnit outputUnit() const { return mOutputUnit; } void setOutputUnit( OutputUnit u ) { mOutputUnit = u; } diff --git a/src/core/symbology-ng/qgsvectorfieldsymbollayer.cpp b/src/core/symbology-ng/qgsvectorfieldsymbollayer.cpp index 9959d21210a6..b97fae07fdcc 100644 --- a/src/core/symbology-ng/qgsvectorfieldsymbollayer.cpp +++ b/src/core/symbology-ng/qgsvectorfieldsymbollayer.cpp @@ -177,6 +177,18 @@ QgsStringMap QgsVectorFieldSymbolLayer::properties() const return properties; } +void QgsVectorFieldSymbolLayer::toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ) const +{ + element.appendChild( doc.createComment( "VectorField not implemented yet..." ) ); + mLineSymbol->toSld( doc, element, props ); +} + +QgsSymbolLayerV2* QgsVectorFieldSymbolLayer::createFromSld( QDomElement &element ) +{ + Q_UNUSED( element ); + return NULL; +} + void QgsVectorFieldSymbolLayer::drawPreviewIcon( QgsSymbolV2RenderContext& context, QSize size ) { if ( mLineSymbol ) diff --git a/src/core/symbology-ng/qgsvectorfieldsymbollayer.h b/src/core/symbology-ng/qgsvectorfieldsymbollayer.h index 8f55b95954cd..367253b1c7ea 100644 --- a/src/core/symbology-ng/qgsvectorfieldsymbollayer.h +++ b/src/core/symbology-ng/qgsvectorfieldsymbollayer.h @@ -47,6 +47,7 @@ class CORE_EXPORT QgsVectorFieldSymbolLayer: public QgsMarkerSymbolLayerV2 ~QgsVectorFieldSymbolLayer(); static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() ); + static QgsSymbolLayerV2* createFromSld( QDomElement &element ); QString layerType() const { return "VectorField"; } @@ -60,6 +61,8 @@ class CORE_EXPORT QgsVectorFieldSymbolLayer: public QgsMarkerSymbolLayerV2 QgsSymbolLayerV2* clone() const; QgsStringMap properties() const; + void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ) const; + void drawPreviewIcon( QgsSymbolV2RenderContext& context, QSize size ); QSet usedAttributes() const;