From a25b0257a544bc0579187ebe1243c99bf141c51a Mon Sep 17 00:00:00 2001 From: Andrea Aime Date: Fri, 26 Aug 2016 11:33:36 +0200 Subject: [PATCH] Export map level scale based dependencies in most vector symbology --- doc/api_break.dox | 9 + python/core/qgsvectorlayer.sip | 3 +- .../core/symbology-ng/qgssymbollayerutils.sip | 31 + src/core/qgsmaplayer.cpp | 8 +- src/core/qgsvectorlayer.cpp | 14 +- src/core/qgsvectorlayer.h | 11 + .../qgscategorizedsymbolrenderer.cpp | 6 +- .../qgscategorizedsymbolrenderer.h | 2 +- .../qgsgraduatedsymbolrenderer.cpp | 3 +- .../symbology-ng/qgsgraduatedsymbolrenderer.h | 2 +- src/core/symbology-ng/qgslinesymbollayer.cpp | 2 +- .../qgspointdisplacementrenderer.cpp | 4 +- .../qgspointdisplacementrenderer.h | 2 +- src/core/symbology-ng/qgsrenderer.cpp | 7 +- src/core/symbology-ng/qgsrenderer.h | 9 +- .../symbology-ng/qgsrulebasedrenderer.cpp | 38 +- src/core/symbology-ng/qgsrulebasedrenderer.h | 2 +- .../symbology-ng/qgssinglesymbolrenderer.cpp | 5 +- .../symbology-ng/qgssinglesymbolrenderer.h | 2 +- src/core/symbology-ng/qgssymbollayerutils.cpp | 64 +- src/core/symbology-ng/qgssymbollayerutils.h | 22 +- .../python/test_qgssymbollayer_createsld.py | 118 ++- tests/testdata/symbol_layer/categorized.qml | 903 ++++++++++++++++++ tests/testdata/symbol_layer/graduated.qml | 585 ++++++++++++ tests/testdata/symbol_layer/ruleBased.qml | 509 ++++++++++ tests/testdata/symbol_layer/singleSymbol.qml | 615 ++++++++++++ 26 files changed, 2909 insertions(+), 67 deletions(-) create mode 100644 tests/testdata/symbol_layer/categorized.qml create mode 100644 tests/testdata/symbol_layer/graduated.qml create mode 100644 tests/testdata/symbol_layer/ruleBased.qml create mode 100644 tests/testdata/symbol_layer/singleSymbol.qml diff --git a/doc/api_break.dox b/doc/api_break.dox index 597318f6ecb..515fdd4da1a 100644 --- a/doc/api_break.dox +++ b/doc/api_break.dox @@ -999,6 +999,15 @@ in code which previously passed a null pointer to QgsVectorFileWriter.
  • QgsWMSLegendNode has been renamed to QgsWmsLegendNode
  • +\subsection qgis_api_break_3_0_QgsRenderer QgsRenderer + + + + \section qgis_api_break_2_4 QGIS 2.4 diff --git a/python/core/qgsvectorlayer.sip b/python/core/qgsvectorlayer.sip index e67ede42417..815389b7d8d 100644 --- a/python/core/qgsvectorlayer.sip +++ b/python/core/qgsvectorlayer.sip @@ -709,7 +709,7 @@ class QgsVectorLayer : QgsMapLayer */ bool writeStyle( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const; - bool writeSld( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const; + bool writeSld( QDomNode& node, QDomDocument& doc, QString& errorMessage, QgsStringMap& props ) const; bool readSld( const QDomNode& node, QString& errorMessage ); /** @@ -1660,4 +1660,3 @@ class QgsVectorLayer : QgsMapLayer }; - diff --git a/python/core/symbology-ng/qgssymbollayerutils.sip b/python/core/symbology-ng/qgssymbollayerutils.sip index 9679e4337dc..81d0652d73e 100644 --- a/python/core/symbology-ng/qgssymbollayerutils.sip +++ b/python/core/symbology-ng/qgssymbollayerutils.sip @@ -224,6 +224,7 @@ class QgsSymbolLayerUtils static void createGeometryElement( QDomDocument &doc, QDomElement &element, const QString& geomFunc ); static bool geometryFromSldElement( QDomElement &element, QString &geomFunc ); + static bool createExpressionElement( QDomDocument &doc, QDomElement &element, const QString& function ); static bool createFunctionElement( QDomDocument &doc, QDomElement &element, const QString& function ); static bool functionFromSldElement( QDomElement &element, QString &function ); @@ -444,4 +445,34 @@ class QgsSymbolLayerUtils */ static QList prettyBreaks( double minimum, double maximum, int classes ); + /** Rescales the given size based on the uomScale found in the props, if any is found, otherwise + * returns the value un-modified + * @note added in 3.0 + */ + static double rescaleUom( double size, QgsUnitTypes::RenderUnit unit, const QgsStringMap& props ); + + /** Rescales the given point based on the uomScale found in the props, if any is found, otherwise + * returns a copy of the original point + * @note added in 3.0 + */ + static QPointF rescaleUom( const QPointF& point, QgsUnitTypes::RenderUnit unit, const QgsStringMap& props ) /PyName=rescalePointUom/; + + /** Rescales the given array based on the uomScale found in the props, if any is found, otherwise + * returns a copy of the original point + * @note added in 3.0 + */ + static QVector rescaleUom( const QVector& array, QgsUnitTypes::RenderUnit unit, const QgsStringMap& props ) /PyName=rescaleArrayUom/; + + /** + * Checks if the properties contain scaleMinDenom and scaleMaxDenom, if available, they are added into the SE Rule element + * @note added in 3.0 + */ + static void applyScaleDependency( QDomDocument& doc, QDomElement& ruleElem, QgsStringMap& props ); + + /** + * Merges the local scale limits, if any, with the ones already in the map, if any + * @note added in 3.0 + */ + static void mergeScaleDependencies( int mScaleMinDenom, int mScaleMaxDenom, QgsStringMap& props ); + }; diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index 76b30f0363f..1b5059e0d41 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -1448,7 +1448,13 @@ void QgsMapLayer::exportSldStyle( QDomDocument &doc, QString &errorMsg ) const return; } - if ( !vlayer->writeSld( namedLayerNode, myDocument, errorMsg ) ) + QgsStringMap props; + if ( hasScaleBasedVisibility() ) + { + props[ "scaleMinDenom" ] = QString::number( mMinScale ); + props[ "scaleMaxDenom" ] = QString::number( mMaxScale ); + } + if ( !vlayer->writeSld( namedLayerNode, myDocument, errorMsg, props ) ) { errorMsg = tr( "Could not save symbology because:\n%1" ).arg( errorMsg ); return; diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index e58931b6393..6aff7570526 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -1991,8 +1991,12 @@ bool QgsVectorLayer::readSld( const QDomNode& node, QString& errorMessage ) return true; } - bool QgsVectorLayer::writeSld( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const +{ + return writeSld( node, doc, errorMessage, QgsStringMap() ); +} + +bool QgsVectorLayer::writeSld( QDomNode& node, QDomDocument& doc, QString& errorMessage, QgsStringMap props ) const { Q_UNUSED( errorMessage ); @@ -2001,9 +2005,15 @@ bool QgsVectorLayer::writeSld( QDomNode& node, QDomDocument& doc, QString& error nameNode.appendChild( doc.createTextNode( name() ) ); node.appendChild( nameNode ); + QgsStringMap localProps = QgsStringMap( props ); + if ( hasScaleBasedVisibility() ) + { + QgsSymbolLayerUtils::mergeScaleDependencies( minimumScale(), maximumScale(), localProps ); + } + if ( hasGeometryType() ) { - node.appendChild( mRenderer->writeSld( doc, name() ) ); + node.appendChild( mRenderer->writeSld( doc, name(), localProps ) ); } return true; } diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index 623bbfa6833..68b974d50f9 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -803,6 +803,17 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte bool writeStyle( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const override; bool writeSld( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const; + + /** + * Writes the symbology of the layer into the document provided in SLD 1.1 format + * @param node the node that will have the style element added to it. + * @param doc the document that will have the QDomNode added. + * @param errorMessage reference to string that will be updated with any error messages + * @param props a open ended set of properties that can drive/inform the SLD encoding + * @return true in case of success + */ + bool writeSld( QDomNode& node, QDomDocument& doc, QString& errorMessage, QgsStringMap props = QgsStringMap() ) const; + bool readSld( const QDomNode& node, QString& errorMessage ) override; /** diff --git a/src/core/symbology-ng/qgscategorizedsymbolrenderer.cpp b/src/core/symbology-ng/qgscategorizedsymbolrenderer.cpp index 8619f8c0ae7..a9c62d827e1 100644 --- a/src/core/symbology-ng/qgscategorizedsymbolrenderer.cpp +++ b/src/core/symbology-ng/qgscategorizedsymbolrenderer.cpp @@ -141,6 +141,9 @@ void QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, QgsStr mValue.toString().replace( '\'', "''" ) ); QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, filterFunc ); + // add the mix/max scale denoms if we got any from the callers + QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props ); + mSymbol->toSld( doc, ruleElem, props ); } @@ -517,9 +520,8 @@ QgsCategorizedSymbolRenderer* QgsCategorizedSymbolRenderer::clone() const return r; } -void QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element ) const +void QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const { - QgsStringMap props; props[ "attribute" ] = mAttrName; if ( mRotation.data() ) props[ "angle" ] = mRotation->expression(); diff --git a/src/core/symbology-ng/qgscategorizedsymbolrenderer.h b/src/core/symbology-ng/qgscategorizedsymbolrenderer.h index 86bc4635a72..c7db1e2e636 100644 --- a/src/core/symbology-ng/qgscategorizedsymbolrenderer.h +++ b/src/core/symbology-ng/qgscategorizedsymbolrenderer.h @@ -99,7 +99,7 @@ class CORE_EXPORT QgsCategorizedSymbolRenderer : public QgsFeatureRenderer virtual QgsCategorizedSymbolRenderer* clone() const override; - virtual void toSld( QDomDocument& doc, QDomElement &element ) const override; + virtual void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props = QgsStringMap() ) const override; //! returns bitwise OR-ed capabilities of the renderer virtual Capabilities capabilities() override { return SymbolLevels | RotationField | Filter; } diff --git a/src/core/symbology-ng/qgsgraduatedsymbolrenderer.cpp b/src/core/symbology-ng/qgsgraduatedsymbolrenderer.cpp index c7f534f6705..8fc9826ee5c 100644 --- a/src/core/symbology-ng/qgsgraduatedsymbolrenderer.cpp +++ b/src/core/symbology-ng/qgsgraduatedsymbolrenderer.cpp @@ -560,9 +560,8 @@ QgsGraduatedSymbolRenderer* QgsGraduatedSymbolRenderer::clone() const return r; } -void QgsGraduatedSymbolRenderer::toSld( QDomDocument& doc, QDomElement &element ) const +void QgsGraduatedSymbolRenderer::toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ) const { - QgsStringMap props; props[ "attribute" ] = mAttrName; props[ "method" ] = graduatedMethodStr( mGraduatedMethod ); if ( mRotation.data() ) diff --git a/src/core/symbology-ng/qgsgraduatedsymbolrenderer.h b/src/core/symbology-ng/qgsgraduatedsymbolrenderer.h index 2ff762e06d1..a9acaca1b8e 100644 --- a/src/core/symbology-ng/qgsgraduatedsymbolrenderer.h +++ b/src/core/symbology-ng/qgsgraduatedsymbolrenderer.h @@ -152,7 +152,7 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer virtual QgsGraduatedSymbolRenderer* clone() const override; - virtual void toSld( QDomDocument& doc, QDomElement &element ) const override; + virtual void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props = QgsStringMap() ) const override; //! returns bitwise OR-ed capabilities of the renderer virtual Capabilities capabilities() override { return SymbolLevels | RotationField | Filter; } diff --git a/src/core/symbology-ng/qgslinesymbollayer.cpp b/src/core/symbology-ng/qgslinesymbollayer.cpp index 289ab9e510c..48f78152269 100644 --- a/src/core/symbology-ng/qgslinesymbollayer.cpp +++ b/src/core/symbology-ng/qgslinesymbollayer.cpp @@ -1450,7 +1450,7 @@ void QgsMarkerLineSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, c if ( !gap.isEmpty() ) { QDomElement gapElem = doc.createElement( "se:Gap" ); - QgsSymbolLayerUtils::createFunctionElement( doc, gapElem, gap ); + QgsSymbolLayerUtils::createExpressionElement( doc, gapElem, gap ); graphicStrokeElem.appendChild( gapElem ); } diff --git a/src/core/symbology-ng/qgspointdisplacementrenderer.cpp b/src/core/symbology-ng/qgspointdisplacementrenderer.cpp index 63a2dd5def2..68ef599c8cd 100644 --- a/src/core/symbology-ng/qgspointdisplacementrenderer.cpp +++ b/src/core/symbology-ng/qgspointdisplacementrenderer.cpp @@ -87,9 +87,9 @@ QgsPointDisplacementRenderer* QgsPointDisplacementRenderer::clone() const return r; } -void QgsPointDisplacementRenderer::toSld( QDomDocument& doc, QDomElement &element ) const +void QgsPointDisplacementRenderer::toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ) const { - mRenderer->toSld( doc, element ); + mRenderer->toSld( doc, element, props ); } diff --git a/src/core/symbology-ng/qgspointdisplacementrenderer.h b/src/core/symbology-ng/qgspointdisplacementrenderer.h index bfd0fdfb98b..bb6ea9c4e52 100644 --- a/src/core/symbology-ng/qgspointdisplacementrenderer.h +++ b/src/core/symbology-ng/qgspointdisplacementrenderer.h @@ -46,7 +46,7 @@ class CORE_EXPORT QgsPointDisplacementRenderer: public QgsFeatureRenderer QgsPointDisplacementRenderer* clone() const override; - virtual void toSld( QDomDocument& doc, QDomElement &element ) const override; + virtual void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props = QgsStringMap() ) const override; /** Reimplemented from QgsFeatureRenderer*/ bool renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer = -1, bool selected = false, bool drawVertexMarker = false ) override; diff --git a/src/core/symbology-ng/qgsrenderer.cpp b/src/core/symbology-ng/qgsrenderer.cpp index c4c408d5f8c..117814b0b6e 100644 --- a/src/core/symbology-ng/qgsrenderer.cpp +++ b/src/core/symbology-ng/qgsrenderer.cpp @@ -340,10 +340,11 @@ QgsFeatureRenderer* QgsFeatureRenderer::loadSld( const QDomNode &node, QgsWkbTyp QDomElement QgsFeatureRenderer::writeSld( QDomDocument& doc, const QgsVectorLayer &layer ) const { - return writeSld( doc, layer.name() ); + QgsStringMap props; + return writeSld( doc, layer.name(), props ); } -QDomElement QgsFeatureRenderer::writeSld( QDomDocument& doc, const QString& styleName ) const +QDomElement QgsFeatureRenderer::writeSld( QDomDocument& doc, const QString& styleName, QgsStringMap props ) const { QDomElement userStyleElem = doc.createElement( "UserStyle" ); @@ -352,7 +353,7 @@ QDomElement QgsFeatureRenderer::writeSld( QDomDocument& doc, const QString& styl userStyleElem.appendChild( nameElem ); QDomElement featureTypeStyleElem = doc.createElement( "se:FeatureTypeStyle" ); - toSld( doc, featureTypeStyleElem ); + toSld( doc, featureTypeStyleElem, props ); userStyleElem.appendChild( featureTypeStyleElem ); return userStyleElem; diff --git a/src/core/symbology-ng/qgsrenderer.h b/src/core/symbology-ng/qgsrenderer.h index 5bddd028b87..696f0f9b803 100644 --- a/src/core/symbology-ng/qgsrenderer.h +++ b/src/core/symbology-ng/qgsrenderer.h @@ -263,7 +263,7 @@ class CORE_EXPORT QgsFeatureRenderer Q_DECL_DEPRECATED virtual QDomElement writeSld( QDomDocument& doc, const QgsVectorLayer &layer ) const; //! create the SLD UserStyle element following the SLD v1.1 specs with the given name //! @note added in 2.8 - virtual QDomElement writeSld( QDomDocument& doc, const QString& styleName ) const; + virtual QDomElement writeSld( QDomDocument& doc, const QString& styleName, QgsStringMap props = QgsStringMap() ) const; /** Create a new renderer according to the information contained in * the UserStyle element of a SLD style document @@ -278,8 +278,11 @@ class CORE_EXPORT QgsFeatureRenderer static QgsFeatureRenderer* loadSld( const QDomNode &node, QgsWkbTypes::GeometryType geomType, QString &errorMessage ); //! used from subclasses to create SLD Rule elements following SLD v1.1 specs - virtual void toSld( QDomDocument& doc, QDomElement &element ) const - { element.appendChild( doc.createComment( QString( "FeatureRendererV2 %1 not implemented yet" ).arg( type() ) ) ); } + virtual void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props = QgsStringMap() ) const + { + element.appendChild( doc.createComment( QString( "FeatureRendererV2 %1 not implemented yet" ).arg( type() ) ) ); + ( void ) props; // warning avoidance + } //! return a list of symbology items for the legend virtual QgsLegendSymbologyList legendSymbologyItems( QSize iconSize ); diff --git a/src/core/symbology-ng/qgsrulebasedrenderer.cpp b/src/core/symbology-ng/qgsrulebasedrenderer.cpp index d959bee75f5..3e35b852c5d 100644 --- a/src/core/symbology-ng/qgsrulebasedrenderer.cpp +++ b/src/core/symbology-ng/qgsrulebasedrenderer.cpp @@ -348,25 +348,7 @@ void QgsRuleBasedRenderer::Rule::toSld( QDomDocument& doc, QDomElement &element, 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 ) ); - } + QgsSymbolLayerUtils::mergeScaleDependencies( mScaleMinDenom, mScaleMaxDenom, props ); if ( mSymbol ) { @@ -402,19 +384,7 @@ void QgsRuleBasedRenderer::Rule::toSld( QDomDocument& doc, QDomElement &element, QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, props.value( "filter", "" ) ); } - 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 ); - } + QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props ); mSymbol->toSld( doc, ruleElem, props ); } @@ -977,9 +947,9 @@ QgsRuleBasedRenderer* QgsRuleBasedRenderer::clone() const return r; } -void QgsRuleBasedRenderer::toSld( QDomDocument& doc, QDomElement &element ) const +void QgsRuleBasedRenderer::toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ) const { - mRootRule->toSld( doc, element, QgsStringMap() ); + mRootRule->toSld( doc, element, props ); } // TODO: ideally this function should be removed in favor of legendSymbol(ogy)Items diff --git a/src/core/symbology-ng/qgsrulebasedrenderer.h b/src/core/symbology-ng/qgsrulebasedrenderer.h index 3f41810f0cc..3a1f3a1d88f 100644 --- a/src/core/symbology-ng/qgsrulebasedrenderer.h +++ b/src/core/symbology-ng/qgsrulebasedrenderer.h @@ -441,7 +441,7 @@ class CORE_EXPORT QgsRuleBasedRenderer : public QgsFeatureRenderer virtual QgsRuleBasedRenderer* clone() const override; - virtual void toSld( QDomDocument& doc, QDomElement &element ) const override; + virtual void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props = QgsStringMap() ) const override; static QgsFeatureRenderer* createFromSld( QDomElement& element, QgsWkbTypes::GeometryType geomType ); diff --git a/src/core/symbology-ng/qgssinglesymbolrenderer.cpp b/src/core/symbology-ng/qgssinglesymbolrenderer.cpp index 4dc82ef776d..003e46a8992 100644 --- a/src/core/symbology-ng/qgssinglesymbolrenderer.cpp +++ b/src/core/symbology-ng/qgssinglesymbolrenderer.cpp @@ -205,9 +205,8 @@ QgsSingleSymbolRenderer* QgsSingleSymbolRenderer::clone() const return r; } -void QgsSingleSymbolRenderer::toSld( QDomDocument& doc, QDomElement &element ) const +void QgsSingleSymbolRenderer::toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props ) const { - QgsStringMap props; if ( mRotation.data() ) props[ "angle" ] = mRotation->expression(); if ( mSizeScale.data() ) @@ -220,6 +219,8 @@ void QgsSingleSymbolRenderer::toSld( QDomDocument& doc, QDomElement &element ) c nameElem.appendChild( doc.createTextNode( "Single symbol" ) ); ruleElem.appendChild( nameElem ); + QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props ); + if ( mSymbol.data() ) mSymbol->toSld( doc, ruleElem, props ); } diff --git a/src/core/symbology-ng/qgssinglesymbolrenderer.h b/src/core/symbology-ng/qgssinglesymbolrenderer.h index 9422bff1828..10f5b0df732 100644 --- a/src/core/symbology-ng/qgssinglesymbolrenderer.h +++ b/src/core/symbology-ng/qgssinglesymbolrenderer.h @@ -61,7 +61,7 @@ class CORE_EXPORT QgsSingleSymbolRenderer : public QgsFeatureRenderer virtual QgsSingleSymbolRenderer* clone() const override; - virtual void toSld( QDomDocument& doc, QDomElement &element ) const override; + virtual void toSld( QDomDocument& doc, QDomElement &element, QgsStringMap props = QgsStringMap() ) const override; static QgsFeatureRenderer* createFromSld( QDomElement& element, QgsWkbTypes::GeometryType geomType ); //! returns bitwise OR-ed capabilities of the renderer diff --git a/src/core/symbology-ng/qgssymbollayerutils.cpp b/src/core/symbology-ng/qgssymbollayerutils.cpp index bf9138b087f..9287ea616e3 100644 --- a/src/core/symbology-ng/qgssymbollayerutils.cpp +++ b/src/core/symbology-ng/qgssymbollayerutils.cpp @@ -2153,7 +2153,7 @@ void QgsSymbolLayerUtils::createRotationElement( QDomDocument &doc, QDomElement if ( !rotationFunc.isEmpty() ) { QDomElement rotationElem = doc.createElement( "se:Rotation" ); - createFunctionElement( doc, rotationElem, rotationFunc ); + createExpressionElement( doc, rotationElem, rotationFunc ); element.appendChild( rotationElem ); } } @@ -2174,7 +2174,7 @@ void QgsSymbolLayerUtils::createOpacityElement( QDomDocument &doc, QDomElement & if ( !alphaFunc.isEmpty() ) { QDomElement opacityElem = doc.createElement( "se:Opacity" ); - createFunctionElement( doc, opacityElem, alphaFunc ); + createExpressionElement( doc, opacityElem, alphaFunc ); element.appendChild( opacityElem ); } } @@ -2378,7 +2378,7 @@ void QgsSymbolLayerUtils::createGeometryElement( QDomDocument &doc, QDomElement * like offset, centroid, ... */ - createFunctionElement( doc, geometryElem, geomFunc ); + createExpressionElement( doc, geometryElem, geomFunc ); } bool QgsSymbolLayerUtils::geometryFromSldElement( QDomElement &element, QString &geomFunc ) @@ -2390,7 +2390,7 @@ bool QgsSymbolLayerUtils::geometryFromSldElement( QDomElement &element, QString return functionFromSldElement( geometryElem, geomFunc ); } -bool QgsSymbolLayerUtils::createFunctionElement( QDomDocument &doc, QDomElement &element, const QString& function ) +bool QgsSymbolLayerUtils::createExpressionElement( QDomDocument &doc, QDomElement &element, const QString& function ) { // let's use QgsExpression to generate the SLD for the function QgsExpression expr( function ); @@ -2405,6 +2405,22 @@ bool QgsSymbolLayerUtils::createFunctionElement( QDomDocument &doc, QDomElement return true; } + +bool QgsSymbolLayerUtils::createFunctionElement( QDomDocument &doc, QDomElement &element, const 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; + } + QDomElement filterElem = QgsOgcUtils::expressionToOgcFilter( expr, doc ); + if ( !filterElem.isNull() ) + element.appendChild( filterElem ); + return true; +} + bool QgsSymbolLayerUtils::functionFromSldElement( QDomElement &element, QString &function ) { QDomElement elem = element; @@ -4000,3 +4016,43 @@ QVector QgsSymbolLayerUtils::rescaleUom( const QVector& array, Qgs } return result; } + +void QgsSymbolLayerUtils::applyScaleDependency( QDomDocument& doc, QDomElement& ruleElem, QgsStringMap& props ) +{ + 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 ); + } +} + +void QgsSymbolLayerUtils::mergeScaleDependencies( int mScaleMinDenom, int mScaleMaxDenom, QgsStringMap& props ) +{ + 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 ) ); + } +} diff --git a/src/core/symbology-ng/qgssymbollayerutils.h b/src/core/symbology-ng/qgssymbollayerutils.h index b4c0f8d03fc..9faa2f8d36d 100644 --- a/src/core/symbology-ng/qgssymbollayerutils.h +++ b/src/core/symbology-ng/qgssymbollayerutils.h @@ -291,6 +291,14 @@ class CORE_EXPORT QgsSymbolLayerUtils static void createGeometryElement( QDomDocument &doc, QDomElement &element, const QString& geomFunc ); static bool geometryFromSldElement( QDomElement &element, QString &geomFunc ); + /** + * Creates a OGC Expression element based on the provided function expression + * @param doc The document owning the element + * @param element The element parent + * @param function The expression to be encoded + * @return + */ + static bool createExpressionElement( QDomDocument &doc, QDomElement &element, const QString& function ); static bool createFunctionElement( QDomDocument &doc, QDomElement &element, const QString& function ); static bool functionFromSldElement( QDomElement &element, QString &function ); @@ -527,24 +535,32 @@ class CORE_EXPORT QgsSymbolLayerUtils /** Rescales the given size based on the uomScale found in the props, if any is found, otherwise * returns the value un-modified * @note added in 3.0 - * @note not available in Python bindings */ static double rescaleUom( double size, QgsUnitTypes::RenderUnit unit, const QgsStringMap& props ); /** Rescales the given point based on the uomScale found in the props, if any is found, otherwise * returns a copy of the original point * @note added in 3.0 - * @note not available in Python bindings */ static QPointF rescaleUom( const QPointF& point, QgsUnitTypes::RenderUnit unit, const QgsStringMap& props ); /** Rescales the given array based on the uomScale found in the props, if any is found, otherwise * returns a copy of the original point * @note added in 3.0 - * @note not available in Python bindings */ static QVector rescaleUom( const QVector& array, QgsUnitTypes::RenderUnit unit, const QgsStringMap& props ); + /** + * Checks if the properties contain scaleMinDenom and scaleMaxDenom, if available, they are added into the SE Rule element + * @note added in 3.0 + */ + static void applyScaleDependency( QDomDocument& doc, QDomElement& ruleElem, QgsStringMap& props ); + + /** + * Merges the local scale limits, if any, with the ones already in the map, if any + * @note added in 3.0 + */ + static void mergeScaleDependencies( int mScaleMinDenom, int mScaleMaxDenom, QgsStringMap& props ); }; diff --git a/tests/src/python/test_qgssymbollayer_createsld.py b/tests/src/python/test_qgssymbollayer_createsld.py index 113e529e098..37bc26a81fe 100644 --- a/tests/src/python/test_qgssymbollayer_createsld.py +++ b/tests/src/python/test_qgssymbollayer_createsld.py @@ -36,7 +36,7 @@ from qgis.core import ( QgsSimpleMarkerSymbolLayer, QgsUnitTypes, QgsSvgMarkerSymbolLayer, QgsFontMarkerSymbolLayer, QgsEllipseSymbolLayer, QgsSimpleLineSymbolLayer, QgsMarkerLineSymbolLayer, QgsMarkerSymbol, QgsSimpleFillSymbolLayer, QgsSVGFillSymbolLayer, - QgsLinePatternFillSymbolLayer, QgsPointPatternFillSymbolLayer) + QgsLinePatternFillSymbolLayer, QgsPointPatternFillSymbolLayer, QgsVectorLayer) from qgis.testing import start_app, unittest from utilities import unitTestDataPath @@ -371,6 +371,113 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase): self.assertStaticSize(root, '2') + def testSingleSymbolNoScaleDependencies(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "singleSymbol")) + layer.loadNamedStyle(mFilePath) + + dom, root = self.layerToSld(layer) + # print("No dep on single symbol:" + dom.toString()) + + self.assertScaleDenominator(root, None, None) + + def testSingleSymbolScaleDependencies(self): + layer = QgsVectorLayer("Point", "addfeat", "memory") + mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "singleSymbol")) + layer.loadNamedStyle(mFilePath) + layer.setMinimumScale(1000) + layer.setMaximumScale(500000) + layer.setScaleBasedVisibility(True) + + dom, root = self.layerToSld(layer) + # print("Scale dep on single symbol:" + dom.toString()) + + self.assertScaleDenominator(root, '1000', '500000') + + def testCategorizedNoScaleDependencies(self): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "categorized")) + layer.loadNamedStyle(mFilePath) + + dom, root = self.layerToSld(layer) + # print("Categorized no scale deps:" + dom.toString()) + + ruleCount = root.elementsByTagName('se:Rule').size() + for i in range(0, ruleCount): + self.assertScaleDenominator(root, None, None, i) + + def testCategorizedWithScaleDependencies(self): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "categorized")) + layer.loadNamedStyle(mFilePath) + layer.setMinimumScale(1000) + layer.setMaximumScale(500000) + layer.setScaleBasedVisibility(True) + + dom, root = self.layerToSld(layer) + # print("Categorized with scale deps:" + dom.toString()) + + ruleCount = root.elementsByTagName('se:Rule').size() + for i in range(0, ruleCount): + self.assertScaleDenominator(root, '1000', '500000', i) + + def testGraduatedNoScaleDependencies(self): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + + mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "graduated")) + status = layer.loadNamedStyle(mFilePath) + + dom, root = self.layerToSld(layer) + # print("Graduated no scale deps:" + dom.toString()) + + ruleCount = root.elementsByTagName('se:Rule').size() + for i in range(0, ruleCount): + self.assertScaleDenominator(root, None, None, i) + + def testRuleBasedNoRootScaleDependencies(self): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + + mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "ruleBased")) + status = layer.loadNamedStyle(mFilePath) + + dom, root = self.layerToSld(layer) + print("Rule based, no root scale deps:" + dom.toString()) + + ruleCount = root.elementsByTagName('se:Rule').size() + self.assertScaleDenominator(root, '1000', '40000000', 0) + self.assertScaleDenominator(root, None, None, 1) + + def testRuleBasedNoRootScaleDependencies(self): + layer = QgsVectorLayer("Polygon", "addfeat", "memory") + + mFilePath = QDir.toNativeSeparators('%s/symbol_layer/%s.qml' % (unitTestDataPath(), "ruleBased")) + status = layer.loadNamedStyle(mFilePath) + layer.setMinimumScale(5000) + layer.setMaximumScale(50000000) + layer.setScaleBasedVisibility(True) + + dom, root = self.layerToSld(layer) + # print("Rule based, with root scale deps:" + dom.toString()) + + ruleCount = root.elementsByTagName('se:Rule').size() + self.assertScaleDenominator(root, '5000', '40000000', 0) + self.assertScaleDenominator(root, '5000', '50000000', 1) + + def assertScaleDenominator(self, root, expectedMinScale, expectedMaxScale, index=0): + rule = root.elementsByTagName('se:Rule').item(index).toElement() + + if expectedMinScale: + minScale = rule.elementsByTagName('se:MinScaleDenominator').item(0) + self.assertEquals(expectedMinScale, minScale.firstChild().nodeValue()) + else: + self.assertEquals(0, root.elementsByTagName('se:MinScaleDenominator').size()) + + if expectedMaxScale: + maxScale = rule.elementsByTagName('se:MaxScaleDenominator').item(0) + self.assertEquals(expectedMaxScale, maxScale.firstChild().nodeValue()) + else: + self.assertEquals(0, root.elementsByTagName('se:MaxScaleDenominator').size()) + def assertDashPattern(self, root, svgParameterIdx, expectedPattern): strokeWidth = root.elementsByTagName( 'se:SvgParameter').item(svgParameterIdx) @@ -401,5 +508,14 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase): symbolLayer.toSld(dom, root, {}) return dom, root + def layerToSld(self, mapLayer): + dom = QDomDocument() + root = dom.createElement("FakeRoot") + dom.appendChild(root) + error = None + mapLayer.writeSld(root, dom, error, {}) + return dom, root + + if __name__ == '__main__': unittest.main() diff --git a/tests/testdata/symbol_layer/categorized.qml b/tests/testdata/symbol_layer/categorized.qml new file mode 100644 index 00000000000..4a92181ef41 --- /dev/null +++ b/tests/testdata/symbol_layer/categorized.qml @@ -0,0 +1,903 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + + name + + 2 + diff --git a/tests/testdata/symbol_layer/graduated.qml b/tests/testdata/symbol_layer/graduated.qml new file mode 100644 index 00000000000..979a75bf830 --- /dev/null +++ b/tests/testdata/symbol_layer/graduated.qml @@ -0,0 +1,585 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + + name + + 2 + diff --git a/tests/testdata/symbol_layer/ruleBased.qml b/tests/testdata/symbol_layer/ruleBased.qml new file mode 100644 index 00000000000..6be8744466f --- /dev/null +++ b/tests/testdata/symbol_layer/ruleBased.qml @@ -0,0 +1,509 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + + name + + 2 + diff --git a/tests/testdata/symbol_layer/singleSymbol.qml b/tests/testdata/symbol_layer/singleSymbol.qml new file mode 100644 index 00000000000..d85299a730d --- /dev/null +++ b/tests/testdata/symbol_layer/singleSymbol.qml @@ -0,0 +1,615 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + + + + + + + . + + 0 + . + + 0 + generatedlayout + + + + + + + NAME + + 0 +