Skip to content

Commit 82e48f9

Browse files
committed
Added SLD 1.0 export for rasters
1 parent c3819e8 commit 82e48f9

32 files changed

+1744
-38
lines changed

python/core/auto_generated/qgsmaplayer.sip.in

+2-2
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ Export the properties of this layer as named style in a QDomDocument
815815
%End
816816

817817

818-
virtual void exportSldStyle( QDomDocument &doc, QString &errorMsg ) const;
818+
virtual void exportSldStyle( QDomDocument &doc, QString &errorMsg );
819819
%Docstring
820820
Export the properties of this layer as SLD style in a QDomDocument
821821

@@ -856,7 +856,7 @@ record in the users style table in their personal qgis.db)
856856
.. seealso:: :py:func:`saveDefaultStyle`
857857
%End
858858

859-
virtual QString saveSldStyle( const QString &uri, bool &resultFlag ) const;
859+
virtual QString saveSldStyle( const QString &uri, bool &resultFlag );
860860
%Docstring
861861
Saves the properties of this layer to an SLD format file.
862862

python/core/auto_generated/raster/qgscontrastenhancement.sip.in

+11
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,17 @@ By default it will be generated.
126126

127127
void readXml( const QDomElement &elem );
128128

129+
void toSld( QDomDocument &doc, QDomElement &element ) const;
130+
%Docstring
131+
! Write ContrastEnhancement tags following SLD v1.0 specs
132+
SLD1.0 is limited to the parameters listed in:
133+
https://docs.geoserver.org/stable/en/user/styling/sld/reference/rastersymbolizer.html#contrastenhancement
134+
Btw only <Normalize> + vendor options are supported because there is no clear mapping
135+
of ContrastEnhancement parameters to support <Histogram> or <GammaValue>
136+
137+
.. versionadded:: 3.6
138+
%End
139+
129140
private:
130141
const QgsContrastEnhancement &operator=( const QgsContrastEnhancement & );
131142
};

python/core/auto_generated/raster/qgshillshaderenderer.sip.in

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ Factory method to create a new renderer
5757
virtual QList<int> usesBands() const;
5858

5959

60+
virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;
61+
62+
6063
int band() const;
6164
%Docstring
6265
Returns the band used by the renderer

python/core/auto_generated/raster/qgsmultibandcolorrenderer.sip.in

+3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ Takes ownership
6868
virtual QList<int> usesBands() const;
6969

7070

71+
virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;
72+
73+
7174
private:
7275
QgsMultiBandColorRenderer( const QgsMultiBandColorRenderer & );
7376
const QgsMultiBandColorRenderer &operator=( const QgsMultiBandColorRenderer & );

python/core/auto_generated/raster/qgspalettedrasterrenderer.sip.in

+3
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ Returns the raster band used for rendering the raster.
9393
virtual QList<int> usesBands() const;
9494

9595

96+
virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;
97+
98+
9699
void setSourceColorRamp( QgsColorRamp *ramp /Transfer/ );
97100
%Docstring
98101
Set the source color ``ramp``. Ownership is transferred to the renderer.

python/core/auto_generated/raster/qgsrasterlayer.sip.in

+15
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,21 @@ Draws a preview of the rasterlayer into a QImage
362362
virtual QDateTime timestamp() const;
363363

364364

365+
bool writeSld( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsStringMap &props = QgsStringMap() );
366+
%Docstring
367+
Writes the symbology of the layer into the document provided in SLD 1.0.0 format
368+
369+
:param node: the node that will have the style element added to it.
370+
:param doc: the document that will have the QDomNode added.
371+
:param errorMessage: reference to string that will be updated with any error messages
372+
:param props: a open ended set of properties that can drive/inform the SLD encoding
373+
374+
:return: true in case of success
375+
376+
.. versionadded:: 3.6
377+
%End
378+
379+
365380
public slots:
366381
void showStatusMessage( const QString &message );
367382

python/core/auto_generated/raster/qgsrasterrenderer.sip.in

+7
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ Returns const reference to origin of min/max values
111111
void setMinMaxOrigin( const QgsRasterMinMaxOrigin &origin );
112112
%Docstring
113113
Sets origin of min/max values
114+
%End
115+
116+
virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;
117+
%Docstring
118+
! Used from subclasses to create SLD Rule elements following SLD v1.0 specs
119+
120+
.. versionadded:: 3.6
114121
%End
115122

116123
protected:

python/core/auto_generated/raster/qgssinglebandgrayrenderer.sip.in

+3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ Takes ownership
6060
virtual QList<int> usesBands() const;
6161

6262

63+
virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;
64+
65+
6366
private:
6467
QgsSingleBandGrayRenderer( const QgsSingleBandGrayRenderer & );
6568
const QgsSingleBandGrayRenderer &operator=( const QgsSingleBandGrayRenderer & );

python/core/auto_generated/raster/qgssinglebandpseudocolorrenderer.sip.in

+3
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ Creates a color ramp shader
8282
virtual QList<int> usesBands() const;
8383

8484

85+
virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;
86+
87+
8588
int band() const;
8689
%Docstring
8790
Returns the band used by the renderer

src/app/qgsrasterlayerproperties.cpp

+27-5
Original file line numberDiff line numberDiff line change
@@ -1863,20 +1863,42 @@ void QgsRasterLayerProperties::saveStyleAs_clicked()
18631863
this,
18641864
tr( "Save layer properties as style file" ),
18651865
lastUsedDir,
1866-
tr( "QGIS Layer Style File" ) + " (*.qml)" );
1866+
tr( "QGIS Layer Style File" ) + " (*.qml)" + ";;" + tr( "Styled Layer Descriptor" ) + " (*.sld)" );
18671867
if ( outputFileName.isEmpty() )
18681868
return;
18691869

1870-
// ensure the user never omits the extension from the file name
1871-
if ( !outputFileName.endsWith( QLatin1String( ".qml" ), Qt::CaseInsensitive ) )
1872-
outputFileName += QLatin1String( ".qml" );
1870+
// set style type depending on extension
1871+
StyleType type = StyleType::QML;
1872+
if ( outputFileName.endsWith( QLatin1String( ".sld" ), Qt::CaseInsensitive ) )
1873+
type = StyleType::SLD;
1874+
else
1875+
// ensure the user never omits the extension from the file name
1876+
if ( !outputFileName.endsWith( QLatin1String( ".qml" ), Qt::CaseInsensitive ) )
1877+
outputFileName += QLatin1String( ".qml" );
18731878

18741879
apply(); // make sure the style to save is uptodate
18751880

1881+
// then export style
18761882
bool defaultLoadedFlag = false;
1877-
QString message = mRasterLayer->saveNamedStyle( outputFileName, defaultLoadedFlag );
1883+
QString message;
1884+
switch ( type )
1885+
{
1886+
case QML:
1887+
{
1888+
message = mRasterLayer->saveNamedStyle( outputFileName, defaultLoadedFlag );
1889+
break;
1890+
}
1891+
case SLD:
1892+
{
1893+
message = mRasterLayer->saveSldStyle( outputFileName, defaultLoadedFlag );
1894+
break;
1895+
}
1896+
}
18781897
if ( defaultLoadedFlag )
1898+
{
18791899
settings.setValue( QStringLiteral( "style/lastStyleDir" ), QFileInfo( outputFileName ).absolutePath() );
1900+
sync();
1901+
}
18801902
else
18811903
QMessageBox::information( this, tr( "Save Style" ), message );
18821904
}

src/app/qgsrasterlayerproperties.h

+7
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ class APP_EXPORT QgsRasterLayerProperties : public QgsOptionsDialogBase, private
4747

4848
public:
4949

50+
enum StyleType
51+
{
52+
QML,
53+
SLD
54+
};
55+
Q_ENUM( StyleType )
56+
5057
/**
5158
* \brief Constructor
5259
* \param ml Map layer for which properties will be displayed

src/core/qgsmaplayer.cpp

+59-28
Original file line numberDiff line numberDiff line change
@@ -1385,51 +1385,84 @@ QString QgsMapLayer::saveNamedStyle( const QString &uri, bool &resultFlag, Style
13851385
return saveNamedProperty( uri, QgsMapLayer::Style, resultFlag, categories );
13861386
}
13871387

1388-
void QgsMapLayer::exportSldStyle( QDomDocument &doc, QString &errorMsg ) const
1388+
void QgsMapLayer::exportSldStyle( QDomDocument &doc, QString &errorMsg )
13891389
{
13901390
QDomDocument myDocument = QDomDocument();
13911391

13921392
QDomNode header = myDocument.createProcessingInstruction( QStringLiteral( "xml" ), QStringLiteral( "version=\"1.0\" encoding=\"UTF-8\"" ) );
13931393
myDocument.appendChild( header );
13941394

1395-
// Create the root element
1396-
QDomElement root = myDocument.createElementNS( QStringLiteral( "http://www.opengis.net/sld" ), QStringLiteral( "StyledLayerDescriptor" ) );
1397-
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.1.0" ) );
1398-
root.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" ) );
1399-
root.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
1400-
root.setAttribute( QStringLiteral( "xmlns:se" ), QStringLiteral( "http://www.opengis.net/se" ) );
1401-
root.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1402-
root.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
1403-
myDocument.appendChild( root );
1404-
1405-
// Create the NamedLayer element
1406-
QDomElement namedLayerNode = myDocument.createElement( QStringLiteral( "NamedLayer" ) );
1407-
root.appendChild( namedLayerNode );
1408-
14091395
const QgsVectorLayer *vlayer = qobject_cast<const QgsVectorLayer *>( this );
1410-
if ( !vlayer )
1396+
QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( this );
1397+
if (!vlayer && !rlayer)
14111398
{
14121399
errorMsg = tr( "Could not save symbology because:\n%1" )
1413-
.arg( QStringLiteral( "Non-vector layers not supported yet" ) );
1400+
.arg( QStringLiteral( "Non vector or raster layers are supported yet" ) );
14141401
return;
14151402
}
14161403

1404+
// Create the root element
1405+
QDomElement root = myDocument.createElementNS( QStringLiteral( "http://www.opengis.net/sld" ), QStringLiteral( "StyledLayerDescriptor" ) );
1406+
QDomElement layerNode;
1407+
if ( vlayer )
1408+
{
1409+
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.1.0" ) );
1410+
root.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" ) );
1411+
root.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
1412+
root.setAttribute( QStringLiteral( "xmlns:se" ), QStringLiteral( "http://www.opengis.net/se" ) );
1413+
root.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1414+
root.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
1415+
myDocument.appendChild( root );
1416+
1417+
// Create the NamedLayer element
1418+
layerNode = myDocument.createElement( QStringLiteral( "NamedLayer" ) );
1419+
root.appendChild( layerNode );
1420+
}
1421+
1422+
// note rster layer generate only 1.0 SLD version mostly becase seems none is using SE1.1.0 at leasst for rasters
1423+
if ( rlayer )
1424+
{
1425+
// Create the root element
1426+
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0.0" ) );
1427+
root.setAttribute( QStringLiteral( "xmlns:gml" ), QStringLiteral( "http://www.opengis.net/gml" ) );
1428+
root.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
1429+
root.setAttribute( QStringLiteral( "xmlns:sld" ), QStringLiteral( "http://www.opengis.net/sld" ) );
1430+
myDocument.appendChild( root );
1431+
1432+
// Create the NamedLayer element
1433+
layerNode = myDocument.createElement( QStringLiteral( "UserLayer" ) );
1434+
root.appendChild( layerNode );
1435+
}
1436+
14171437
QgsStringMap props;
14181438
if ( hasScaleBasedVisibility() )
14191439
{
1420-
props[ QStringLiteral( "scaleMinDenom" )] = QString::number( mMinScale );
1421-
props[ QStringLiteral( "scaleMaxDenom" )] = QString::number( mMaxScale );
1440+
props[ QStringLiteral( "scaleMinDenom" ) ] = QString::number( mMinScale );
1441+
props[ QStringLiteral( "scaleMaxDenom" ) ] = QString::number( mMaxScale );
14221442
}
1423-
if ( !vlayer->writeSld( namedLayerNode, myDocument, errorMsg, props ) )
1443+
1444+
if (vlayer)
14241445
{
1425-
errorMsg = tr( "Could not save symbology because:\n%1" ).arg( errorMsg );
1426-
return;
1446+
if ( !vlayer->writeSld( layerNode, myDocument, errorMsg, props ) )
1447+
{
1448+
errorMsg = tr( "Could not save symbology because:\n%1" ).arg( errorMsg );
1449+
return;
1450+
}
1451+
}
1452+
1453+
if (rlayer)
1454+
{
1455+
if ( !rlayer->writeSld( layerNode, myDocument, errorMsg, props ) )
1456+
{
1457+
errorMsg = tr( "Could not save symbology because:\n%1" ).arg( errorMsg );
1458+
return;
1459+
}
14271460
}
14281461

14291462
doc = myDocument;
14301463
}
14311464

1432-
QString QgsMapLayer::saveSldStyle( const QString &uri, bool &resultFlag ) const
1465+
QString QgsMapLayer::saveSldStyle( const QString &uri, bool &resultFlag )
14331466
{
14341467
QString errorMsg;
14351468
QDomDocument myDocument;
@@ -1439,22 +1472,20 @@ QString QgsMapLayer::saveSldStyle( const QString &uri, bool &resultFlag ) const
14391472
resultFlag = false;
14401473
return errorMsg;
14411474
}
1442-
const QgsVectorLayer *vlayer = qobject_cast<const QgsVectorLayer *>( this );
1443-
14441475
// check if the uri is a file or ends with .sld,
14451476
// which indicates that it should become one
14461477
QString filename;
1447-
if ( vlayer->providerType() == QLatin1String( "ogr" ) )
1478+
if ( providerType() == QLatin1String( "ogr" ) )
14481479
{
14491480
QStringList theURIParts = uri.split( '|' );
14501481
filename = theURIParts[0];
14511482
}
1452-
else if ( vlayer->providerType() == QLatin1String( "gpx" ) )
1483+
else if ( providerType() == QLatin1String( "gpx" ) )
14531484
{
14541485
QStringList theURIParts = uri.split( '?' );
14551486
filename = theURIParts[0];
14561487
}
1457-
else if ( vlayer->providerType() == QLatin1String( "delimitedtext" ) )
1488+
else if ( providerType() == QLatin1String( "delimitedtext" ) )
14581489
{
14591490
filename = QUrl::fromEncoded( uri.toLatin1() ).toLocalFile();
14601491
// toLocalFile() returns an empty string if theURI is a plain Windows-path, e.g. "C:/style.qml"

src/core/qgsmaplayer.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ class CORE_EXPORT QgsMapLayer : public QObject
780780
* \param errorMsg this QString will be initialized on error
781781
* during the execution of writeSymbology
782782
*/
783-
virtual void exportSldStyle( QDomDocument &doc, QString &errorMsg ) const;
783+
virtual void exportSldStyle( QDomDocument &doc, QString &errorMsg );
784784

785785
/**
786786
* Save the properties of this layer as the default style
@@ -818,7 +818,7 @@ class CORE_EXPORT QgsMapLayer : public QObject
818818
* \returns a string with any status or error messages
819819
* \see loadSldStyle()
820820
*/
821-
virtual QString saveSldStyle( const QString &uri, bool &resultFlag ) const;
821+
virtual QString saveSldStyle( const QString &uri, bool &resultFlag );
822822

823823
/**
824824
* Attempts to style the layer using the formatting from an SLD type file.

src/core/raster/qgscontrastenhancement.cpp

+48
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,54 @@ void QgsContrastEnhancement::readXml( const QDomElement &elem )
377377
}
378378
}
379379

380+
void QgsContrastEnhancement::toSld( QDomDocument &doc, QDomElement &element ) const
381+
{
382+
if ( doc.isNull() || element.isNull() )
383+
return;
384+
385+
QString algName;
386+
switch( contrastEnhancementAlgorithm() )
387+
{
388+
case StretchToMinimumMaximum:
389+
algName = QStringLiteral( "StretchToMinimumMaximum" );
390+
break;
391+
/* TODO: check if ClipToZero => StretchAndClipToMinimumMaximum
392+
* because value outside min/max ar considered as NoData instead of 0 */
393+
case StretchAndClipToMinimumMaximum:
394+
algName = QStringLiteral( "ClipToMinimumMaximum" );
395+
break;
396+
case ClipToMinimumMaximum:
397+
algName = QStringLiteral( "ClipToMinimumMaximum" );
398+
break;
399+
case NoEnhancement:
400+
return;
401+
default:
402+
QgsDebugMsgLevel( QStringLiteral( "No SLD1.0 convertion yet for stretch algorithm %1" ).arg( contrastEnhancementAlgorithmString( contrastEnhancementAlgorithm() ) ), 4 );
403+
return;
404+
}
405+
406+
// Only <Normalize> is supported
407+
// minValue and maxValue are that values as set depending on "Min /Max value settings"
408+
// parameters
409+
QDomElement normalizeElem = doc.createElement( QStringLiteral( "sld:Normalize" ) );
410+
element.appendChild( normalizeElem );
411+
412+
QDomElement vendorOptionAlgorithmElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
413+
vendorOptionAlgorithmElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "algorithm" ) );
414+
vendorOptionAlgorithmElem.appendChild( doc.createTextNode( algName ) );
415+
normalizeElem.appendChild( vendorOptionAlgorithmElem );
416+
417+
QDomElement vendorOptionMinValueElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
418+
vendorOptionMinValueElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "minValue" ) );
419+
vendorOptionMinValueElem.appendChild( doc.createTextNode( QString::number( minimumValue() ) ) );
420+
normalizeElem.appendChild( vendorOptionMinValueElem );
421+
422+
QDomElement vendorOptionMaxValueElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
423+
vendorOptionMaxValueElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "maxValue" ) );
424+
vendorOptionMaxValueElem.appendChild( doc.createTextNode( QString::number( maximumValue() ) ) );
425+
normalizeElem.appendChild( vendorOptionMaxValueElem );
426+
}
427+
380428
QString QgsContrastEnhancement::contrastEnhancementAlgorithmString( ContrastEnhancementAlgorithm algorithm )
381429
{
382430
switch ( algorithm )

0 commit comments

Comments
 (0)