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
virtual bool requiresAdvancedEffects() const;
virtual void toSld( QDomNode &parent, const QgsStringMap &props ) const;
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
%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:
QgsAbstractVectorLayerLabeling( const QgsAbstractVectorLayerLabeling &rhs );
};

View File

@ -13,7 +13,7 @@
* *
***************************************************************************/
#include "qgsrulebasedlabeling.h"
#include "qgssymbollayerutils.h"
QgsRuleBasedLabelProvider::QgsRuleBasedLabelProvider( const QgsRuleBasedLabeling &rules, QgsVectorLayer *layer, bool withFeatureLoop )
: QgsVectorLayerLabelProvider( layer, QString(), withFeatureLoop, nullptr )
@ -475,3 +475,44 @@ void QgsRuleBasedLabeling::setSettings( QgsPalLayerSettings *settings, const QSt
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;
bool requiresAdvancedEffects() const override;
virtual void toSld( QDomNode &parent, const QgsStringMap &props ) const override;
protected:
Rule *mRootRule = nullptr;

View File

@ -221,6 +221,314 @@ std::unique_ptr<QgsMarkerSymbolLayer> backgroundToMarkerLayer( const QgsTextBack
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
{
@ -235,313 +543,14 @@ void QgsVectorLayerSimpleLabeling::toSld( QDomNode &parent, const QgsStringMap &
if ( mSettings->scaleVisibility )
{
QgsStringMap scaleProps = QgsStringMap();
scaleProps.insert( QStringLiteral( "scaleMinDenom" ), qgsDoubleToString( mSettings->minimumScale ) );
scaleProps.insert( QStringLiteral( "scaleMaxDenom" ), qgsDoubleToString( mSettings->maximumScale ) );
// tricky here, the max scale is expressed as its denominator, but it's still the max scale
// in other words, the smallest scale denominator....
scaleProps.insert( "scaleMinDenom", qgsDoubleToString( mSettings->maximumScale ) );
scaleProps.insert( "scaleMaxDenom", qgsDoubleToString( mSettings->minimumScale ) );
QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElement, scaleProps );
}
// text symbolizer
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;
}
}
writeTextSymbolizer( ruleElement, *mSettings, props );
}

View File

@ -102,6 +102,17 @@ class CORE_EXPORT QgsAbstractVectorLayerLabeling
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:
Q_DISABLE_COPY( QgsAbstractVectorLayerLabeling )

View File

@ -915,8 +915,9 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
self.loadStyleWithCustomProperties(layer, "polygonLabel")
settings = layer.labeling().settings()
settings.scaleVisibility = True
settings.minimumScale = 1000000
settings.maximumScale = 10000000
# Careful: min scale -> large scale denomin
settings.minimumScale = 10000000
settings.maximumScale = 1000000
layer.setLabeling(QgsVectorLayerSimpleLabeling(settings))
dom, root = self.layerToSld(layer)
@ -1018,6 +1019,39 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
else:
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):
settings = layer.labeling().settings()
settings.placement = linePlacement
@ -1124,6 +1158,10 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
self.assertTrue(node.isElement(), 'Found node but it''s not an element')
return node.toElement()
def getRule(self, root, ruleIndex):
rule = self.assertElement(root, 'se:Rule', ruleIndex)
return rule
def getTextSymbolizer(self, root, ruleIndex, textSymbolizerIndex):
rule = self.assertElement(root, 'se:Rule', ruleIndex)
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