Skip to content

Commit

Permalink
Merge pull request #9162 from luipir/rasterSLD_export_backport-3_4
Browse files Browse the repository at this point in the history
Backport of Add SLD 1.0 export for rasters
  • Loading branch information
luipir authored Feb 19, 2019
2 parents 223d26b + 754b2e7 commit c88e7ab
Show file tree
Hide file tree
Showing 30 changed files with 1,749 additions and 33 deletions.
11 changes: 11 additions & 0 deletions python/core/auto_generated/raster/qgscontrastenhancement.sip.in
Original file line number Diff line number Diff line change
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 sld:Normalize + vendor options are supported because there is no clear mapping
of ContrastEnhancement parameters to support sld:Histogram or sld:GammaValue

.. versionadded:: 3.4.5
%End

private:
const QgsContrastEnhancement &operator=( const QgsContrastEnhancement & );
};
Expand Down
3 changes: 3 additions & 0 deletions python/core/auto_generated/raster/qgshillshaderenderer.sip.in
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,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() ) const;
%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.4.5
%End


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

Expand Down
7 changes: 7 additions & 0 deletions python/core/auto_generated/raster/qgsrasterrenderer.sip.in
Original file line number Diff line number Diff line change
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.4.5
%End

protected:
Expand Down
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include "qgshillshaderendererwidget.h"
#include "qgssettings.h"
#include "qgsmaplayerlegend.h"
#include "qgsfileutils.h"

#include <QDesktopServices>
#include <QTableWidgetItem>
Expand Down Expand Up @@ -1863,20 +1864,41 @@ 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
outputFileName = QgsFileUtils::ensureFileNameHasExtension( outputFileName, QStringList() << QStringLiteral( "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
Original file line number Diff line number Diff line change
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: 60 additions & 27 deletions src/core/qgsmaplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1398,69 +1398,102 @@ void QgsMapLayer::exportSldStyle( QDomDocument &doc, QString &errorMsg ) const
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 )
const QgsRasterLayer *rlayer = qobject_cast<const QgsRasterLayer *>( this );
if ( !vlayer && !rlayer )
{
errorMsg = tr( "Could not save symbology because:\n%1" )
.arg( QStringLiteral( "Non-vector layers not supported yet" ) );
.arg( tr( "Only vector and raster layers are supported" ) );
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: Only SLD 1.0 version is generated because seems none is using SE1.1.0 at least 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
{
const QgsMapLayer *mlayer = qobject_cast<const QgsMapLayer *>( this );

QString errorMsg;
QDomDocument myDocument;
exportSldStyle( myDocument, errorMsg );
mlayer->exportSldStyle( myDocument, errorMsg );
if ( !errorMsg.isNull() )
{
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 ( mlayer->dataProvider()->name() == QLatin1String( "ogr" ) )
{
QStringList theURIParts = uri.split( '|' );
filename = theURIParts[0];
}
else if ( vlayer->providerType() == QLatin1String( "gpx" ) )
else if ( mlayer->dataProvider()->name() == QLatin1String( "gpx" ) )
{
QStringList theURIParts = uri.split( '?' );
filename = theURIParts[0];
}
else if ( vlayer->providerType() == QLatin1String( "delimitedtext" ) )
else if ( mlayer->dataProvider()->name() == 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
49 changes: 49 additions & 0 deletions src/core/raster/qgscontrastenhancement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,55 @@ 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;
case UserDefinedEnhancement:
QString algName = contrastEnhancementAlgorithmString( contrastEnhancementAlgorithm() );
QgsDebugMsg( QObject::tr( "No SLD1.0 conversion yet for stretch algorithm %1" ).arg( algName ) );
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
9 changes: 9 additions & 0 deletions src/core/raster/qgscontrastenhancement.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,15 @@ class CORE_EXPORT QgsContrastEnhancement

void readXml( const QDomElement &elem );

/**
* 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 sld:Normalize + vendor options are supported because there is no clear mapping
* of ContrastEnhancement parameters to support sld:Histogram or sld:GammaValue
* \since QGIS 3.4.5 */
void toSld( QDomDocument &doc, QDomElement &element ) const;

private:
#ifdef SIP_RUN
const QgsContrastEnhancement &operator=( const QgsContrastEnhancement & );
Expand Down
Loading

0 comments on commit c88e7ab

Please sign in to comment.