mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
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:
commit
a411669dae
@ -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:
|
||||
};
|
||||
|
@ -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 );
|
||||
};
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 )
|
||||
|
||||
|
@ -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
1262
tests/testdata/symbol_layer/ruleLabel.qml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user