QGIS API Documentation  3.6.0-Noosa (5873452)
qgspalettedrasterrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspalettedrasterrenderer.cpp
3  -----------------------------
4  begin : December 2011
5  copyright : (C) 2011 by Marco Hugentobler
6  email : marco at sourcepole dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
19 #include "qgsrastertransparency.h"
20 #include "qgsrasterviewport.h"
21 #include "qgssymbollayerutils.h"
22 
23 #include <QColor>
24 #include <QDomDocument>
25 #include <QDomElement>
26 #include <QImage>
27 #include <QVector>
28 #include <memory>
29 
31  : QgsRasterRenderer( input, QStringLiteral( "paletted" ) )
32  , mBand( bandNumber )
33  , mClassData( classes )
34 {
35  updateArrays();
36 }
37 
39 {
40  QgsPalettedRasterRenderer *renderer = new QgsPalettedRasterRenderer( nullptr, mBand, mClassData );
41  if ( mSourceColorRamp )
42  renderer->setSourceColorRamp( mSourceColorRamp->clone() );
43 
44  renderer->copyCommonProperties( this );
45  return renderer;
46 }
47 
49 {
50  if ( elem.isNull() )
51  {
52  return nullptr;
53  }
54 
55  int bandNumber = elem.attribute( QStringLiteral( "band" ), QStringLiteral( "-1" ) ).toInt();
56  ClassData classData;
57 
58  QDomElement paletteElem = elem.firstChildElement( QStringLiteral( "colorPalette" ) );
59  if ( !paletteElem.isNull() )
60  {
61  QDomNodeList paletteEntries = paletteElem.elementsByTagName( QStringLiteral( "paletteEntry" ) );
62 
63  QDomElement entryElem;
64  int value;
65 
66  for ( int i = 0; i < paletteEntries.size(); ++i )
67  {
68  QColor color;
69  QString label;
70  entryElem = paletteEntries.at( i ).toElement();
71  value = static_cast<int>( entryElem.attribute( QStringLiteral( "value" ), QStringLiteral( "0" ) ).toDouble() );
72  QgsDebugMsgLevel( entryElem.attribute( "color", "#000000" ), 4 );
73  color = QColor( entryElem.attribute( QStringLiteral( "color" ), QStringLiteral( "#000000" ) ) );
74  color.setAlpha( entryElem.attribute( QStringLiteral( "alpha" ), QStringLiteral( "255" ) ).toInt() );
75  label = entryElem.attribute( QStringLiteral( "label" ) );
76  classData << Class( value, color, label );
77  }
78  }
79 
80  QgsPalettedRasterRenderer *r = new QgsPalettedRasterRenderer( input, bandNumber, classData );
81  r->readXml( elem );
82 
83  // try to load color ramp (optional)
84  QDomElement sourceColorRampElem = elem.firstChildElement( QStringLiteral( "colorramp" ) );
85  if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( QStringLiteral( "name" ) ) == QLatin1String( "[source]" ) )
86  {
87  r->setSourceColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ) );
88  }
89 
90  return r;
91 }
92 
94 {
95  return mClassData;
96 }
97 
98 QString QgsPalettedRasterRenderer::label( int idx ) const
99 {
100  Q_FOREACH ( const Class &c, mClassData )
101  {
102  if ( c.value == idx )
103  return c.label;
104  }
105 
106  return QString();
107 }
108 
109 void QgsPalettedRasterRenderer::setLabel( int idx, const QString &label )
110 {
111  ClassData::iterator cIt = mClassData.begin();
112  for ( ; cIt != mClassData.end(); ++cIt )
113  {
114  if ( cIt->value == idx )
115  {
116  cIt->label = label;
117  return;
118  }
119  }
120 }
121 
122 QgsRasterBlock *QgsPalettedRasterRenderer::block( int bandNo, QgsRectangle const &extent, int width, int height, QgsRasterBlockFeedback *feedback )
123 {
124  std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
125  if ( !mInput || mClassData.isEmpty() )
126  {
127  return outputBlock.release();
128  }
129 
130  std::shared_ptr< QgsRasterBlock > inputBlock( mInput->block( bandNo, extent, width, height, feedback ) );
131 
132  if ( !inputBlock || inputBlock->isEmpty() )
133  {
134  QgsDebugMsg( QStringLiteral( "No raster data!" ) );
135  return outputBlock.release();
136  }
137 
138  double currentOpacity = mOpacity;
139 
140  //rendering is faster without considering user-defined transparency
141  bool hasTransparency = usesTransparency();
142 
143  std::shared_ptr< QgsRasterBlock > alphaBlock;
144 
145  if ( mAlphaBand > 0 && mAlphaBand != mBand )
146  {
147  alphaBlock.reset( mInput->block( mAlphaBand, extent, width, height, feedback ) );
148  if ( !alphaBlock || alphaBlock->isEmpty() )
149  {
150  return outputBlock.release();
151  }
152  }
153  else if ( mAlphaBand == mBand )
154  {
155  alphaBlock = inputBlock;
156  }
157 
158  if ( !outputBlock->reset( Qgis::ARGB32_Premultiplied, width, height ) )
159  {
160  return outputBlock.release();
161  }
162 
163  QRgb myDefaultColor = NODATA_COLOR;
164 
165  //use direct data access instead of QgsRasterBlock::setValue
166  //because of performance
167  unsigned int *outputData = ( unsigned int * )( outputBlock->bits() );
168 
169  qgssize rasterSize = ( qgssize )width * height;
170  bool isNoData = false;
171  for ( qgssize i = 0; i < rasterSize; ++i )
172  {
173  const double value = inputBlock->valueAndNoData( i, isNoData );
174  if ( isNoData )
175  {
176  outputData[i] = myDefaultColor;
177  continue;
178  }
179  int val = static_cast< int >( value );
180  if ( !mColors.contains( val ) )
181  {
182  outputData[i] = myDefaultColor;
183  continue;
184  }
185 
186  if ( !hasTransparency )
187  {
188  outputData[i] = mColors.value( val );
189  }
190  else
191  {
192  currentOpacity = mOpacity;
193  if ( mRasterTransparency )
194  {
195  currentOpacity = mRasterTransparency->alphaValue( val, mOpacity * 255 ) / 255.0;
196  }
197  if ( mAlphaBand > 0 )
198  {
199  currentOpacity *= alphaBlock->value( i ) / 255.0;
200  }
201 
202  QRgb c = mColors.value( val );
203  outputData[i] = qRgba( currentOpacity * qRed( c ), currentOpacity * qGreen( c ), currentOpacity * qBlue( c ), currentOpacity * qAlpha( c ) );
204  }
205  }
206 
207  return outputBlock.release();
208 }
209 
210 void QgsPalettedRasterRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
211 {
212  if ( parentElem.isNull() )
213  {
214  return;
215  }
216 
217  QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
218  _writeXml( doc, rasterRendererElem );
219 
220  rasterRendererElem.setAttribute( QStringLiteral( "band" ), mBand );
221  QDomElement colorPaletteElem = doc.createElement( QStringLiteral( "colorPalette" ) );
222  ClassData::const_iterator it = mClassData.constBegin();
223  for ( ; it != mClassData.constEnd(); ++it )
224  {
225  QColor color = it->color;
226  QDomElement colorElem = doc.createElement( QStringLiteral( "paletteEntry" ) );
227  colorElem.setAttribute( QStringLiteral( "value" ), it->value );
228  colorElem.setAttribute( QStringLiteral( "color" ), color.name() );
229  colorElem.setAttribute( QStringLiteral( "alpha" ), color.alpha() );
230  if ( !it->label.isEmpty() )
231  {
232  colorElem.setAttribute( QStringLiteral( "label" ), it->label );
233  }
234  colorPaletteElem.appendChild( colorElem );
235  }
236  rasterRendererElem.appendChild( colorPaletteElem );
237 
238  // save source color ramp
239  if ( mSourceColorRamp )
240  {
241  QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mSourceColorRamp.get(), doc );
242  rasterRendererElem.appendChild( colorRampElem );
243  }
244 
245  parentElem.appendChild( rasterRendererElem );
246 }
247 
248 void QgsPalettedRasterRenderer::toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const
249 {
250  QgsStringMap newProps = props;
251 
252  // create base structure
253  QgsRasterRenderer::toSld( doc, element, props );
254 
255  // look for RasterSymbolizer tag
256  QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
257  if ( elements.size() == 0 )
258  return;
259 
260  // there SHOULD be only one
261  QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
262 
263  // add Channel Selection tags
264  QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
265  rasterSymbolizerElem.appendChild( channelSelectionElem );
266 
267  // for the mapped band
268  QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
269  channelSelectionElem.appendChild( channelElem );
270 
271  // set band
272  QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
273  sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( band() ) ) );
274  channelElem.appendChild( sourceChannelNameElem );
275 
276  // add ColorMap tag
277  QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
278  colorMapElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "values" ) );
279  if ( this->classes().size() >= 255 )
280  colorMapElem.setAttribute( QStringLiteral( "extended" ), QStringLiteral( "true" ) );
281  rasterSymbolizerElem.appendChild( colorMapElem );
282 
283  // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
284  // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
285  QList<QgsPalettedRasterRenderer::Class> classes = this->classes();
286  QList<QgsPalettedRasterRenderer::Class>::const_iterator classDataIt = classes.constBegin();
287  for ( ; classDataIt != classes.constEnd(); ++classDataIt )
288  {
289  QDomElement colorMapEntryElem = doc.createElement( QStringLiteral( "sld:ColorMapEntry" ) );
290  colorMapElem.appendChild( colorMapEntryElem );
291 
292  // set colorMapEntryElem attributes
293  colorMapEntryElem.setAttribute( QStringLiteral( "color" ), classDataIt->color.name() );
294  colorMapEntryElem.setAttribute( QStringLiteral( "quantity" ), QString::number( classDataIt->value ) );
295  colorMapEntryElem.setAttribute( QStringLiteral( "label" ), classDataIt->label );
296  if ( classDataIt->color.alphaF() != 1.0 )
297  {
298  colorMapEntryElem.setAttribute( QStringLiteral( "opacity" ), QString::number( classDataIt->color.alphaF() ) );
299  }
300  }
301 }
302 
303 void QgsPalettedRasterRenderer::legendSymbologyItems( QList< QPair< QString, QColor > > &symbolItems ) const
304 {
305  ClassData::const_iterator it = mClassData.constBegin();
306  for ( ; it != mClassData.constEnd(); ++it )
307  {
308  QString lab = it->label.isEmpty() ? QString::number( it->value ) : it->label;
309  symbolItems << qMakePair( lab, it->color );
310  }
311 }
312 
314 {
315  QList<int> bandList;
316  if ( mBand != -1 )
317  {
318  bandList << mBand;
319  }
320  return bandList;
321 }
322 
324 {
325  mSourceColorRamp.reset( ramp );
326 }
327 
329 {
330  return mSourceColorRamp.get();
331 }
332 
333 QgsPalettedRasterRenderer::ClassData QgsPalettedRasterRenderer::colorTableToClassData( const QList<QgsColorRampShader::ColorRampItem> &table )
334 {
335  QList<QgsColorRampShader::ColorRampItem>::const_iterator colorIt = table.constBegin();
337  for ( ; colorIt != table.constEnd(); ++colorIt )
338  {
339  int idx = ( int )( colorIt->value );
340  classes << QgsPalettedRasterRenderer::Class( idx, colorIt->color, colorIt->label );
341  }
342  return classes;
343 }
344 
346 {
348 
349  QRegularExpression linePartRx( QStringLiteral( "[\\s,:]+" ) );
350 
351  QStringList parts = string.split( '\n', QString::SkipEmptyParts );
352  Q_FOREACH ( const QString &part, parts )
353  {
354  QStringList lineParts = part.split( linePartRx, QString::SkipEmptyParts );
355  bool ok = false;
356  switch ( lineParts.count() )
357  {
358  case 1:
359  {
360  int value = lineParts.at( 0 ).toInt( &ok );
361  if ( !ok )
362  continue;
363 
364  classes << Class( value );
365  break;
366  }
367 
368  case 2:
369  {
370  int value = lineParts.at( 0 ).toInt( &ok );
371  if ( !ok )
372  continue;
373 
374  QColor c( lineParts.at( 1 ) );
375 
376  classes << Class( value, c );
377  break;
378  }
379 
380  default:
381  {
382  if ( lineParts.count() < 4 )
383  continue;
384 
385  int value = lineParts.at( 0 ).toInt( &ok );
386  if ( !ok )
387  continue;
388 
389  bool rOk = false;
390  double r = lineParts.at( 1 ).toDouble( &rOk );
391  bool gOk = false;
392  double g = lineParts.at( 2 ).toDouble( &gOk );
393  bool bOk = false;
394  double b = lineParts.at( 3 ).toDouble( &bOk );
395 
396  QColor c;
397  if ( rOk && gOk && bOk )
398  {
399  c = QColor( r, g, b );
400  }
401 
402  if ( lineParts.count() >= 5 )
403  {
404  double alpha = lineParts.at( 4 ).toDouble( &ok );
405  if ( ok )
406  c.setAlpha( alpha );
407  }
408 
409  QString label;
410  if ( lineParts.count() > 5 )
411  {
412  label = lineParts.mid( 5 ).join( ' ' );
413  }
414 
415  classes << Class( value, c, label );
416  break;
417  }
418  }
419 
420  }
421  return classes;
422 }
423 
425 {
426  QFile inputFile( path );
427  QString input;
428  if ( inputFile.open( QIODevice::ReadOnly ) )
429  {
430  QTextStream in( &inputFile );
431  input = in.readAll();
432  inputFile.close();
433  }
434  return classDataFromString( input );
435 }
436 
438 {
439  QStringList out;
440  // must be sorted
442  std::sort( cd.begin(), cd.end(), []( const Class & a, const Class & b ) -> bool
443  {
444  return a.value < b.value;
445  } );
446 
447  Q_FOREACH ( const Class &c, cd )
448  {
449  out << QStringLiteral( "%1 %2 %3 %4 %5 %6" ).arg( c.value ).arg( c.color.red() )
450  .arg( c.color.green() ).arg( c.color.blue() ).arg( c.color.alpha() ).arg( c.label );
451  }
452  return out.join( '\n' );
453 }
454 
456 {
457  if ( !raster )
458  return ClassData();
459 
460  // get min and max value from raster
461  QgsRasterBandStats stats = raster->bandStatistics( bandNumber, QgsRasterBandStats::Min | QgsRasterBandStats::Max, QgsRectangle(), 0, feedback );
462  if ( feedback && feedback->isCanceled() )
463  return ClassData();
464 
465  double min = stats.minimumValue;
466  double max = stats.maximumValue;
467  // need count of every individual value
468  int bins = std::ceil( max - min ) + 1;
469  if ( bins <= 0 )
470  return ClassData();
471 
472  QgsRasterHistogram histogram = raster->histogram( bandNumber, bins, min, max, QgsRectangle(), 0, false, feedback );
473  if ( feedback && feedback->isCanceled() )
474  return ClassData();
475 
476  double interval = ( histogram.maximum - histogram.minimum + 1 ) / histogram.binCount;
477 
478  ClassData data;
479 
480  double currentValue = histogram.minimum;
481  double presentValues = 0;
482  for ( int idx = 0; idx < histogram.binCount; ++idx )
483  {
484  int count = histogram.histogramVector.at( idx );
485  if ( count > 0 )
486  {
487  data << Class( currentValue, QColor(), QString::number( currentValue ) );
488  presentValues++;
489  }
490  currentValue += interval;
491  }
492 
493  // assign colors from ramp
494  if ( ramp )
495  {
496  int i = 0;
497 
498  if ( QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp ) )
499  {
500  //ramp is a random colors ramp, so inform it of the total number of required colors
501  //this allows the ramp to pregenerate a set of visually distinctive colors
502  randomRamp->setTotalColorCount( data.count() );
503  }
504 
505  if ( presentValues > 1 )
506  presentValues -= 1; //avoid duplicate first color
507 
508  QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
509  for ( ; cIt != data.end(); ++cIt )
510  {
511  cIt->color = ramp->color( i / presentValues );
512  i++;
513  }
514  }
515  return data;
516 }
517 
518 void QgsPalettedRasterRenderer::updateArrays()
519 {
520  mColors.clear();
521  int i = 0;
522  ClassData::const_iterator it = mClassData.constBegin();
523  for ( ; it != mClassData.constEnd(); ++it )
524  {
525  mColors[it->value] = qPremultiply( it->color.rgba() );
526  i++;
527  }
528 }
void setSourceColorRamp(QgsColorRamp *ramp)
Set the source color ramp.
static QString classDataToString(const QgsPalettedRasterRenderer::ClassData &classes)
Converts classes to a string representation, using the .clr/gdal color table file format...
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsPalettedRasterRenderer(QgsRasterInterface *input, int bandNumber, const ClassData &classes)
Constructor for QgsPalettedRasterRenderer.
QColor color
Color to render value.
virtual void toSld(QDomDocument &doc, QDomElement &element, const QgsStringMap &props=QgsStringMap()) const
Used from subclasses to create SLD Rule elements following SLD v1.0 specs.
Renderer for paletted raster images.
virtual QgsRectangle extent() const
Gets the extent of the interface.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Abstract base class for color ramps.
Definition: qgscolorramp.h:31
double minimum
The minimum histogram value.
virtual QgsRasterInterface * input() const
Current input.
ClassData classes() const
Returns a map of value to classes (colors) used by the renderer.
Properties of a single value class.
static QDomElement saveColorRamp(const QString &name, QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp&#39;s settings to an XML element.
void setLabel(int idx, const QString &label)
Set category label.
double maximumValue
The maximum cell value in the raster band.
void writeXml(QDomDocument &doc, QDomElement &parentElem) const override
Write base class members to xml.
virtual QgsRasterHistogram histogram(int bandNo, int binCount=0, double minimum=std::numeric_limits< double >::quiet_NaN(), double maximum=std::numeric_limits< double >::quiet_NaN(), const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, bool includeOutOfRange=false, QgsRasterBlockFeedback *feedback=nullptr)
Returns a band histogram.
virtual QColor color(double value) const =0
Returns the color corresponding to a specified value.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:587
QgsRasterTransparency * mRasterTransparency
Raster transparency per color or value. Overwrites global alpha value.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
static const QRgb NODATA_COLOR
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition: qgis.h:94
The RasterBandStats struct is a container for statistics about a single raster band.
Raster data container.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
virtual QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr)=0
Read block of data using given extent and size.
QList< QgsPalettedRasterRenderer::Class > ClassData
Map of value to class properties.
void _writeXml(QDomDocument &doc, QDomElement &rasterRendererElem) const
Write upper class info into rasterrenderer element (called by writeXml method of subclasses) ...
static QgsPalettedRasterRenderer::ClassData classDataFromString(const QString &string)
Converts a string containing a color table or class data to to paletted renderer class data...
void copyCommonProperties(const QgsRasterRenderer *other, bool copyMinMaxOrigin=true)
Copies common properties like opacity / transparency data from other renderer.
bool usesTransparency() const
QgsRasterHistogram::HistogramVector histogramVector
Stores the histogram for a given layer.
static QgsColorRamp * loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
QString label(int idx) const
Returns optional category label.
int mAlphaBand
Read alpha value from band.
void readXml(const QDomElement &rendererElem) override
Sets base class members from xml. Usually called from create() methods of subclasses.
Base class for processing filters like renderers, reprojector, resampler etc.
int alphaValue(double value, int globalTransparency=255) const
Returns the transparency value for a single value pixel.
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition: qgis.h:596
static QgsRasterRenderer * create(const QDomElement &elem, QgsRasterInterface *input)
Totally random color ramp.
Definition: qgscolorramp.h:427
QList< int > usesBands() const override
Returns a list of band numbers used by the renderer.
double maximum
The maximum histogram value.
QgsColorRamp * sourceColorRamp() const
Gets the source color ramp.
virtual QgsRasterBandStats bandStatistics(int bandNo, int stats=QgsRasterBandStats::All, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, QgsRasterBlockFeedback *feedback=nullptr)
Returns the band statistics.
QgsPalettedRasterRenderer * clone() const override
Clone itself, create deep copy.
static QgsPalettedRasterRenderer::ClassData classDataFromFile(const QString &path)
Opens a color table file and returns corresponding paletted renderer class data.
static QgsPalettedRasterRenderer::ClassData classDataFromRaster(QgsRasterInterface *raster, int bandNumber, QgsColorRamp *ramp=nullptr, QgsRasterBlockFeedback *feedback=nullptr)
Generates class data from a raster, for the specified bandNumber.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void legendSymbologyItems(QList< QPair< QString, QColor > > &symbolItems) const override
Gets symbology items if provided by renderer.
int band() const
Returns the raster band used for rendering the raster.
double minimumValue
The minimum cell value in the raster band.
void toSld(QDomDocument &doc, QDomElement &element, const QgsStringMap &props=QgsStringMap()) const override
Used from subclasses to create SLD Rule elements following SLD v1.0 specs.
The QgsRasterHistogram is a container for histogram of a single raster band.
int binCount
Number of bins (intervals,buckets) in histogram.
double mOpacity
Global alpha value (0-1)
QgsRasterInterface * mInput
Feedback object tailored for raster block reading.
static QgsPalettedRasterRenderer::ClassData colorTableToClassData(const QList< QgsColorRampShader::ColorRampItem > &table)
Converts a raster color table to paletted renderer class data.
Raster renderer pipe that applies colors to a raster.
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.