Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SLD loading when using external graphic raster image fill #51622

Merged
merged 3 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,14 @@ specified ``imageFilePath``.
%Docstring
Creates a new QgsRasterFillSymbolLayer from a ``properties`` map. The caller takes
ownership of the returned object.
%End

static QgsSymbolLayer *createFromSld( QDomElement &element ) /Factory/;
%Docstring
Creates a new QgsRasterFillSymbolLayer from a SLD ``element``. The caller takes
ownership of the returned object.

.. versionadded:: 3.30
%End

static void resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving );
Expand Down
22 changes: 22 additions & 0 deletions python/core/auto_generated/symbology/qgssymbollayerutils.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,21 @@ Creates a symbol layer list from a DOM ``element``.
%Docstring
Converts a polygon symbolizer ``element`` to a list of marker symbol layers.
%End

static bool hasExternalGraphic( QDomElement &element );
%Docstring
Checks if ``element`` contains an ExternalGraphic element with format "image/svg+xml"
@return ``True`` if the ExternalGraphic with format "image/svg+xml" is found .
%End

static bool hasExternalGraphicV2( QDomElement &element, const QString format = QString() );
%Docstring
Checks if ``element`` contains an ExternalGraphic element, if the optional ``format`` is specified it will also be checked.
@return ``True`` if the ExternalGraphic element is found and the optionally specified format matches.

.. versionadded:: 3.30
%End

static bool hasWellKnownMark( QDomElement &element );

static bool needFontMarker( QDomElement &element );
Expand All @@ -448,6 +462,14 @@ Converts a polygon symbolizer ``element`` to a list of marker symbol layers.
static bool needPointPatternFill( QDomElement &element );
static bool needSvgFill( QDomElement &element );

static bool needRasterImageFill( QDomElement &element );
%Docstring
Checks if ``element`` contains a graphic fill with a raster image of type PNG, JPEG or GIF.
@return ``True`` if element contains a graphic fill with a raster image.

.. versionadded:: 3.30
%End

static void fillToSld( QDomDocument &doc, QDomElement &element,
Qt::BrushStyle brushStyle, const QColor &color = QColor() );
static bool fillFromSld( QDomElement &element,
Expand Down
76 changes: 70 additions & 6 deletions src/core/symbology/qgsfillsymbollayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4416,6 +4416,20 @@ QImage QgsPointPatternFillSymbolLayer::toTiledPatternImage() const
int distanceXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceX, mDistanceXUnit, {} ) ) };
int distanceYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceY, mDistanceYUnit, {} ) ) };

const int displacementXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementX, mDisplacementXUnit, {} ) ) };
const int displacementYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementY, mDisplacementYUnit, {} ) ) };

// Consider displacement, double the distance.
if ( displacementXPx != 0 )
{
distanceXPx *= 2;
}

if ( displacementYPx != 0 )
{
distanceYPx *= 2;
}

const QSize size { QgsSymbolLayerUtils::tileSize( distanceXPx, distanceYPx, angleRads ) };

QPixmap pixmap( size );
Expand All @@ -4434,6 +4448,10 @@ QImage QgsPointPatternFillSymbolLayer::toTiledPatternImage() const

layerClone->setAngle( qRadiansToDegrees( angleRads ) );

// No way we can export a random pattern, disable it.
layerClone->setMaximumRandomDeviationX( 0 );
layerClone->setMaximumRandomDeviationY( 0 );

layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
painter.end();
return pixmap.toImage();
Expand Down Expand Up @@ -4471,14 +4489,12 @@ QgsSymbolLayer *QgsPointPatternFillSymbolLayer::createFromSld( QDomElement &elem

std::unique_ptr< QgsPointPatternFillSymbolLayer > pointPatternFillSl = std::make_unique< QgsPointPatternFillSymbolLayer >();
pointPatternFillSl->setSubSymbol( marker.release() );
// This may not be correct in all cases, TODO: check "uom"
pointPatternFillSl->setDistanceXUnit( QgsUnitTypes::RenderUnit::RenderPixels );
pointPatternFillSl->setDistanceYUnit( QgsUnitTypes::RenderUnit::RenderPixels );

auto distanceParser = [ & ]( const QStringList & values )
{

// This may not be correct in all cases, TODO: check "uom"
pointPatternFillSl->setDistanceXUnit( QgsUnitTypes::RenderUnit::RenderPixels );
pointPatternFillSl->setDistanceYUnit( QgsUnitTypes::RenderUnit::RenderPixels );

switch ( values.count( ) )
{
case 1: // top-right-bottom-left (single value for all four margins)
Expand Down Expand Up @@ -4554,22 +4570,38 @@ QgsSymbolLayer *QgsPointPatternFillSymbolLayer::createFromSld( QDomElement &elem
}
};

// Set distance X and Y from vendor options
// Set distance X and Y from vendor options, or from Size if no vendor options are set
bool distanceFromVendorOption { false };
QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
{
// Legacy
if ( it.key() == QLatin1String( "distance" ) )
{
distanceParser( it.value().split( ',' ) );
distanceFromVendorOption = true;
}
// GeoServer
else if ( it.key() == QLatin1String( "graphic-margin" ) )
{
distanceParser( it.value().split( ' ' ) );
distanceFromVendorOption = true;
}
}

// Get distances from size
if ( ! distanceFromVendorOption && ! graphicFillElem.elementsByTagName( QStringLiteral( "Size" ) ).isEmpty() )
{
const QDomElement sizeElement { graphicFillElem.elementsByTagName( QStringLiteral( "Size" ) ).at( 0 ).toElement() };
bool ok;
const double size { sizeElement.text().toDouble( &ok ) };
if ( ok )
{
pointPatternFillSl->setDistanceX( size );
pointPatternFillSl->setDistanceY( size );
}
}

return pointPatternFillSl.release();
}

Expand Down Expand Up @@ -5072,6 +5104,38 @@ QgsSymbolLayer *QgsRasterFillSymbolLayer::create( const QVariantMap &properties
return symbolLayer.release();
}

QgsSymbolLayer *QgsRasterFillSymbolLayer::createFromSld( QDomElement &element )
{
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;

QString path, mimeType;
double size;
QColor fillColor;

if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
return nullptr;

// Try to correct the path, this is a wild guess but we have not access to the SLD path here.
if ( ! QFile::exists( path ) )
{
path = QgsProject::instance()->pathResolver().readPath( path );
}

std::unique_ptr< QgsRasterFillSymbolLayer> sl = std::make_unique< QgsRasterFillSymbolLayer>( path );

return sl.release();
}

void QgsRasterFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
{
QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
Expand Down
7 changes: 7 additions & 0 deletions src/core/symbology/qgsfillsymbollayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,13 @@ class CORE_EXPORT QgsRasterFillSymbolLayer: public QgsImageFillSymbolLayer
*/
static QgsSymbolLayer *create( const QVariantMap &properties = QVariantMap() ) SIP_FACTORY;

/**
* Creates a new QgsRasterFillSymbolLayer from a SLD \a element. The caller takes
* ownership of the returned object.
* \since QGIS 3.30
*/
static QgsSymbolLayer *createFromSld( QDomElement &element ) SIP_FACTORY;

/**
* Turns relative paths in properties map to absolute when reading and vice versa when writing.
* Used internally when reading/writing symbols.
Expand Down
2 changes: 1 addition & 1 deletion src/core/symbology/qgssymbollayerregistry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ QgsSymbolLayerRegistry::QgsSymbolLayerRegistry()
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "ShapeburstFill" ), QObject::tr( "Shapeburst Fill" ), Qgis::SymbolType::Fill,
QgsShapeburstFillSymbolLayer::create ) );
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "RasterFill" ), QObject::tr( "Raster Image Fill" ), Qgis::SymbolType::Fill,
QgsRasterFillSymbolLayer::create, nullptr, QgsRasterFillSymbolLayer::resolvePaths ) );
QgsRasterFillSymbolLayer::create, QgsRasterFillSymbolLayer::createFromSld, QgsRasterFillSymbolLayer::resolvePaths ) );
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "SVGFill" ), QObject::tr( "SVG Fill" ), Qgis::SymbolType::Fill,
QgsSVGFillSymbolLayer::create, QgsSVGFillSymbolLayer::createFromSld, QgsSVGFillSymbolLayer::resolvePaths ) );
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "CentroidFill" ), QObject::tr( "Centroid Fill" ), Qgis::SymbolType::Fill,
Expand Down
30 changes: 25 additions & 5 deletions src/core/symbology/qgssymbollayerutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1628,6 +1628,8 @@ QgsSymbolLayer *QgsSymbolLayerUtils::createFillLayerFromSld( QDomElement &elemen
l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "PointPatternFill" ), element );
else if ( needSvgFill( element ) )
l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SVGFill" ), element );
else if ( needRasterImageFill( element ) )
l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "RasterFill" ), element );
else
l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SimpleFill" ), element );

Expand Down Expand Up @@ -1677,6 +1679,11 @@ QgsSymbolLayer *QgsSymbolLayerUtils::createMarkerLayerFromSld( QDomElement &elem
}

bool QgsSymbolLayerUtils::hasExternalGraphic( QDomElement &element )
{
return hasExternalGraphicV2( element, QStringLiteral( "image/svg+xml" ) );
}

bool QgsSymbolLayerUtils::hasExternalGraphicV2( QDomElement &element, const QString format )
{
const QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
if ( graphicElem.isNull() )
Expand All @@ -1691,10 +1698,10 @@ bool QgsSymbolLayerUtils::hasExternalGraphic( QDomElement &element )
if ( formatElem.isNull() )
return false;

const QString format = formatElem.firstChild().nodeValue();
if ( format != QLatin1String( "image/svg+xml" ) )
const QString elementFormat = formatElem.firstChild().nodeValue();
if ( ! format.isEmpty() && elementFormat != format )
{
QgsDebugMsg( "unsupported External Graphic format found: " + format );
QgsDebugMsgLevel( "unsupported External Graphic format found: " + elementFormat, 4 );
return false;
}

Expand Down Expand Up @@ -1774,7 +1781,7 @@ bool QgsSymbolLayerUtils::needFontMarker( QDomElement &element )

bool QgsSymbolLayerUtils::needSvgMarker( QDomElement &element )
{
return hasExternalGraphic( element );
return hasExternalGraphicV2( element, QStringLiteral( "image/svg+xml" ) );
}

bool QgsSymbolLayerUtils::needEllipseMarker( QDomElement &element )
Expand Down Expand Up @@ -1874,7 +1881,20 @@ bool QgsSymbolLayerUtils::needSvgFill( QDomElement &element )
if ( graphicFillElem.isNull() )
return false;

return hasExternalGraphic( graphicFillElem );
return hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/svg+xml" ) );
}

bool QgsSymbolLayerUtils::needRasterImageFill( QDomElement &element )
{
const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
if ( fillElem.isNull() )
return false;

QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
if ( graphicFillElem.isNull() )
return false;

return hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/png" ) ) || hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/jpeg" ) ) || hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/gif" ) );
}


Expand Down
20 changes: 20 additions & 0 deletions src/core/symbology/qgssymbollayerutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,20 @@ class CORE_EXPORT QgsSymbolLayerUtils
* Converts a polygon symbolizer \a element to a list of marker symbol layers.
*/
static bool convertPolygonSymbolizerToPointMarker( QDomElement &element, QList<QgsSymbolLayer *> &layerList );

/**
* Checks if \a element contains an ExternalGraphic element with format "image/svg+xml"
* @return TRUE if the ExternalGraphic with format "image/svg+xml" is found .
*/
static bool hasExternalGraphic( QDomElement &element );

/**
* Checks if \a element contains an ExternalGraphic element, if the optional \a format is specified it will also be checked.
* @return TRUE if the ExternalGraphic element is found and the optionally specified format matches.
* \since QGIS 3.30
*/
static bool hasExternalGraphicV2( QDomElement &element, const QString format = QString() );

static bool hasWellKnownMark( QDomElement &element );

static bool needFontMarker( QDomElement &element );
Expand All @@ -433,6 +446,13 @@ class CORE_EXPORT QgsSymbolLayerUtils
static bool needPointPatternFill( QDomElement &element );
static bool needSvgFill( QDomElement &element );

/**
* Checks if \a element contains a graphic fill with a raster image of type PNG, JPEG or GIF.
* @return TRUE if element contains a graphic fill with a raster image.
* \since QGIS 3.30
*/
static bool needRasterImageFill( QDomElement &element );

static void fillToSld( QDomDocument &doc, QDomElement &element,
Qt::BrushStyle brushStyle, const QColor &color = QColor() );
static bool fillFromSld( QDomElement &element,
Expand Down
2 changes: 1 addition & 1 deletion src/gui/qgsmapcanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1252,7 +1252,7 @@ void QgsMapCanvas::onElevationShadingRendererChanged()
return;
bool wasDeactivated = !mSettings.elevationShadingRenderer().isActive();
mSettings.setElevationShadingRenderer( mProject->elevationShadingRenderer() );
if ( wasDeactivated )
if ( mCache && wasDeactivated )
mCache->clear();
refresh();
}
Expand Down