mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-05 00:09:32 -04:00
Merge pull request #51371 from elpaso/bugfix-sld-wellknown-polygon-fill
SLD: implement import/export of wellknown polygon pattern fills
This commit is contained in:
commit
863a126175
@ -4229,7 +4229,7 @@ QgsPointPatternFillSymbolLayer *QgsPointPatternFillSymbolLayer::clone() const
|
||||
|
||||
void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
|
||||
{
|
||||
for ( int i = 0; i < mMarkerSymbol->symbolLayerCount(); i++ )
|
||||
for ( int symbolLayerIdx = 0; symbolLayerIdx < mMarkerSymbol->symbolLayerCount(); symbolLayerIdx++ )
|
||||
{
|
||||
QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
|
||||
if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
|
||||
@ -4245,14 +4245,21 @@ void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &elem
|
||||
QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
|
||||
fillElem.appendChild( graphicFillElem );
|
||||
|
||||
QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( symbolLayerIdx );
|
||||
|
||||
// Converts to GeoServer "graphic-margin": symbol size must be subtracted from distance and then divided by 2
|
||||
const double markerSize { mMarkerSymbol->size() };
|
||||
|
||||
// store distanceX, distanceY, displacementX, displacementY in a <VendorOption>
|
||||
double dx = QgsSymbolLayerUtils::rescaleUom( mDistanceX, mDistanceXUnit, props );
|
||||
double dy = QgsSymbolLayerUtils::rescaleUom( mDistanceY, mDistanceYUnit, props );
|
||||
QString dist = QgsSymbolLayerUtils::encodePoint( QPointF( dx, dy ) );
|
||||
QDomElement distanceElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "distance" ), dist );
|
||||
symbolizerElem.appendChild( distanceElem );
|
||||
// From: https://docs.geoserver.org/stable/en/user/styling/sld/extensions/margins.html
|
||||
// top-bottom,right-left (two values, top and bottom sharing the same value)
|
||||
const QString marginSpec = QString( "%1 %2" ).arg( qgsDoubleToString( ( dy - markerSize ) / 2, 2 ), qgsDoubleToString( ( dx - markerSize ) / 2, 2 ) );
|
||||
|
||||
QDomElement graphicMarginElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-margin" ), marginSpec );
|
||||
symbolizerElem.appendChild( graphicMarginElem );
|
||||
|
||||
QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( i );
|
||||
if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
|
||||
{
|
||||
markerLayer->writeSldMarker( doc, graphicFillElem, props );
|
||||
@ -4272,8 +4279,126 @@ void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &elem
|
||||
|
||||
QgsSymbolLayer *QgsPointPatternFillSymbolLayer::createFromSld( QDomElement &element )
|
||||
{
|
||||
Q_UNUSED( element )
|
||||
return nullptr;
|
||||
|
||||
// input element is PolygonSymbolizer
|
||||
|
||||
QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
|
||||
if ( fillElem.isNull() )
|
||||
return nullptr;
|
||||
|
||||
QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
|
||||
if ( graphicFillElem.isNull() )
|
||||
return nullptr;
|
||||
|
||||
QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
|
||||
if ( graphicElem.isNull() )
|
||||
return nullptr;
|
||||
|
||||
QgsSymbolLayer *simpleMarkerSl = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicFillElem );
|
||||
if ( !simpleMarkerSl )
|
||||
return nullptr;
|
||||
|
||||
|
||||
QgsSymbolLayerList layers;
|
||||
layers.append( simpleMarkerSl );
|
||||
|
||||
std::unique_ptr< QgsMarkerSymbol > marker = std::make_unique< QgsMarkerSymbol >( layers );
|
||||
|
||||
// Converts from GeoServer "graphic-margin": symbol size must be added and margin doubled
|
||||
const double markerSize { marker->size() };
|
||||
|
||||
std::unique_ptr< QgsPointPatternFillSymbolLayer > pointPatternFillSl = std::make_unique< QgsPointPatternFillSymbolLayer >();
|
||||
pointPatternFillSl->setSubSymbol( marker.release() );
|
||||
|
||||
// Set distance X and Y from vendor options
|
||||
QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
|
||||
for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
|
||||
{
|
||||
if ( it.key() == QLatin1String( "graphic-margin" ) )
|
||||
{
|
||||
|
||||
// This may not be correct in all cases, TODO: check "uom"
|
||||
pointPatternFillSl->setDistanceXUnit( QgsUnitTypes::RenderUnit::RenderPixels );
|
||||
pointPatternFillSl->setDistanceYUnit( QgsUnitTypes::RenderUnit::RenderPixels );
|
||||
|
||||
const QStringList values = it.value().split( ' ' );
|
||||
|
||||
switch ( values.count( ) )
|
||||
{
|
||||
case 1: // top-right-bottom-left (single value for all four margins)
|
||||
{
|
||||
bool ok;
|
||||
const double v { values.at( 0 ).toDouble( &ok ) };
|
||||
if ( ok )
|
||||
{
|
||||
pointPatternFillSl->setDistanceX( v * 2 + markerSize );
|
||||
pointPatternFillSl->setDistanceY( v * 2 + markerSize );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: // top-bottom,right-left (two values, top and bottom sharing the same value)
|
||||
{
|
||||
bool ok;
|
||||
const double vX { values.at( 1 ).toDouble( &ok ) };
|
||||
if ( ok )
|
||||
{
|
||||
pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
|
||||
}
|
||||
const double vY { values.at( 0 ).toDouble( &ok ) };
|
||||
if ( ok )
|
||||
{
|
||||
pointPatternFillSl->setDistanceY( vY * 2 + markerSize );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: // top,right-left,bottom (three values, with right and left sharing the same value)
|
||||
{
|
||||
bool ok;
|
||||
const double vX { values.at( 1 ).toDouble( &ok ) };
|
||||
if ( ok )
|
||||
{
|
||||
pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
|
||||
}
|
||||
const double vYt { values.at( 0 ).toDouble( &ok ) };
|
||||
if ( ok )
|
||||
{
|
||||
const double vYb { values.at( 2 ).toDouble( &ok ) };
|
||||
if ( ok )
|
||||
{
|
||||
pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: // top,right,bottom,left (one explicit value per margin)
|
||||
{
|
||||
bool ok;
|
||||
const double vYt { values.at( 0 ).toDouble( &ok ) };
|
||||
if ( ok )
|
||||
{
|
||||
const double vYb { values.at( 2 ).toDouble( &ok ) };
|
||||
if ( ok )
|
||||
{
|
||||
pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
|
||||
}
|
||||
}
|
||||
const double vXr { values.at( 1 ).toDouble( &ok ) };
|
||||
if ( ok )
|
||||
{
|
||||
const double vXl { values.at( 3 ).toDouble( &ok ) };
|
||||
if ( ok )
|
||||
{
|
||||
pointPatternFillSl->setDistanceX( ( vXr + vXl ) + markerSize );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pointPatternFillSl.release();
|
||||
}
|
||||
|
||||
bool QgsPointPatternFillSymbolLayer::setSubSymbol( QgsSymbol *symbol )
|
||||
|
@ -1844,8 +1844,23 @@ bool QgsSymbolLayerUtils::needLinePatternFill( QDomElement &element )
|
||||
|
||||
bool QgsSymbolLayerUtils::needPointPatternFill( QDomElement &element )
|
||||
{
|
||||
Q_UNUSED( element )
|
||||
return false;
|
||||
const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
|
||||
if ( fillElem.isNull() )
|
||||
return false;
|
||||
|
||||
const QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
|
||||
if ( graphicFillElem.isNull() )
|
||||
return false;
|
||||
|
||||
const QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
|
||||
if ( graphicElem.isNull() )
|
||||
return false;
|
||||
|
||||
const QDomElement markElem = graphicElem.firstChildElement( QStringLiteral( "Mark" ) );
|
||||
if ( markElem.isNull() )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsSymbolLayerUtils::needSvgFill( QDomElement &element )
|
||||
|
@ -813,7 +813,6 @@ class TestQgsSymbolLayer(unittest.TestCase):
|
||||
self.assertEqual(mSymbolLayer.subSymbol().color(), QColor(250, 150, 200))
|
||||
self.assertEqual(mSymbolLayer.color(), QColor(250, 150, 200))
|
||||
|
||||
@unittest.expectedFailure
|
||||
def testQgsPointPatternFillSymbolLayerSld(self):
|
||||
"""
|
||||
Create a new style from a .sld file and match test
|
||||
|
@ -25,13 +25,16 @@ import qgis # NOQA
|
||||
|
||||
import os
|
||||
from qgis.PyQt.QtXml import QDomDocument
|
||||
from qgis.PyQt.QtCore import QTemporaryDir
|
||||
from qgis.testing import start_app, unittest
|
||||
from qgis.core import (QgsVectorLayer,
|
||||
from qgis.core import (Qgis,
|
||||
QgsVectorLayer,
|
||||
QgsFeature,
|
||||
QgsGeometry,
|
||||
QgsUnitTypes,
|
||||
QgsPointXY,
|
||||
QgsSvgMarkerSymbolLayer,
|
||||
QgsSymbol,
|
||||
QgsEllipseSymbolLayer,
|
||||
QgsSimpleFillSymbolLayer,
|
||||
QgsSVGFillSymbolLayer,
|
||||
@ -466,6 +469,105 @@ class TestQgsSymbolLayerReadSld(unittest.TestCase):
|
||||
self.assertEqual(settings.yOffset, 0)
|
||||
self.assertEqual(settings.offsetUnits, QgsUnitTypes.RenderPixels)
|
||||
|
||||
def test_read_circle(self):
|
||||
"""Test wellknown name circle polygon fill"""
|
||||
|
||||
sld = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<StyledLayerDescriptor xmlns="http://www.opengis.net/sld" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ogc="http://www.opengis.net/ogc" version="1.1.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" xmlns:se="http://www.opengis.net/se">
|
||||
<NamedLayer>
|
||||
<se:Name>Single symbol fill</se:Name>
|
||||
<UserStyle>
|
||||
<se:Name>Single symbol fill</se:Name>
|
||||
<se:FeatureTypeStyle>
|
||||
<se:Rule>
|
||||
<se:Name>Single symbol</se:Name>
|
||||
<se:PolygonSymbolizer>
|
||||
<se:Fill>
|
||||
<se:GraphicFill>
|
||||
<se:Graphic>
|
||||
<se:Mark>
|
||||
<se:WellKnownName>circle</se:WellKnownName>
|
||||
<se:Fill>
|
||||
<se:SvgParameter name="fill">#db1e2a</se:SvgParameter>
|
||||
</se:Fill>
|
||||
<se:Stroke>
|
||||
<se:SvgParameter name="stroke">#801119</se:SvgParameter>
|
||||
<se:SvgParameter name="stroke-width">1.5</se:SvgParameter>
|
||||
</se:Stroke>
|
||||
</se:Mark>
|
||||
<se:Size>14</se:Size>
|
||||
</se:Graphic>
|
||||
</se:GraphicFill>
|
||||
</se:Fill>
|
||||
<sld:VendorOption name="graphic-margin">{}</sld:VendorOption>
|
||||
</se:PolygonSymbolizer>
|
||||
<se:PolygonSymbolizer>
|
||||
<se:Stroke>
|
||||
<se:SvgParameter name="stroke">#ff0000</se:SvgParameter>
|
||||
<se:SvgParameter name="stroke-width">2</se:SvgParameter>
|
||||
<se:SvgParameter name="stroke-linejoin">bevel</se:SvgParameter>
|
||||
</se:Stroke>
|
||||
</se:PolygonSymbolizer>
|
||||
</se:Rule>
|
||||
</se:FeatureTypeStyle>
|
||||
</UserStyle>
|
||||
</NamedLayer>
|
||||
</StyledLayerDescriptor>
|
||||
"""
|
||||
|
||||
tmp_dir = QTemporaryDir()
|
||||
tmp_path = tmp_dir.path()
|
||||
sld_path = os.path.join(tmp_path, 'circle_fill.sld')
|
||||
|
||||
layer = createLayerWithOnePolygon()
|
||||
|
||||
def _check_layer(layer, yMargin=10, xMargin=15):
|
||||
"""
|
||||
- QgsFillSymbol
|
||||
- layers
|
||||
- QgsPointPatternFillSymbolLayer
|
||||
- subSymbol: QgsMarkerSymbol
|
||||
- layers
|
||||
- QgsSimpleMarkerSymbolLayer (shape)
|
||||
"""
|
||||
layer.loadSldStyle(sld_path)
|
||||
point_pattern_fill_symbol_layer = layer.renderer().symbol().symbolLayers()[0]
|
||||
marker = point_pattern_fill_symbol_layer.subSymbol()
|
||||
self.assertEqual(marker.type(), QgsSymbol.SymbolType.Marker)
|
||||
marker_symbol = marker.symbolLayers()[0]
|
||||
self.assertEqual(marker_symbol.strokeColor().name(), '#801119')
|
||||
self.assertEqual(marker_symbol.strokeWidth(), 1.5)
|
||||
self.assertEqual(marker_symbol.shape(), Qgis.MarkerShape.Circle)
|
||||
self.assertEqual(marker_symbol.size(), 14)
|
||||
self.assertEqual(point_pattern_fill_symbol_layer.distanceXUnit(), QgsUnitTypes.RenderUnit.RenderPixels)
|
||||
self.assertEqual(point_pattern_fill_symbol_layer.distanceYUnit(), QgsUnitTypes.RenderUnit.RenderPixels)
|
||||
self.assertEqual(point_pattern_fill_symbol_layer.distanceX(), xMargin * 2 + marker_symbol.size())
|
||||
self.assertEqual(point_pattern_fill_symbol_layer.distanceY(), yMargin * 2 + marker_symbol.size())
|
||||
|
||||
with open(sld_path, 'w+') as f:
|
||||
f.write(sld.format('25'))
|
||||
_check_layer(layer, 25, 25)
|
||||
|
||||
# From: https://docs.geoserver.org/stable/en/user/styling/sld/extensions/margins.html
|
||||
# top,right,bottom,left (one explicit value per margin)
|
||||
# top,right-left,bottom (three values, with right and left sharing the same value)
|
||||
# top-bottom,right-left (two values, top and bottom sharing the same value)
|
||||
# top-right-bottom-left (single value for all four margins)
|
||||
|
||||
for margin in ('10 15', '10 15 10', '10 15 10 15'):
|
||||
with open(sld_path, 'w+') as f:
|
||||
f.write(sld.format(margin))
|
||||
_check_layer(layer)
|
||||
|
||||
# Round trip
|
||||
dom = QDomDocument()
|
||||
root = dom.createElement("FakeRoot")
|
||||
dom.appendChild(root)
|
||||
result = layer.saveSldStyle(sld_path)
|
||||
self.assertTrue(result)
|
||||
|
||||
_check_layer(layer)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user