QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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  const auto constMClassData = mClassData;
101  for ( const Class &c : constMClassData )
102  {
103  if ( c.value == idx )
104  return c.label;
105  }
106 
107  return QString();
108 }
109 
110 void QgsPalettedRasterRenderer::setLabel( int idx, const QString &label )
111 {
112  ClassData::iterator cIt = mClassData.begin();
113  for ( ; cIt != mClassData.end(); ++cIt )
114  {
115  if ( cIt->value == idx )
116  {
117  cIt->label = label;
118  return;
119  }
120  }
121 }
122 
124 {
125  std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
126  if ( !mInput || mClassData.isEmpty() )
127  {
128  return outputBlock.release();
129  }
130 
131  std::shared_ptr< QgsRasterBlock > inputBlock( mInput->block( mBand, extent, width, height, feedback ) );
132 
133  if ( !inputBlock || inputBlock->isEmpty() )
134  {
135  QgsDebugMsg( QStringLiteral( "No raster data!" ) );
136  return outputBlock.release();
137  }
138 
139  double currentOpacity = mOpacity;
140 
141  //rendering is faster without considering user-defined transparency
142  bool hasTransparency = usesTransparency();
143 
144  std::shared_ptr< QgsRasterBlock > alphaBlock;
145 
146  if ( mAlphaBand > 0 && mAlphaBand != mBand )
147  {
148  alphaBlock.reset( mInput->block( mAlphaBand, extent, width, height, feedback ) );
149  if ( !alphaBlock || alphaBlock->isEmpty() )
150  {
151  return outputBlock.release();
152  }
153  }
154  else if ( mAlphaBand == mBand )
155  {
156  alphaBlock = inputBlock;
157  }
158 
159  if ( !outputBlock->reset( Qgis::ARGB32_Premultiplied, width, height ) )
160  {
161  return outputBlock.release();
162  }
163 
164  QRgb myDefaultColor = NODATA_COLOR;
165 
166  //use direct data access instead of QgsRasterBlock::setValue
167  //because of performance
168  unsigned int *outputData = ( unsigned int * )( outputBlock->bits() );
169 
170  qgssize rasterSize = ( qgssize )width * height;
171  bool isNoData = false;
172  for ( qgssize i = 0; i < rasterSize; ++i )
173  {
174  const double value = inputBlock->valueAndNoData( i, isNoData );
175  if ( isNoData )
176  {
177  outputData[i] = myDefaultColor;
178  continue;
179  }
180  int val = static_cast< int >( value );
181  if ( !mColors.contains( val ) )
182  {
183  outputData[i] = myDefaultColor;
184  continue;
185  }
186 
187  if ( !hasTransparency )
188  {
189  outputData[i] = mColors.value( val );
190  }
191  else
192  {
193  currentOpacity = mOpacity;
194  if ( mRasterTransparency )
195  {
196  currentOpacity = mRasterTransparency->alphaValue( val, mOpacity * 255 ) / 255.0;
197  }
198  if ( mAlphaBand > 0 )
199  {
200  currentOpacity *= alphaBlock->value( i ) / 255.0;
201  }
202 
203  QRgb c = mColors.value( val );
204  outputData[i] = qRgba( currentOpacity * qRed( c ), currentOpacity * qGreen( c ), currentOpacity * qBlue( c ), currentOpacity * qAlpha( c ) );
205  }
206  }
207 
208  return outputBlock.release();
209 }
210 
211 void QgsPalettedRasterRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
212 {
213  if ( parentElem.isNull() )
214  {
215  return;
216  }
217 
218  QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
219  _writeXml( doc, rasterRendererElem );
220 
221  rasterRendererElem.setAttribute( QStringLiteral( "band" ), mBand );
222  QDomElement colorPaletteElem = doc.createElement( QStringLiteral( "colorPalette" ) );
223  ClassData::const_iterator it = mClassData.constBegin();
224  for ( ; it != mClassData.constEnd(); ++it )
225  {
226  QColor color = it->color;
227  QDomElement colorElem = doc.createElement( QStringLiteral( "paletteEntry" ) );
228  colorElem.setAttribute( QStringLiteral( "value" ), it->value );
229  colorElem.setAttribute( QStringLiteral( "color" ), color.name() );
230  colorElem.setAttribute( QStringLiteral( "alpha" ), color.alpha() );
231  if ( !it->label.isEmpty() )
232  {
233  colorElem.setAttribute( QStringLiteral( "label" ), it->label );
234  }
235  colorPaletteElem.appendChild( colorElem );
236  }
237  rasterRendererElem.appendChild( colorPaletteElem );
238 
239  // save source color ramp
240  if ( mSourceColorRamp )
241  {
242  QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mSourceColorRamp.get(), doc );
243  rasterRendererElem.appendChild( colorRampElem );
244  }
245 
246  parentElem.appendChild( rasterRendererElem );
247 }
248 
249 void QgsPalettedRasterRenderer::toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const
250 {
251  // create base structure
252  QgsRasterRenderer::toSld( doc, element, props );
253 
254  // look for RasterSymbolizer tag
255  QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
256  if ( elements.size() == 0 )
257  return;
258 
259  // there SHOULD be only one
260  QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
261 
262  // add Channel Selection tags
263  QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
264  rasterSymbolizerElem.appendChild( channelSelectionElem );
265 
266  // for the mapped band
267  QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
268  channelSelectionElem.appendChild( channelElem );
269 
270  // set band
271  QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
272  sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( band() ) ) );
273  channelElem.appendChild( sourceChannelNameElem );
274 
275  // add ColorMap tag
276  QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
277  colorMapElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "values" ) );
278  if ( this->classes().size() >= 255 )
279  colorMapElem.setAttribute( QStringLiteral( "extended" ), QStringLiteral( "true" ) );
280  rasterSymbolizerElem.appendChild( colorMapElem );
281 
282  // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
283  // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
284  QList<QgsPalettedRasterRenderer::Class> classes = this->classes();
285  QList<QgsPalettedRasterRenderer::Class>::const_iterator classDataIt = classes.constBegin();
286  for ( ; classDataIt != classes.constEnd(); ++classDataIt )
287  {
288  QDomElement colorMapEntryElem = doc.createElement( QStringLiteral( "sld:ColorMapEntry" ) );
289  colorMapElem.appendChild( colorMapEntryElem );
290 
291  // set colorMapEntryElem attributes
292  colorMapEntryElem.setAttribute( QStringLiteral( "color" ), classDataIt->color.name() );
293  colorMapEntryElem.setAttribute( QStringLiteral( "quantity" ), QString::number( classDataIt->value ) );
294  colorMapEntryElem.setAttribute( QStringLiteral( "label" ), classDataIt->label );
295  if ( classDataIt->color.alphaF() != 1.0 )
296  {
297  colorMapEntryElem.setAttribute( QStringLiteral( "opacity" ), QString::number( classDataIt->color.alphaF() ) );
298  }
299  }
300 }
301 
302 void QgsPalettedRasterRenderer::legendSymbologyItems( QList< QPair< QString, QColor > > &symbolItems ) const
303 {
304  ClassData::const_iterator it = mClassData.constBegin();
305  for ( ; it != mClassData.constEnd(); ++it )
306  {
307  QString lab = it->label.isEmpty() ? QString::number( it->value ) : it->label;
308  symbolItems << qMakePair( lab, it->color );
309  }
310 }
311 
313 {
314  QList<int> bandList;
315  if ( mBand != -1 )
316  {
317  bandList << mBand;
318  }
319  return bandList;
320 }
321 
323 {
324  mSourceColorRamp.reset( ramp );
325 }
326 
328 {
329  return mSourceColorRamp.get();
330 }
331 
332 QgsPalettedRasterRenderer::ClassData QgsPalettedRasterRenderer::colorTableToClassData( const QList<QgsColorRampShader::ColorRampItem> &table )
333 {
334  QList<QgsColorRampShader::ColorRampItem>::const_iterator colorIt = table.constBegin();
336  for ( ; colorIt != table.constEnd(); ++colorIt )
337  {
338  int idx = ( int )( colorIt->value );
339  classes << QgsPalettedRasterRenderer::Class( idx, colorIt->color, colorIt->label );
340  }
341  return classes;
342 }
343 
345 {
347 
348  QRegularExpression linePartRx( QStringLiteral( "[\\s,:]+" ) );
349 
350  QStringList parts = string.split( '\n', QString::SkipEmptyParts );
351  const auto constParts = parts;
352  for ( const QString &part : constParts )
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  const auto constCd = cd;
448  for ( const Class &c : constCd )
449  {
450  out << QStringLiteral( "%1 %2 %3 %4 %5 %6" ).arg( c.value ).arg( c.color.red() )
451  .arg( c.color.green() ).arg( c.color.blue() ).arg( c.color.alpha() ).arg( c.label );
452  }
453  return out.join( '\n' );
454 }
455 
457 {
458  if ( !raster )
459  return ClassData();
460 
461  // get min and max value from raster
462  QgsRasterBandStats stats = raster->bandStatistics( bandNumber, QgsRasterBandStats::Min | QgsRasterBandStats::Max, QgsRectangle(), 0, feedback );
463  if ( feedback && feedback->isCanceled() )
464  return ClassData();
465 
466  double min = stats.minimumValue;
467  double max = stats.maximumValue;
468  // need count of every individual value
469  int bins = std::ceil( max - min ) + 1;
470  if ( bins <= 0 )
471  return ClassData();
472 
473  QgsRasterHistogram histogram = raster->histogram( bandNumber, bins, min, max, QgsRectangle(), 0, false, feedback );
474  if ( feedback && feedback->isCanceled() )
475  return ClassData();
476 
477  double interval = ( histogram.maximum - histogram.minimum + 1 ) / histogram.binCount;
478 
479  ClassData data;
480 
481  double currentValue = histogram.minimum;
482  double presentValues = 0;
483  for ( int idx = 0; idx < histogram.binCount; ++idx )
484  {
485  int count = histogram.histogramVector.at( idx );
486  if ( count > 0 )
487  {
488  data << Class( currentValue, QColor(), QString::number( currentValue ) );
489  presentValues++;
490  }
491  currentValue += interval;
492  }
493 
494  // assign colors from ramp
495  if ( ramp )
496  {
497  int i = 0;
498 
499  if ( QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp ) )
500  {
501  //ramp is a random colors ramp, so inform it of the total number of required colors
502  //this allows the ramp to pregenerate a set of visually distinctive colors
503  randomRamp->setTotalColorCount( data.count() );
504  }
505 
506  if ( presentValues > 1 )
507  presentValues -= 1; //avoid duplicate first color
508 
509  QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
510  for ( ; cIt != data.end(); ++cIt )
511  {
512  cIt->color = ramp->color( i / presentValues );
513  i++;
514  }
515  }
516  return data;
517 }
518 
519 void QgsPalettedRasterRenderer::updateArrays()
520 {
521  mColors.clear();
522  int i = 0;
523  ClassData::const_iterator it = mClassData.constBegin();
524  for ( ; it != mClassData.constEnd(); ++it )
525  {
526  mColors[it->value] = qPremultiply( it->color.rgba() );
527  i++;
528  }
529 }
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.
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.