Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Added SLD 1.0 export for rasters
  • Loading branch information
luipir committed Jan 31, 2019
1 parent c3819e8 commit 82e48f9
Show file tree
Hide file tree
Showing 32 changed files with 1,744 additions and 38 deletions.
4 changes: 2 additions & 2 deletions python/core/auto_generated/qgsmaplayer.sip.in
Expand Up @@ -815,7 +815,7 @@ Export the properties of this layer as named style in a QDomDocument
%End


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

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

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

Expand Down
11 changes: 11 additions & 0 deletions python/core/auto_generated/raster/qgscontrastenhancement.sip.in
Expand Up @@ -126,6 +126,17 @@ By default it will be generated.

void readXml( const QDomElement &elem );

void toSld( QDomDocument &doc, QDomElement &element ) const;
%Docstring
! Write ContrastEnhancement tags following SLD v1.0 specs
SLD1.0 is limited to the parameters listed in:
https://docs.geoserver.org/stable/en/user/styling/sld/reference/rastersymbolizer.html#contrastenhancement
Btw only <Normalize> + vendor options are supported because there is no clear mapping
of ContrastEnhancement parameters to support <Histogram> or <GammaValue>

.. versionadded:: 3.6
%End

private:
const QgsContrastEnhancement &operator=( const QgsContrastEnhancement & );
};
Expand Down
3 changes: 3 additions & 0 deletions python/core/auto_generated/raster/qgshillshaderenderer.sip.in
Expand Up @@ -57,6 +57,9 @@ Factory method to create a new renderer
virtual QList<int> usesBands() const;


virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;


int band() const;
%Docstring
Returns the band used by the renderer
Expand Down
Expand Up @@ -68,6 +68,9 @@ Takes ownership
virtual QList<int> usesBands() const;


virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;


private:
QgsMultiBandColorRenderer( const QgsMultiBandColorRenderer & );
const QgsMultiBandColorRenderer &operator=( const QgsMultiBandColorRenderer & );
Expand Down
Expand Up @@ -93,6 +93,9 @@ Returns the raster band used for rendering the raster.
virtual QList<int> usesBands() const;


virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;


void setSourceColorRamp( QgsColorRamp *ramp /Transfer/ );
%Docstring
Set the source color ``ramp``. Ownership is transferred to the renderer.
Expand Down
15 changes: 15 additions & 0 deletions python/core/auto_generated/raster/qgsrasterlayer.sip.in
Expand Up @@ -362,6 +362,21 @@ Draws a preview of the rasterlayer into a QImage
virtual QDateTime timestamp() const;


bool writeSld( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsStringMap &props = QgsStringMap() );
%Docstring
Writes the symbology of the layer into the document provided in SLD 1.0.0 format

:param node: the node that will have the style element added to it.
:param doc: the document that will have the QDomNode added.
:param errorMessage: reference to string that will be updated with any error messages
:param props: a open ended set of properties that can drive/inform the SLD encoding

:return: true in case of success

.. versionadded:: 3.6
%End


public slots:
void showStatusMessage( const QString &message );

Expand Down
7 changes: 7 additions & 0 deletions python/core/auto_generated/raster/qgsrasterrenderer.sip.in
Expand Up @@ -111,6 +111,13 @@ Returns const reference to origin of min/max values
void setMinMaxOrigin( const QgsRasterMinMaxOrigin &origin );
%Docstring
Sets origin of min/max values
%End

virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;
%Docstring
! Used from subclasses to create SLD Rule elements following SLD v1.0 specs

.. versionadded:: 3.6
%End

protected:
Expand Down
Expand Up @@ -60,6 +60,9 @@ Takes ownership
virtual QList<int> usesBands() const;


virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;


private:
QgsSingleBandGrayRenderer( const QgsSingleBandGrayRenderer & );
const QgsSingleBandGrayRenderer &operator=( const QgsSingleBandGrayRenderer & );
Expand Down
Expand Up @@ -82,6 +82,9 @@ Creates a color ramp shader
virtual QList<int> usesBands() const;


virtual void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const;


int band() const;
%Docstring
Returns the band used by the renderer
Expand Down
32 changes: 27 additions & 5 deletions src/app/qgsrasterlayerproperties.cpp
Expand Up @@ -1863,20 +1863,42 @@ void QgsRasterLayerProperties::saveStyleAs_clicked()
this,
tr( "Save layer properties as style file" ),
lastUsedDir,
tr( "QGIS Layer Style File" ) + " (*.qml)" );
tr( "QGIS Layer Style File" ) + " (*.qml)" + ";;" + tr( "Styled Layer Descriptor" ) + " (*.sld)" );
if ( outputFileName.isEmpty() )
return;

// ensure the user never omits the extension from the file name
if ( !outputFileName.endsWith( QLatin1String( ".qml" ), Qt::CaseInsensitive ) )
outputFileName += QLatin1String( ".qml" );
// set style type depending on extension
StyleType type = StyleType::QML;
if ( outputFileName.endsWith( QLatin1String( ".sld" ), Qt::CaseInsensitive ) )
type = StyleType::SLD;
else
// ensure the user never omits the extension from the file name
if ( !outputFileName.endsWith( QLatin1String( ".qml" ), Qt::CaseInsensitive ) )
outputFileName += QLatin1String( ".qml" );

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

// then export style
bool defaultLoadedFlag = false;
QString message = mRasterLayer->saveNamedStyle( outputFileName, defaultLoadedFlag );
QString message;
switch ( type )
{
case QML:
{
message = mRasterLayer->saveNamedStyle( outputFileName, defaultLoadedFlag );
break;
}
case SLD:
{
message = mRasterLayer->saveSldStyle( outputFileName, defaultLoadedFlag );
break;
}
}
if ( defaultLoadedFlag )
{
settings.setValue( QStringLiteral( "style/lastStyleDir" ), QFileInfo( outputFileName ).absolutePath() );
sync();
}
else
QMessageBox::information( this, tr( "Save Style" ), message );
}
Expand Down
7 changes: 7 additions & 0 deletions src/app/qgsrasterlayerproperties.h
Expand Up @@ -47,6 +47,13 @@ class APP_EXPORT QgsRasterLayerProperties : public QgsOptionsDialogBase, private

public:

enum StyleType
{
QML,
SLD
};
Q_ENUM( StyleType )

/**
* \brief Constructor
* \param ml Map layer for which properties will be displayed
Expand Down
87 changes: 59 additions & 28 deletions src/core/qgsmaplayer.cpp
Expand Up @@ -1385,51 +1385,84 @@ QString QgsMapLayer::saveNamedStyle( const QString &uri, bool &resultFlag, Style
return saveNamedProperty( uri, QgsMapLayer::Style, resultFlag, categories );
}

void QgsMapLayer::exportSldStyle( QDomDocument &doc, QString &errorMsg ) const
void QgsMapLayer::exportSldStyle( QDomDocument &doc, QString &errorMsg )
{
QDomDocument myDocument = QDomDocument();

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

// Create the root element
QDomElement root = myDocument.createElementNS( QStringLiteral( "http://www.opengis.net/sld" ), QStringLiteral( "StyledLayerDescriptor" ) );
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.1.0" ) );
root.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" ) );
root.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
root.setAttribute( QStringLiteral( "xmlns:se" ), QStringLiteral( "http://www.opengis.net/se" ) );
root.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
root.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
myDocument.appendChild( root );

// Create the NamedLayer element
QDomElement namedLayerNode = myDocument.createElement( QStringLiteral( "NamedLayer" ) );
root.appendChild( namedLayerNode );

const QgsVectorLayer *vlayer = qobject_cast<const QgsVectorLayer *>( this );
if ( !vlayer )
QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( this );
if (!vlayer && !rlayer)
{
errorMsg = tr( "Could not save symbology because:\n%1" )
.arg( QStringLiteral( "Non-vector layers not supported yet" ) );
.arg( QStringLiteral( "Non vector or raster layers are supported yet" ) );
return;
}

// Create the root element
QDomElement root = myDocument.createElementNS( QStringLiteral( "http://www.opengis.net/sld" ), QStringLiteral( "StyledLayerDescriptor" ) );
QDomElement layerNode;
if ( vlayer )
{
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.1.0" ) );
root.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" ) );
root.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
root.setAttribute( QStringLiteral( "xmlns:se" ), QStringLiteral( "http://www.opengis.net/se" ) );
root.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
root.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
myDocument.appendChild( root );

// Create the NamedLayer element
layerNode = myDocument.createElement( QStringLiteral( "NamedLayer" ) );
root.appendChild( layerNode );
}

// note rster layer generate only 1.0 SLD version mostly becase seems none is using SE1.1.0 at leasst for rasters
if ( rlayer )
{
// Create the root element
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0.0" ) );
root.setAttribute( QStringLiteral( "xmlns:gml" ), QStringLiteral( "http://www.opengis.net/gml" ) );
root.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
root.setAttribute( QStringLiteral( "xmlns:sld" ), QStringLiteral( "http://www.opengis.net/sld" ) );
myDocument.appendChild( root );

// Create the NamedLayer element
layerNode = myDocument.createElement( QStringLiteral( "UserLayer" ) );
root.appendChild( layerNode );
}

QgsStringMap props;
if ( hasScaleBasedVisibility() )
{
props[ QStringLiteral( "scaleMinDenom" )] = QString::number( mMinScale );
props[ QStringLiteral( "scaleMaxDenom" )] = QString::number( mMaxScale );
props[ QStringLiteral( "scaleMinDenom" ) ] = QString::number( mMinScale );
props[ QStringLiteral( "scaleMaxDenom" ) ] = QString::number( mMaxScale );
}
if ( !vlayer->writeSld( namedLayerNode, myDocument, errorMsg, props ) )

if (vlayer)
{
errorMsg = tr( "Could not save symbology because:\n%1" ).arg( errorMsg );
return;
if ( !vlayer->writeSld( layerNode, myDocument, errorMsg, props ) )
{
errorMsg = tr( "Could not save symbology because:\n%1" ).arg( errorMsg );
return;
}
}

if (rlayer)
{
if ( !rlayer->writeSld( layerNode, myDocument, errorMsg, props ) )
{
errorMsg = tr( "Could not save symbology because:\n%1" ).arg( errorMsg );
return;
}
}

doc = myDocument;
}

QString QgsMapLayer::saveSldStyle( const QString &uri, bool &resultFlag ) const
QString QgsMapLayer::saveSldStyle( const QString &uri, bool &resultFlag )
{
QString errorMsg;
QDomDocument myDocument;
Expand All @@ -1439,22 +1472,20 @@ QString QgsMapLayer::saveSldStyle( const QString &uri, bool &resultFlag ) const
resultFlag = false;
return errorMsg;
}
const QgsVectorLayer *vlayer = qobject_cast<const QgsVectorLayer *>( this );

// check if the uri is a file or ends with .sld,
// which indicates that it should become one
QString filename;
if ( vlayer->providerType() == QLatin1String( "ogr" ) )
if ( providerType() == QLatin1String( "ogr" ) )
{
QStringList theURIParts = uri.split( '|' );
filename = theURIParts[0];
}
else if ( vlayer->providerType() == QLatin1String( "gpx" ) )
else if ( providerType() == QLatin1String( "gpx" ) )
{
QStringList theURIParts = uri.split( '?' );
filename = theURIParts[0];
}
else if ( vlayer->providerType() == QLatin1String( "delimitedtext" ) )
else if ( providerType() == QLatin1String( "delimitedtext" ) )
{
filename = QUrl::fromEncoded( uri.toLatin1() ).toLocalFile();
// toLocalFile() returns an empty string if theURI is a plain Windows-path, e.g. "C:/style.qml"
Expand Down
4 changes: 2 additions & 2 deletions src/core/qgsmaplayer.h
Expand Up @@ -780,7 +780,7 @@ class CORE_EXPORT QgsMapLayer : public QObject
* \param errorMsg this QString will be initialized on error
* during the execution of writeSymbology
*/
virtual void exportSldStyle( QDomDocument &doc, QString &errorMsg ) const;
virtual void exportSldStyle( QDomDocument &doc, QString &errorMsg );

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

/**
* Attempts to style the layer using the formatting from an SLD type file.
Expand Down
48 changes: 48 additions & 0 deletions src/core/raster/qgscontrastenhancement.cpp
Expand Up @@ -377,6 +377,54 @@ void QgsContrastEnhancement::readXml( const QDomElement &elem )
}
}

void QgsContrastEnhancement::toSld( QDomDocument &doc, QDomElement &element ) const
{
if ( doc.isNull() || element.isNull() )
return;

QString algName;
switch( contrastEnhancementAlgorithm() )
{
case StretchToMinimumMaximum:
algName = QStringLiteral( "StretchToMinimumMaximum" );
break;
/* TODO: check if ClipToZero => StretchAndClipToMinimumMaximum
* because value outside min/max ar considered as NoData instead of 0 */
case StretchAndClipToMinimumMaximum:
algName = QStringLiteral( "ClipToMinimumMaximum" );
break;
case ClipToMinimumMaximum:
algName = QStringLiteral( "ClipToMinimumMaximum" );
break;
case NoEnhancement:
return;
default:
QgsDebugMsgLevel( QStringLiteral( "No SLD1.0 convertion yet for stretch algorithm %1" ).arg( contrastEnhancementAlgorithmString( contrastEnhancementAlgorithm() ) ), 4 );
return;
}

// Only <Normalize> is supported
// minValue and maxValue are that values as set depending on "Min /Max value settings"
// parameters
QDomElement normalizeElem = doc.createElement( QStringLiteral( "sld:Normalize" ) );
element.appendChild( normalizeElem );

QDomElement vendorOptionAlgorithmElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
vendorOptionAlgorithmElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "algorithm" ) );
vendorOptionAlgorithmElem.appendChild( doc.createTextNode( algName ) );
normalizeElem.appendChild( vendorOptionAlgorithmElem );

QDomElement vendorOptionMinValueElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
vendorOptionMinValueElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "minValue" ) );
vendorOptionMinValueElem.appendChild( doc.createTextNode( QString::number( minimumValue() ) ) );
normalizeElem.appendChild( vendorOptionMinValueElem );

QDomElement vendorOptionMaxValueElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
vendorOptionMaxValueElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "maxValue" ) );
vendorOptionMaxValueElem.appendChild( doc.createTextNode( QString::number( maximumValue() ) ) );
normalizeElem.appendChild( vendorOptionMaxValueElem );
}

QString QgsContrastEnhancement::contrastEnhancementAlgorithmString( ContrastEnhancementAlgorithm algorithm )
{
switch ( algorithm )
Expand Down

0 comments on commit 82e48f9

Please sign in to comment.