Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #51622 from elpaso/sld-fix-raster-external-graphic…
…-loading

Fix SLD loading when using external graphic raster image fill
  • Loading branch information
elpaso committed Jan 30, 2023
2 parents 458c2a8 + 0c48209 commit 339d41e
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 12 deletions.
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
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
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
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
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
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
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

0 comments on commit 339d41e

Please sign in to comment.