QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
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( mBand, 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  for ( qgssize i = 0; i < rasterSize; ++i )
171  {
172  if ( inputBlock->isNoData( i ) )
173  {
174  outputData[i] = myDefaultColor;
175  continue;
176  }
177  int val = ( int ) inputBlock->value( i );
178  if ( !mColors.contains( val ) )
179  {
180  outputData[i] = myDefaultColor;
181  continue;
182  }
183 
184  if ( !hasTransparency )
185  {
186  outputData[i] = mColors.value( val );
187  }
188  else
189  {
190  currentOpacity = mOpacity;
191  if ( mRasterTransparency )
192  {
193  currentOpacity = mRasterTransparency->alphaValue( val, mOpacity * 255 ) / 255.0;
194  }
195  if ( mAlphaBand > 0 )
196  {
197  currentOpacity *= alphaBlock->value( i ) / 255.0;
198  }
199 
200  QRgb c = mColors.value( val );
201  outputData[i] = qRgba( currentOpacity * qRed( c ), currentOpacity * qGreen( c ), currentOpacity * qBlue( c ), currentOpacity * qAlpha( c ) );
202  }
203  }
204 
205  return outputBlock.release();
206 }
207 
208 void QgsPalettedRasterRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
209 {
210  if ( parentElem.isNull() )
211  {
212  return;
213  }
214 
215  QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
216  _writeXml( doc, rasterRendererElem );
217 
218  rasterRendererElem.setAttribute( QStringLiteral( "band" ), mBand );
219  QDomElement colorPaletteElem = doc.createElement( QStringLiteral( "colorPalette" ) );
220  ClassData::const_iterator it = mClassData.constBegin();
221  for ( ; it != mClassData.constEnd(); ++it )
222  {
223  QColor color = it->color;
224  QDomElement colorElem = doc.createElement( QStringLiteral( "paletteEntry" ) );
225  colorElem.setAttribute( QStringLiteral( "value" ), it->value );
226  colorElem.setAttribute( QStringLiteral( "color" ), color.name() );
227  colorElem.setAttribute( QStringLiteral( "alpha" ), color.alpha() );
228  if ( !it->label.isEmpty() )
229  {
230  colorElem.setAttribute( QStringLiteral( "label" ), it->label );
231  }
232  colorPaletteElem.appendChild( colorElem );
233  }
234  rasterRendererElem.appendChild( colorPaletteElem );
235 
236  // save source color ramp
237  if ( mSourceColorRamp )
238  {
239  QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mSourceColorRamp.get(), doc );
240  rasterRendererElem.appendChild( colorRampElem );
241  }
242 
243  parentElem.appendChild( rasterRendererElem );
244 }
245 
246 void QgsPalettedRasterRenderer::toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const
247 {
248  QgsStringMap newProps = props;
249 
250  // create base structure
251  QgsRasterRenderer::toSld( doc, element, props );
252 
253  // look for RasterSymbolizer tag
254  QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
255  if ( elements.size() == 0 )
256  return;
257 
258  // there SHOULD be only one
259  QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
260 
261  // add Channel Selection tags
262  QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
263  rasterSymbolizerElem.appendChild( channelSelectionElem );
264 
265  // for the mapped band
266  QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
267  channelSelectionElem.appendChild( channelElem );
268 
269  // set band
270  QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
271  sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( band() ) ) );
272  channelElem.appendChild( sourceChannelNameElem );
273 
274  // add ColorMap tag
275  QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
276  colorMapElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "values" ) );
277  if ( this->classes().size() >= 255 )
278  colorMapElem.setAttribute( QStringLiteral( "extended" ), QStringLiteral( "true" ) );
279  rasterSymbolizerElem.appendChild( colorMapElem );
280 
281  // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
282  // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
283  QList<QgsPalettedRasterRenderer::Class> classes = this->classes();
284  QList<QgsPalettedRasterRenderer::Class>::const_iterator classDataIt = classes.constBegin();
285  for ( ; classDataIt != classes.constEnd(); ++classDataIt )
286  {
287  QDomElement colorMapEntryElem = doc.createElement( QStringLiteral( "sld:ColorMapEntry" ) );
288  colorMapElem.appendChild( colorMapEntryElem );
289 
290  // set colorMapEntryElem attributes
291  colorMapEntryElem.setAttribute( QStringLiteral( "color" ), classDataIt->color.name() );
292  colorMapEntryElem.setAttribute( QStringLiteral( "quantity" ), QString::number( classDataIt->value ) );
293  colorMapEntryElem.setAttribute( QStringLiteral( "label" ), classDataIt->label );
294  if ( classDataIt->color.alphaF() != 1.0 )
295  {
296  colorMapEntryElem.setAttribute( QStringLiteral( "opacity" ), QString::number( classDataIt->color.alphaF() ) );
297  }
298  }
299 }
300 
301 void QgsPalettedRasterRenderer::legendSymbologyItems( QList< QPair< QString, QColor > > &symbolItems ) const
302 {
303  ClassData::const_iterator it = mClassData.constBegin();
304  for ( ; it != mClassData.constEnd(); ++it )
305  {
306  QString lab = it->label.isEmpty() ? QString::number( it->value ) : it->label;
307  symbolItems << qMakePair( lab, it->color );
308  }
309 }
310 
312 {
313  QList<int> bandList;
314  if ( mBand != -1 )
315  {
316  bandList << mBand;
317  }
318  return bandList;
319 }
320 
322 {
323  mSourceColorRamp.reset( ramp );
324 }
325 
327 {
328  return mSourceColorRamp.get();
329 }
330 
331 QgsPalettedRasterRenderer::ClassData QgsPalettedRasterRenderer::colorTableToClassData( const QList<QgsColorRampShader::ColorRampItem> &table )
332 {
333  QList<QgsColorRampShader::ColorRampItem>::const_iterator colorIt = table.constBegin();
335  for ( ; colorIt != table.constEnd(); ++colorIt )
336  {
337  int idx = ( int )( colorIt->value );
338  classes << QgsPalettedRasterRenderer::Class( idx, colorIt->color, colorIt->label );
339  }
340  return classes;
341 }
342 
344 {
346 
347  QRegularExpression linePartRx( QStringLiteral( "[\\s,:]+" ) );
348 
349  QStringList parts = string.split( '\n', QString::SkipEmptyParts );
350  Q_FOREACH ( const QString &part, parts )
351  {
352  QStringList lineParts = part.split( linePartRx, QString::SkipEmptyParts );
353  bool ok = false;
354  switch ( lineParts.count() )
355  {
356  case 1:
357  {
358  int value = lineParts.at( 0 ).toInt( &ok );
359  if ( !ok )
360  continue;
361 
362  classes << Class( value );
363  break;
364  }
365 
366  case 2:
367  {
368  int value = lineParts.at( 0 ).toInt( &ok );
369  if ( !ok )
370  continue;
371 
372  QColor c( lineParts.at( 1 ) );
373 
374  classes << Class( value, c );
375  break;
376  }
377 
378  default:
379  {
380  if ( lineParts.count() < 4 )
381  continue;
382 
383  int value = lineParts.at( 0 ).toInt( &ok );
384  if ( !ok )
385  continue;
386 
387  bool rOk = false;
388  double r = lineParts.at( 1 ).toDouble( &rOk );
389  bool gOk = false;
390  double g = lineParts.at( 2 ).toDouble( &gOk );
391  bool bOk = false;
392  double b = lineParts.at( 3 ).toDouble( &bOk );
393 
394  QColor c;
395  if ( rOk && gOk && bOk )
396  {
397  c = QColor( r, g, b );
398  }
399 
400  if ( lineParts.count() >= 5 )
401  {
402  double alpha = lineParts.at( 4 ).toDouble( &ok );
403  if ( ok )
404  c.setAlpha( alpha );
405  }
406 
407  QString label;
408  if ( lineParts.count() > 5 )
409  {
410  label = lineParts.mid( 5 ).join( ' ' );
411  }
412 
413  classes << Class( value, c, label );
414  break;
415  }
416  }
417 
418  }
419  return classes;
420 }
421 
423 {
424  QFile inputFile( path );
425  QString input;
426  if ( inputFile.open( QIODevice::ReadOnly ) )
427  {
428  QTextStream in( &inputFile );
429  input = in.readAll();
430  inputFile.close();
431  }
432  return classDataFromString( input );
433 }
434 
436 {
437  QStringList out;
438  // must be sorted
440  std::sort( cd.begin(), cd.end(), []( const Class & a, const Class & b ) -> bool
441  {
442  return a.value < b.value;
443  } );
444 
445  Q_FOREACH ( const Class &c, cd )
446  {
447  out << QStringLiteral( "%1 %2 %3 %4 %5 %6" ).arg( c.value ).arg( c.color.red() )
448  .arg( c.color.green() ).arg( c.color.blue() ).arg( c.color.alpha() ).arg( c.label );
449  }
450  return out.join( '\n' );
451 }
452 
454 {
455  if ( !raster )
456  return ClassData();
457 
458  // get min and max value from raster
459  QgsRasterBandStats stats = raster->bandStatistics( bandNumber, QgsRasterBandStats::Min | QgsRasterBandStats::Max, QgsRectangle(), 0, feedback );
460  if ( feedback && feedback->isCanceled() )
461  return ClassData();
462 
463  double min = stats.minimumValue;
464  double max = stats.maximumValue;
465  // need count of every individual value
466  int bins = std::ceil( max - min ) + 1;
467  if ( bins <= 0 )
468  return ClassData();
469 
470  QgsRasterHistogram histogram = raster->histogram( bandNumber, bins, min, max, QgsRectangle(), 0, false, feedback );
471  if ( feedback && feedback->isCanceled() )
472  return ClassData();
473 
474  double interval = ( histogram.maximum - histogram.minimum + 1 ) / histogram.binCount;
475 
476  ClassData data;
477 
478  double currentValue = histogram.minimum;
479  double presentValues = 0;
480  for ( int idx = 0; idx < histogram.binCount; ++idx )
481  {
482  int count = histogram.histogramVector.at( idx );
483  if ( count > 0 )
484  {
485  data << Class( currentValue, QColor(), QString::number( currentValue ) );
486  presentValues++;
487  }
488  currentValue += interval;
489  }
490 
491  // assign colors from ramp
492  if ( ramp )
493  {
494  int i = 0;
495 
496  if ( QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp ) )
497  {
498  //ramp is a random colors ramp, so inform it of the total number of required colors
499  //this allows the ramp to pregenerate a set of visually distinctive colors
500  randomRamp->setTotalColorCount( data.count() );
501  }
502 
503  if ( presentValues > 1 )
504  presentValues -= 1; //avoid duplicate first color
505 
506  QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
507  for ( ; cIt != data.end(); ++cIt )
508  {
509  cIt->color = ramp->color( i / presentValues );
510  i++;
511  }
512  }
513  return data;
514 }
515 
516 void QgsPalettedRasterRenderer::updateArrays()
517 {
518  mColors.clear();
519  int i = 0;
520  ClassData::const_iterator it = mClassData.constBegin();
521  for ( ; it != mClassData.constEnd(); ++it )
522  {
523  mColors[it->value] = qPremultiply( it->color.rgba() );
524  i++;
525  }
526 }
void setSourceColorRamp(QgsColorRamp *ramp)
Set the source color ramp.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
static QString classDataToString(const QgsPalettedRasterRenderer::ClassData &classes)
Converts classes to a string representation, using the .clr/gdal color table file format...
int alphaValue(double value, int globalTransparency=255) const
Returns the transparency value for a single value pixel.
A rectangle specified with double values.
Definition: qgsrectangle.h:40
QString label(int idx) const
Returns optional category label.
QgsPalettedRasterRenderer(QgsRasterInterface *input, int bandNumber, const ClassData &classes)
Constructor for QgsPalettedRasterRenderer.
QColor color
Color to render value.
Renderer for paletted raster images.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Abstract base class for color ramps.
Definition: qgscolorramp.h:31
double minimum
The minimum histogram value.
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 QgsRasterInterface * input() const
Current input.
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:577
bool usesTransparency() const
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:107
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.
int band() const
Returns the raster band used for rendering the raster.
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.
ClassData classes() const
Returns a map of value to classes (colors) used by the renderer.
QList< QgsPalettedRasterRenderer::Class > ClassData
Map of value to class properties.
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.
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.
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.
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:586
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.
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.
virtual QgsRectangle extent() const
Gets the extent of the interface.
void legendSymbologyItems(QList< QPair< QString, QColor > > &symbolItems) const override
Gets symbology items if provided by renderer.
QgsColorRamp * sourceColorRamp() const
Gets the source color ramp.
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
void _writeXml(QDomDocument &doc, QDomElement &rasterRendererElem) const
Write upper class info into rasterrenderer element (called by writeXml method of subclasses) ...
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.