Merge pull request #5226 from aaime/rule_labels

Also export rule based labelling in SLD. Follows up to ticket #8925
This commit is contained in:
Alessandro Pasotti 2017-10-19 17:58:25 +02:00 committed by GitHub
commit a411669dae
8 changed files with 1681 additions and 307 deletions

View File

@ -298,6 +298,7 @@ Create the instance from a DOM element with saved configuration
%End %End
virtual bool requiresAdvancedEffects() const; virtual bool requiresAdvancedEffects() const;
virtual void toSld( QDomNode &parent, const QgsStringMap &props ) const;
protected: protected:
}; };

View File

@ -92,6 +92,17 @@ Try to create instance of an implementation based on the XML data
Writes the SE 1.1 TextSymbolizer element based on the current layer labeling settings Writes the SE 1.1 TextSymbolizer element based on the current layer labeling settings
%End %End
protected:
virtual void writeTextSymbolizer( QDomNode &parent, QgsPalLayerSettings &settings, const QgsStringMap &props ) const;
%Docstring
Writes a TextSymbolizer element contents based on the provided labeling settings
writeTextSymbolizer
@param parent the node that will have the text symbolizer element added to it
@param settings the settings getting translated to a TextSymbolizer
@param props a open ended set of properties that can drive/inform the SLD encoding
%End
private: private:
QgsAbstractVectorLayerLabeling( const QgsAbstractVectorLayerLabeling &rhs ); QgsAbstractVectorLayerLabeling( const QgsAbstractVectorLayerLabeling &rhs );
}; };

View File

@ -13,7 +13,7 @@
* * * *
***************************************************************************/ ***************************************************************************/
#include "qgsrulebasedlabeling.h" #include "qgsrulebasedlabeling.h"
#include "qgssymbollayerutils.h"
QgsRuleBasedLabelProvider::QgsRuleBasedLabelProvider( const QgsRuleBasedLabeling &rules, QgsVectorLayer *layer, bool withFeatureLoop ) QgsRuleBasedLabelProvider::QgsRuleBasedLabelProvider( const QgsRuleBasedLabeling &rules, QgsVectorLayer *layer, bool withFeatureLoop )
: QgsVectorLayerLabelProvider( layer, QString(), withFeatureLoop, nullptr ) : QgsVectorLayerLabelProvider( layer, QString(), withFeatureLoop, nullptr )
@ -475,3 +475,44 @@ void QgsRuleBasedLabeling::setSettings( QgsPalLayerSettings *settings, const QSt
return rule->setSettings( settings ); return rule->setSettings( settings );
} }
} }
void QgsRuleBasedLabeling::toSld( QDomNode &parent, const QgsStringMap &props ) const
{
if ( !mRootRule )
{
return;
}
const QgsRuleBasedLabeling::RuleList rules = mRootRule->children();
for ( Rule *rule : rules )
{
QgsPalLayerSettings *settings = rule->settings();
if ( settings->drawLabels )
{
QDomDocument doc = parent.ownerDocument();
QDomElement ruleElement = doc.createElement( QStringLiteral( "se:Rule" ) );
parent.appendChild( ruleElement );
if ( !rule->filterExpression().isEmpty() )
{
QgsSymbolLayerUtils::createFunctionElement( doc, ruleElement, rule->filterExpression() );
}
// scale dependencies, the actual behavior is that the PAL settings min/max and
// the rule min/max get intersected
QgsStringMap localProps = QgsStringMap( props );
QgsSymbolLayerUtils::mergeScaleDependencies( rule->maximumScale(), rule->minimumScale(), localProps );
if ( settings->scaleVisibility )
{
QgsSymbolLayerUtils::mergeScaleDependencies( settings->maximumScale, settings->minimumScale, localProps );
}
QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElement, localProps );
QgsAbstractVectorLayerLabeling::writeTextSymbolizer( ruleElement, *settings, props );
}
}
}

View File

@ -372,6 +372,7 @@ class CORE_EXPORT QgsRuleBasedLabeling : public QgsAbstractVectorLayerLabeling
*/ */
virtual void setSettings( QgsPalLayerSettings *settings SIP_TRANSFER, const QString &providerId = QString() ) override; virtual void setSettings( QgsPalLayerSettings *settings SIP_TRANSFER, const QString &providerId = QString() ) override;
bool requiresAdvancedEffects() const override; bool requiresAdvancedEffects() const override;
virtual void toSld( QDomNode &parent, const QgsStringMap &props ) const override;
protected: protected:
Rule *mRootRule = nullptr; Rule *mRootRule = nullptr;

View File

@ -221,6 +221,314 @@ std::unique_ptr<QgsMarkerSymbolLayer> backgroundToMarkerLayer( const QgsTextBack
return layer; return layer;
} }
void QgsAbstractVectorLayerLabeling::writeTextSymbolizer( QDomNode &parent, QgsPalLayerSettings &settings, const QgsStringMap &props ) const
{
QDomDocument doc = parent.ownerDocument();
// text symbolizer
QDomElement textSymbolizerElement = doc.createElement( QStringLiteral( "se:TextSymbolizer" ) );
parent.appendChild( textSymbolizerElement );
// label
QgsTextFormat format = settings.format();
QFont font = format.font();
QDomElement labelElement = doc.createElement( QStringLiteral( "se:Label" ) );
textSymbolizerElement.appendChild( labelElement );
if ( settings.isExpression )
{
labelElement.appendChild( doc.createComment( QStringLiteral( "SE Export for %1 not implemented yet" ).arg( settings.getLabelExpression()->dump() ) ) );
labelElement.appendChild( doc.createTextNode( "Placeholder" ) );
}
else
{
if ( font.capitalization() == QFont::AllUppercase )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strToUpperCase" ), settings.fieldName );
}
else if ( font.capitalization() == QFont::AllLowercase )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strToLowerCase" ), settings.fieldName );
}
else if ( font.capitalization() == QFont::Capitalize )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strCapitalize" ), settings.fieldName );
}
else
{
QDomElement propertyNameElement = doc.createElement( QStringLiteral( "ogc:PropertyName" ) );
propertyNameElement.appendChild( doc.createTextNode( settings.fieldName ) );
labelElement.appendChild( propertyNameElement );
}
}
// font
QDomElement fontElement = doc.createElement( QStringLiteral( "se:Font" ) );
textSymbolizerElement.appendChild( fontElement );
fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-family" ), font.family() ) );
double fontSize = QgsSymbolLayerUtils::rescaleUom( format.size(), format.sizeUnit(), props );
fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-size" ), QString::number( fontSize ) ) );
if ( format.font().italic() )
{
fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-style" ), QStringLiteral( "italic" ) ) );
}
if ( format.font().bold() )
{
fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-weight" ), QStringLiteral( "bold" ) ) );
}
// label placement
QDomElement labelPlacement = doc.createElement( QStringLiteral( "se:LabelPlacement" ) );
textSymbolizerElement.appendChild( labelPlacement );
double maxDisplacement = 0;
double repeatDistance = 0;
switch ( settings.placement )
{
case QgsPalLayerSettings::OverPoint:
{
QDomElement pointPlacement = doc.createElement( "se:PointPlacement" );
labelPlacement.appendChild( pointPlacement );
// anchor point
QPointF anchor = quadOffsetToSldAnchor( settings.quadOffset );
QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, anchor );
// displacement
if ( settings.xOffset > 0 || settings.yOffset > 0 )
{
QgsUnitTypes::RenderUnit offsetUnit = settings.offsetUnits;
double dx = QgsSymbolLayerUtils::rescaleUom( settings.xOffset, offsetUnit, props );
double dy = QgsSymbolLayerUtils::rescaleUom( settings.yOffset, offsetUnit, props );
QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( dx, dy ) );
}
// rotation
if ( settings.angleOffset != 0 )
{
QDomElement rotation = doc.createElement( "se:Rotation" );
pointPlacement.appendChild( rotation );
rotation.appendChild( doc.createTextNode( QString::number( settings.angleOffset ) ) );
}
}
break;
case QgsPalLayerSettings::AroundPoint:
case QgsPalLayerSettings::OrderedPositionsAroundPoint:
{
QDomElement pointPlacement = doc.createElement( "se:PointPlacement" );
labelPlacement.appendChild( pointPlacement );
// SLD cannot do either, but let's do a best effort setting the distance using
// anchor point and displacement
QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, QPointF( 0, 0.5 ) );
QgsUnitTypes::RenderUnit distUnit = settings.distUnits;
double radius = QgsSymbolLayerUtils::rescaleUom( settings.dist, distUnit, props );
double offset = std::sqrt( radius * radius / 2 ); // make it start top/right
maxDisplacement = radius + 1; // lock the distance
QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( offset, offset ) );
}
break;
case QgsPalLayerSettings::Horizontal:
case QgsPalLayerSettings::Free:
{
// still a point placement (for "free" it's a fallback, there is no SLD equivalent)
QDomElement pointPlacement = doc.createElement( "se:PointPlacement" );
labelPlacement.appendChild( pointPlacement );
QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, QPointF( 0.5, 0.5 ) );
QgsUnitTypes::RenderUnit distUnit = settings.distUnits;
double dist = QgsSymbolLayerUtils::rescaleUom( settings.dist, distUnit, props );
QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( 0, dist ) );
break;
}
case QgsPalLayerSettings::Line:
case QgsPalLayerSettings::Curved:
case QgsPalLayerSettings::PerimeterCurved:
{
QDomElement linePlacement = doc.createElement( "se:LinePlacement" );
labelPlacement.appendChild( linePlacement );
// perpendicular distance if required
if ( settings.dist > 0 )
{
QgsUnitTypes::RenderUnit distUnit = settings.distUnits;
double dist = QgsSymbolLayerUtils::rescaleUom( settings.dist, distUnit, props );
QDomElement perpendicular = doc.createElement( "se:PerpendicularOffset" );
linePlacement.appendChild( perpendicular );
perpendicular.appendChild( doc.createTextNode( qgsDoubleToString( dist, 2 ) ) );
}
// repeat distance if required
if ( settings.repeatDistance > 0 )
{
QDomElement repeat = doc.createElement( "se:Repeat" );
linePlacement.appendChild( repeat );
repeat.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) );
QDomElement gap = doc.createElement( "se:Gap" );
linePlacement.appendChild( gap );
repeatDistance = QgsSymbolLayerUtils::rescaleUom( settings.repeatDistance, settings.repeatDistanceUnit, props );
gap.appendChild( doc.createTextNode( qgsDoubleToString( repeatDistance, 2 ) ) );
}
// always generalized
QDomElement generalize = doc.createElement( "se:GeneralizeLine" );
linePlacement.appendChild( generalize );
generalize.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) );
}
break;
}
// halo
QgsTextBufferSettings buffer = format.buffer();
if ( buffer.enabled() )
{
QDomElement haloElement = doc.createElement( QStringLiteral( "se:Halo" ) );
textSymbolizerElement.appendChild( haloElement );
QDomElement radiusElement = doc.createElement( QStringLiteral( "se:Radius" ) );
haloElement.appendChild( radiusElement );
// the SLD uses a radius, which is actually half of the link thickness the buffer size specifies
double radius = QgsSymbolLayerUtils::rescaleUom( buffer.size(), buffer.sizeUnit(), props ) / 2;
radiusElement.appendChild( doc.createTextNode( qgsDoubleToString( radius ) ) );
QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) );
haloElement.appendChild( fillElement );
fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), buffer.color().name() ) );
if ( buffer.opacity() != 1 )
{
fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), QString::number( buffer.opacity() ) ) );
}
}
// fill
QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) );
textSymbolizerElement.appendChild( fillElement );
fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), format.color().name() ) );
if ( format.opacity() != 1 )
{
fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), QString::number( format.opacity() ) ) );
}
// background graphic (not supported by SE 1.1, but supported by the GeoTools ecosystem as an extension)
QgsTextBackgroundSettings background = format.background();
if ( background.enabled() )
{
std::unique_ptr<QgsMarkerSymbolLayer> layer = backgroundToMarkerLayer( background );
layer->writeSldMarker( doc, textSymbolizerElement, props );
}
// priority and zIndex, the default values are 0 and 5 in qgis (and between 0 and 10),
// in the GeoTools ecosystem there is a single priority value set at 1000 by default
if ( settings.priority != 5 || settings.zIndex > 0 )
{
QDomElement priorityElement = doc.createElement( QStringLiteral( "se:Priority" ) );
textSymbolizerElement.appendChild( priorityElement );
int priority = 500 + 1000 * settings.zIndex + ( settings.priority - 5 ) * 100;
if ( settings.priority == 0 && settings.zIndex > 0 )
{
// small adjustment to make sure labels in z index n+1 are all above level n despite the priority value
priority += 1;
}
priorityElement.appendChild( doc.createTextNode( QString::number( priority ) ) );
}
// vendor options for text appearance
if ( font.underline() )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "underlineText" ), QStringLiteral( "true" ) );
textSymbolizerElement.appendChild( vo );
}
if ( font.strikeOut() )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "strikethroughText" ), QStringLiteral( "true" ) );
textSymbolizerElement.appendChild( vo );
}
// vendor options for text positioning
if ( maxDisplacement > 0 )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "maxDisplacement" ), qgsDoubleToString( maxDisplacement, 2 ) );
textSymbolizerElement.appendChild( vo );
}
if ( settings.placement == QgsPalLayerSettings::Curved || settings.placement == QgsPalLayerSettings::PerimeterCurved )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "followLine" ), QStringLiteral( "true" ) );
textSymbolizerElement.appendChild( vo );
if ( settings.maxCurvedCharAngleIn > 0 || settings.maxCurvedCharAngleOut > 0 )
{
// SLD has no notion for this, the GeoTools ecosystem can only do a single angle
double angle = std::min( std::fabs( settings.maxCurvedCharAngleIn ), std::fabs( settings.maxCurvedCharAngleOut ) );
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "maxAngleDelta" ), qgsDoubleToString( angle ) );
textSymbolizerElement.appendChild( vo );
}
}
if ( repeatDistance > 0 )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "repeat" ), qgsDoubleToString( repeatDistance, 2 ) );
textSymbolizerElement.appendChild( vo );
}
// miscellaneous options
if ( settings.displayAll )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "conflictResolution" ), QStringLiteral( "false" ) );
textSymbolizerElement.appendChild( vo );
}
if ( settings.upsidedownLabels == QgsPalLayerSettings::ShowAll )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "forceLeftToRight" ), QStringLiteral( "false" ) );
textSymbolizerElement.appendChild( vo );
}
if ( settings.mergeLines )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "group" ), QStringLiteral( "yes" ) );
textSymbolizerElement.appendChild( vo );
if ( settings.labelPerPart )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "labelAllGroup" ), QStringLiteral( "true" ) );
textSymbolizerElement.appendChild( vo );
}
}
// background symbol resize handling
if ( background.enabled() )
{
// enable resizing if needed
switch ( background.sizeType() )
{
case QgsTextBackgroundSettings::SizeBuffer:
{
QString resizeType;
if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle || background.type() == QgsTextBackgroundSettings::ShapeEllipse )
{
resizeType = QStringLiteral( "stretch" );
}
else
{
resizeType = QStringLiteral( "proportional" );
}
QDomElement voResize = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-resize" ), resizeType );
textSymbolizerElement.appendChild( voResize );
// now hadle margin
QSizeF size = background.size();
if ( size.width() > 0 || size.height() > 0 )
{
double x = QgsSymbolLayerUtils::rescaleUom( size.width(), background.sizeUnit(), props );
double y = QgsSymbolLayerUtils::rescaleUom( size.height(), background.sizeUnit(), props );
// in case of ellipse qgis pads the size generously to make sure the text is inside the ellipse
// the following seems to do the trick and keep visual output similar
if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
{
x += fontSize / 2;
y += fontSize;
}
QString resizeSpec = QString( "%1 %2" ).arg( qgsDoubleToString( x, 2 ), qgsDoubleToString( y, 2 ) );
QDomElement voMargin = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-margin" ), resizeSpec );
textSymbolizerElement.appendChild( voMargin );
}
break;
}
case QgsTextBackgroundSettings::SizeFixed:
case QgsTextBackgroundSettings::SizePercent:
// nothing to do here
break;
}
}
}
void QgsVectorLayerSimpleLabeling::toSld( QDomNode &parent, const QgsStringMap &props ) const void QgsVectorLayerSimpleLabeling::toSld( QDomNode &parent, const QgsStringMap &props ) const
{ {
@ -235,313 +543,14 @@ void QgsVectorLayerSimpleLabeling::toSld( QDomNode &parent, const QgsStringMap &
if ( mSettings->scaleVisibility ) if ( mSettings->scaleVisibility )
{ {
QgsStringMap scaleProps = QgsStringMap(); QgsStringMap scaleProps = QgsStringMap();
scaleProps.insert( QStringLiteral( "scaleMinDenom" ), qgsDoubleToString( mSettings->minimumScale ) ); // tricky here, the max scale is expressed as its denominator, but it's still the max scale
scaleProps.insert( QStringLiteral( "scaleMaxDenom" ), qgsDoubleToString( mSettings->maximumScale ) ); // in other words, the smallest scale denominator....
scaleProps.insert( "scaleMinDenom", qgsDoubleToString( mSettings->maximumScale ) );
scaleProps.insert( "scaleMaxDenom", qgsDoubleToString( mSettings->minimumScale ) );
QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElement, scaleProps ); QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElement, scaleProps );
} }
// text symbolizer writeTextSymbolizer( ruleElement, *mSettings, props );
QDomElement textSymbolizerElement = doc.createElement( QStringLiteral( "se:TextSymbolizer" ) );
ruleElement.appendChild( textSymbolizerElement );
// label
QgsTextFormat format = mSettings->format();
QFont font = format.font();
QDomElement labelElement = doc.createElement( QStringLiteral( "se:Label" ) );
textSymbolizerElement.appendChild( labelElement );
if ( mSettings->isExpression )
{
labelElement.appendChild( doc.createComment( QStringLiteral( "SE Export for %1 not implemented yet" ).arg( mSettings->getLabelExpression()->dump() ) ) );
labelElement.appendChild( doc.createTextNode( QStringLiteral( "Placeholder" ) ) );
}
else
{
if ( font.capitalization() == QFont::AllUppercase )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strToUpperCase" ), mSettings->fieldName );
}
else if ( font.capitalization() == QFont::AllLowercase )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strToLowerCase" ), mSettings->fieldName );
}
else if ( font.capitalization() == QFont::Capitalize )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strCapitalize" ), mSettings->fieldName );
}
else
{
QDomElement propertyNameElement = doc.createElement( QStringLiteral( "ogc:PropertyName" ) );
propertyNameElement.appendChild( doc.createTextNode( mSettings->fieldName ) );
labelElement.appendChild( propertyNameElement );
}
}
// font
QDomElement fontElement = doc.createElement( QStringLiteral( "se:Font" ) );
textSymbolizerElement.appendChild( fontElement );
fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-family" ), font.family() ) );
double fontSize = QgsSymbolLayerUtils::rescaleUom( format.size(), format.sizeUnit(), props );
fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-size" ), QString::number( fontSize ) ) );
if ( format.font().italic() )
{
fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-style" ), QStringLiteral( "italic" ) ) );
}
if ( format.font().bold() )
{
fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-weight" ), QStringLiteral( "bold" ) ) );
}
// label placement
QDomElement labelPlacement = doc.createElement( QStringLiteral( "se:LabelPlacement" ) );
textSymbolizerElement.appendChild( labelPlacement );
double maxDisplacement = 0;
double repeatDistance = 0;
switch ( mSettings->placement )
{
case QgsPalLayerSettings::OverPoint:
{
QDomElement pointPlacement = doc.createElement( QStringLiteral( "se:PointPlacement" ) );
labelPlacement.appendChild( pointPlacement );
// anchor point
QPointF anchor = quadOffsetToSldAnchor( mSettings->quadOffset );
QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, anchor );
// displacement
if ( mSettings->xOffset > 0 || mSettings->yOffset > 0 )
{
QgsUnitTypes::RenderUnit offsetUnit = mSettings->offsetUnits;
double dx = QgsSymbolLayerUtils::rescaleUom( mSettings->xOffset, offsetUnit, props );
double dy = QgsSymbolLayerUtils::rescaleUom( mSettings->yOffset, offsetUnit, props );
QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( dx, dy ) );
}
// rotation
if ( mSettings->angleOffset != 0 )
{
QDomElement rotation = doc.createElement( QStringLiteral( "se:Rotation" ) );
pointPlacement.appendChild( rotation );
rotation.appendChild( doc.createTextNode( QString::number( mSettings->angleOffset ) ) );
}
}
break;
case QgsPalLayerSettings::AroundPoint:
case QgsPalLayerSettings::OrderedPositionsAroundPoint:
{
QDomElement pointPlacement = doc.createElement( QStringLiteral( "se:PointPlacement" ) );
labelPlacement.appendChild( pointPlacement );
// SLD cannot do either, but let's do a best effort setting the distance using
// anchor point and displacement
QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, QPointF( 0, 0.5 ) );
QgsUnitTypes::RenderUnit distUnit = mSettings->distUnits;
double radius = QgsSymbolLayerUtils::rescaleUom( mSettings->dist, distUnit, props );
double offset = std::sqrt( radius * radius / 2 ); // make it start top/right
maxDisplacement = radius + 1; // lock the distance
QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( offset, offset ) );
}
break;
case QgsPalLayerSettings::Horizontal:
case QgsPalLayerSettings::Free:
{
// still a point placement (for "free" it's a fallback, there is no SLD equivalent)
QDomElement pointPlacement = doc.createElement( QStringLiteral( "se:PointPlacement" ) );
labelPlacement.appendChild( pointPlacement );
QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, QPointF( 0.5, 0.5 ) );
QgsUnitTypes::RenderUnit distUnit = mSettings->distUnits;
double dist = QgsSymbolLayerUtils::rescaleUom( mSettings->dist, distUnit, props );
QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( 0, dist ) );
break;
}
case QgsPalLayerSettings::Line:
case QgsPalLayerSettings::Curved:
case QgsPalLayerSettings::PerimeterCurved:
{
QDomElement linePlacement = doc.createElement( QStringLiteral( "se:LinePlacement" ) );
labelPlacement.appendChild( linePlacement );
// perpendicular distance if required
if ( mSettings->dist > 0 )
{
QgsUnitTypes::RenderUnit distUnit = mSettings->distUnits;
double dist = QgsSymbolLayerUtils::rescaleUom( mSettings->dist, distUnit, props );
QDomElement perpendicular = doc.createElement( QStringLiteral( "se:PerpendicularOffset" ) );
linePlacement.appendChild( perpendicular );
perpendicular.appendChild( doc.createTextNode( qgsDoubleToString( dist, 2 ) ) );
}
// repeat distance if required
if ( mSettings->repeatDistance > 0 )
{
QDomElement repeat = doc.createElement( QStringLiteral( "se:Repeat" ) );
linePlacement.appendChild( repeat );
repeat.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) );
QDomElement gap = doc.createElement( QStringLiteral( "se:Gap" ) );
linePlacement.appendChild( gap );
repeatDistance = QgsSymbolLayerUtils::rescaleUom( mSettings->repeatDistance, mSettings->repeatDistanceUnit, props );
gap.appendChild( doc.createTextNode( qgsDoubleToString( repeatDistance, 2 ) ) );
}
// always generalized
QDomElement generalize = doc.createElement( QStringLiteral( "se:GeneralizeLine" ) );
linePlacement.appendChild( generalize );
generalize.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) );
}
break;
}
// halo
QgsTextBufferSettings buffer = format.buffer();
if ( buffer.enabled() )
{
QDomElement haloElement = doc.createElement( QStringLiteral( "se:Halo" ) );
textSymbolizerElement.appendChild( haloElement );
QDomElement radiusElement = doc.createElement( QStringLiteral( "se:Radius" ) );
haloElement.appendChild( radiusElement );
// the SLD uses a radius, which is actually half of the link thickness the buffer size specifies
double radius = QgsSymbolLayerUtils::rescaleUom( buffer.size(), buffer.sizeUnit(), props ) / 2;
radiusElement.appendChild( doc.createTextNode( qgsDoubleToString( radius ) ) );
QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) );
haloElement.appendChild( fillElement );
fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), buffer.color().name() ) );
if ( buffer.opacity() != 1 )
{
fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), QString::number( buffer.opacity() ) ) );
}
}
// fill
QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) );
textSymbolizerElement.appendChild( fillElement );
fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), format.color().name() ) );
if ( format.opacity() != 1 )
{
fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), QString::number( format.opacity() ) ) );
}
// background graphic (not supported by SE 1.1, but supported by the GeoTools ecosystem as an extension)
QgsTextBackgroundSettings background = format.background();
if ( background.enabled() )
{
std::unique_ptr<QgsMarkerSymbolLayer> layer = backgroundToMarkerLayer( background );
layer->writeSldMarker( doc, textSymbolizerElement, props );
}
// priority and zIndex, the default values are 0 and 5 in qgis (and between 0 and 10),
// in the GeoTools ecosystem there is a single priority value set at 1000 by default
if ( mSettings->priority != 5 || mSettings->zIndex > 0 )
{
QDomElement priorityElement = doc.createElement( QStringLiteral( "se:Priority" ) );
textSymbolizerElement.appendChild( priorityElement );
int priority = 500 + 1000 * mSettings->zIndex + ( mSettings->priority - 5 ) * 100;
if ( mSettings->priority == 0 && mSettings->zIndex > 0 )
{
// small adjustment to make sure labels in z index n+1 are all above level n despite the priority value
priority += 1;
}
priorityElement.appendChild( doc.createTextNode( QString::number( priority ) ) );
}
// vendor options for text appearance
if ( font.underline() )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "underlineText" ), QStringLiteral( "true" ) );
textSymbolizerElement.appendChild( vo );
}
if ( font.strikeOut() )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "strikethroughText" ), QStringLiteral( "true" ) );
textSymbolizerElement.appendChild( vo );
}
// vendor options for text positioning
if ( maxDisplacement > 0 )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "maxDisplacement" ), qgsDoubleToString( maxDisplacement, 2 ) );
textSymbolizerElement.appendChild( vo );
}
if ( mSettings->placement == QgsPalLayerSettings::Curved || mSettings->placement == QgsPalLayerSettings::PerimeterCurved )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "followLine" ), QStringLiteral( "true" ) );
textSymbolizerElement.appendChild( vo );
if ( mSettings->maxCurvedCharAngleIn > 0 || mSettings->maxCurvedCharAngleOut > 0 )
{
// SLD has no notion for this, the GeoTools ecosystem can only do a single angle
double angle = std::min( std::fabs( mSettings->maxCurvedCharAngleIn ), std::fabs( mSettings->maxCurvedCharAngleOut ) );
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "maxAngleDelta" ), qgsDoubleToString( angle ) );
textSymbolizerElement.appendChild( vo );
}
}
if ( repeatDistance > 0 )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "repeat" ), qgsDoubleToString( repeatDistance, 2 ) );
textSymbolizerElement.appendChild( vo );
}
// miscellaneous options
if ( mSettings->displayAll )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "conflictResolution" ), QStringLiteral( "false" ) );
textSymbolizerElement.appendChild( vo );
}
if ( mSettings->upsidedownLabels == QgsPalLayerSettings::ShowAll )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "forceLeftToRight" ), QStringLiteral( "false" ) );
textSymbolizerElement.appendChild( vo );
}
if ( mSettings->mergeLines )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "group" ), QStringLiteral( "yes" ) );
textSymbolizerElement.appendChild( vo );
if ( mSettings->labelPerPart )
{
QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "labelAllGroup" ), QStringLiteral( "true" ) );
textSymbolizerElement.appendChild( vo );
}
}
// background symbol resize handling
if ( background.enabled() )
{
// enable resizing if needed
switch ( background.sizeType() )
{
case QgsTextBackgroundSettings::SizeBuffer:
{
QString resizeType;
if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle || background.type() == QgsTextBackgroundSettings::ShapeEllipse )
{
resizeType = QStringLiteral( "stretch" );
}
else
{
resizeType = QStringLiteral( "proportional" );
}
QDomElement voResize = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-resize" ), resizeType );
textSymbolizerElement.appendChild( voResize );
// now hadle margin
QSizeF size = background.size();
if ( size.width() > 0 || size.height() > 0 )
{
double x = QgsSymbolLayerUtils::rescaleUom( size.width(), background.sizeUnit(), props );
double y = QgsSymbolLayerUtils::rescaleUom( size.height(), background.sizeUnit(), props );
// in case of ellipse qgis pads the size generously to make sure the text is inside the ellipse
// the following seems to do the trick and keep visual output similar
if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
{
x += fontSize / 2;
y += fontSize;
}
QString resizeSpec = QStringLiteral( "%1 %2" ).arg( qgsDoubleToString( x, 2 ), qgsDoubleToString( y, 2 ) );
QDomElement voMargin = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-margin" ), resizeSpec );
textSymbolizerElement.appendChild( voMargin );
}
break;
}
case QgsTextBackgroundSettings::SizeFixed:
case QgsTextBackgroundSettings::SizePercent:
// nothing to do here
break;
}
}
} }

View File

@ -102,6 +102,17 @@ class CORE_EXPORT QgsAbstractVectorLayerLabeling
parent.appendChild( doc.createComment( QStringLiteral( "SE Export for %1 not implemented yet" ).arg( type() ) ) ); parent.appendChild( doc.createComment( QStringLiteral( "SE Export for %1 not implemented yet" ).arg( type() ) ) );
} }
protected:
/**
* Writes a TextSymbolizer element contents based on the provided labeling settings
* @brief writeTextSymbolizer
* @param parent the node that will have the text symbolizer element added to it
* @param settings the settings getting translated to a TextSymbolizer
* @param props a open ended set of properties that can drive/inform the SLD encoding
*/
virtual void writeTextSymbolizer( QDomNode &parent, QgsPalLayerSettings &settings, const QgsStringMap &props ) const;
private: private:
Q_DISABLE_COPY( QgsAbstractVectorLayerLabeling ) Q_DISABLE_COPY( QgsAbstractVectorLayerLabeling )

View File

@ -915,8 +915,9 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
self.loadStyleWithCustomProperties(layer, "polygonLabel") self.loadStyleWithCustomProperties(layer, "polygonLabel")
settings = layer.labeling().settings() settings = layer.labeling().settings()
settings.scaleVisibility = True settings.scaleVisibility = True
settings.minimumScale = 1000000 # Careful: min scale -> large scale denomin
settings.maximumScale = 10000000 settings.minimumScale = 10000000
settings.maximumScale = 1000000
layer.setLabeling(QgsVectorLayerSimpleLabeling(settings)) layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
dom, root = self.layerToSld(layer) dom, root = self.layerToSld(layer)
@ -1018,6 +1019,39 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
else: else:
self.assertIsNone(self.assertVendorOption(ts, 'graphic-margin', True)) self.assertIsNone(self.assertVendorOption(ts, 'graphic-margin', True))
def testRuleBasedLabels(self):
layer = QgsVectorLayer("Point", "addfeat", "memory")
self.loadStyleWithCustomProperties(layer, "ruleLabel")
dom, root = self.layerToSld(layer)
# print("Rule based labeling: " + dom.toString())
# three rules, one with the point symbol, one with the first rule based label,
# one with the second rule based label
rule1 = self.getRule(root, 0)
self.assertElement(rule1, 'se:PointSymbolizer', 0)
rule2 = self.getRule(root, 1)
self.assertScaleDenominator(root, '100000', '10000000', 1)
tsRule2 = self.assertElement(rule2, 'se:TextSymbolizer', 0)
gt = rule2.elementsByTagName("Filter").item(0).firstChild()
self.assertEqual("ogc:PropertyIsGreaterThan", gt.nodeName())
gtProperty = gt.toElement().firstChild()
self.assertEqual("ogc:PropertyName", gtProperty.nodeName())
self.assertEqual("POP_MAX", gtProperty.toElement().text())
gtValue = gt.childNodes().item(1)
self.assertEqual("1000000", gtValue.toElement().text())
rule3 = self.getRule(root, 2)
tsRule3 = self.assertElement(rule3, 'se:TextSymbolizer', 0)
lt = rule3.elementsByTagName("Filter").item(0).firstChild()
self.assertEqual("ogc:PropertyIsLessThan", lt.nodeName())
ltProperty = lt.toElement().firstChild()
self.assertEqual("ogc:PropertyName", ltProperty.nodeName())
self.assertEqual("POP_MAX", ltProperty.toElement().text())
ltValue = gt.childNodes().item(1)
self.assertEqual("1000000", gtValue.toElement().text())
def updateLinePlacementProperties(self, layer, linePlacement, distance, repeat, maxAngleInternal=25, maxAngleExternal=-25): def updateLinePlacementProperties(self, layer, linePlacement, distance, repeat, maxAngleInternal=25, maxAngleExternal=-25):
settings = layer.labeling().settings() settings = layer.labeling().settings()
settings.placement = linePlacement settings.placement = linePlacement
@ -1124,6 +1158,10 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
self.assertTrue(node.isElement(), 'Found node but it''s not an element') self.assertTrue(node.isElement(), 'Found node but it''s not an element')
return node.toElement() return node.toElement()
def getRule(self, root, ruleIndex):
rule = self.assertElement(root, 'se:Rule', ruleIndex)
return rule
def getTextSymbolizer(self, root, ruleIndex, textSymbolizerIndex): def getTextSymbolizer(self, root, ruleIndex, textSymbolizerIndex):
rule = self.assertElement(root, 'se:Rule', ruleIndex) rule = self.assertElement(root, 'se:Rule', ruleIndex)
textSymbolizer = self.assertElement(rule, 'se:TextSymbolizer', textSymbolizerIndex) textSymbolizer = self.assertElement(rule, 'se:TextSymbolizer', textSymbolizerIndex)

1262
tests/testdata/symbol_layer/ruleLabel.qml vendored Normal file

File diff suppressed because it is too large Load Diff