1 /***************************************************************************
2   qgspointcloud3dsymbolwidget.cpp
3   ------------------------------
4   Date                 : November 2020
5   Copyright            : (C) 2020 by Nedjima Belgacem
6   Email                : belgacem dot nedjima at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgspointcloud3dsymbolwidget.h"
17 
18 #include "qgspointcloudlayer.h"
19 #include "qgspointcloud3dsymbol.h"
20 #include "qgspointcloudlayer3drenderer.h"
21 #include "qgsapplication.h"
22 #include "qgspointcloudrenderer.h"
23 #include "qgspointcloudattributebyramprenderer.h"
24 #include "qgspointcloudrgbrenderer.h"
25 #include "qgspointcloudclassifiedrenderer.h"
26 #include "qgsdoublevalidator.h"
27 #include "qgspointcloudclassifiedrendererwidget.h"
28 #include "qgspointcloudlayerelevationproperties.h"
29 
QgsPointCloud3DSymbolWidget(QgsPointCloudLayer * layer,QgsPointCloud3DSymbol * symbol,QWidget * parent)30 QgsPointCloud3DSymbolWidget::QgsPointCloud3DSymbolWidget( QgsPointCloudLayer *layer, QgsPointCloud3DSymbol *symbol, QWidget *parent )
31   : QWidget( parent )
32   , mLayer( layer )
33 {
34   setupUi( this );
35 
36   mPointSizeSpinBox->setClearValue( 2.0 );
37   mMaxScreenErrorSpinBox->setClearValue( 1.0 );
38 
39   mColorRampShaderMinEdit->setShowClearButton( false );
40   mColorRampShaderMaxEdit->setShowClearButton( false );
41 
42   mRenderingParameterComboBox->setLayer( layer );
43   mRenderingParameterComboBox->setFilters( QgsPointCloudAttributeProxyModel::AllTypes );
44   mRenderingParameterComboBox->setAllowEmptyAttributeName( false );
45 
46   mSingleColorBtn->setAllowOpacity( false );
47   mSingleColorBtn->setColorDialogTitle( tr( "Select Point Color" ) );
48   mSingleColorBtn->setColor( QColor( 0, 0, 255 ) ); // default color
49 
50   mRenderingStyleComboBox->addItem( tr( "No Rendering" ), QString() );
51   mRenderingStyleComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "styleicons/singlecolor.svg" ) ), tr( "Single Color" ), QStringLiteral( "single-color" ) );
52   mRenderingStyleComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "styleicons/singlebandpseudocolor.svg" ) ), tr( "Attribute by Ramp" ), QStringLiteral( "color-ramp" ) );
53   mRenderingStyleComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "styleicons/multibandcolor.svg" ) ), tr( "RGB" ), QStringLiteral( "rgb" ) );
54   mRenderingStyleComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "styleicons/paletted.svg" ) ), tr( "Classification" ), QStringLiteral( "classification" ) );
55 
56   connect( mRedMinLineEdit, &QLineEdit::textChanged, this, &QgsPointCloud3DSymbolWidget::mRedMinLineEdit_textChanged );
57   connect( mRedMaxLineEdit, &QLineEdit::textChanged, this, &QgsPointCloud3DSymbolWidget::mRedMaxLineEdit_textChanged );
58   connect( mGreenMinLineEdit, &QLineEdit::textChanged, this, &QgsPointCloud3DSymbolWidget::mGreenMinLineEdit_textChanged );
59   connect( mGreenMaxLineEdit, &QLineEdit::textChanged, this, &QgsPointCloud3DSymbolWidget::mGreenMaxLineEdit_textChanged );
60   connect( mBlueMinLineEdit, &QLineEdit::textChanged, this, &QgsPointCloud3DSymbolWidget::mBlueMinLineEdit_textChanged );
61   connect( mBlueMaxLineEdit, &QLineEdit::textChanged, this, &QgsPointCloud3DSymbolWidget::mBlueMaxLineEdit_textChanged );
62   createValidators();
63 
64   mRedAttributeComboBox->setAllowEmptyAttributeName( true );
65   mGreenAttributeComboBox->setAllowEmptyAttributeName( true );
66   mBlueAttributeComboBox->setAllowEmptyAttributeName( true );
67 
68   //contrast enhancement algorithms
69   mContrastEnhancementAlgorithmComboBox->addItem( tr( "No Enhancement" ), QgsContrastEnhancement::NoEnhancement );
70   mContrastEnhancementAlgorithmComboBox->addItem( tr( "Stretch to MinMax" ), QgsContrastEnhancement::StretchToMinimumMaximum );
71   mContrastEnhancementAlgorithmComboBox->addItem( tr( "Stretch and Clip to MinMax" ), QgsContrastEnhancement::StretchAndClipToMinimumMaximum );
72   mContrastEnhancementAlgorithmComboBox->addItem( tr( "Clip to MinMax" ), QgsContrastEnhancement::ClipToMinimumMaximum );
73 
74   mRedAttributeComboBox->setLayer( layer );
75   mGreenAttributeComboBox->setLayer( layer );
76   mBlueAttributeComboBox->setLayer( layer );
77 
78   connect( mRedAttributeComboBox, &QgsPointCloudAttributeComboBox::attributeChanged, this, &QgsPointCloud3DSymbolWidget::redAttributeChanged );
79   connect( mGreenAttributeComboBox, &QgsPointCloudAttributeComboBox::attributeChanged, this, &QgsPointCloud3DSymbolWidget::greenAttributeChanged );
80   connect( mBlueAttributeComboBox, &QgsPointCloudAttributeComboBox::attributeChanged, this, &QgsPointCloud3DSymbolWidget::blueAttributeChanged );
81   connect( mContrastEnhancementAlgorithmComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsPointCloud3DSymbolWidget::emitChangedSignal );
82 
83   // set nice initial values
84   redAttributeChanged();
85   greenAttributeChanged();
86   blueAttributeChanged();
87 
88   mRenderingStyleComboBox->setCurrentIndex( 0 );
89   mStackedWidget->setCurrentIndex( 0 );
90 
91   whileBlocking( mPointBudgetSpinBox )->setMinimum( std::min( mLayer->pointCount() / 2, ( qint64 )100000 ) );
92   whileBlocking( mPointBudgetSpinBox )->setMaximum( mLayer->pointCount() + 1 );
93   whileBlocking( mPointBudgetSpinBox )->setValue( 1000000 );
94 
95   if ( symbol )
96     setSymbol( symbol );
97 
98   connect( mPointSizeSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsPointCloud3DSymbolWidget::emitChangedSignal );
99   connect( mRenderingStyleComboBox, qOverload< int >( &QComboBox::currentIndexChanged ), this, &QgsPointCloud3DSymbolWidget::onRenderingStyleChanged );
100   connect( mScalarRecalculateMinMaxButton, &QPushButton::clicked, this, &QgsPointCloud3DSymbolWidget::setMinMaxFromLayer );
101   connect( mColorRampShaderWidget, &QgsColorRampShaderWidget::widgetChanged, this, &QgsPointCloud3DSymbolWidget::emitChangedSignal );
102   connect( mSingleColorBtn, &QgsColorButton::colorChanged, this, &QgsPointCloud3DSymbolWidget::emitChangedSignal );
103   connect( mRenderingParameterComboBox, &QgsPointCloudAttributeComboBox::attributeChanged, this, &QgsPointCloud3DSymbolWidget::rampAttributeChanged );
104   connect( mColorRampShaderMinEdit, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsPointCloud3DSymbolWidget::minMaxChanged );
105   connect( mColorRampShaderMaxEdit, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsPointCloud3DSymbolWidget::minMaxChanged );
106 
107   connect( mMaxScreenErrorSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, [&]() { emitChangedSignal(); } );
108   connect( mShowBoundingBoxesCheckBox, &QCheckBox::stateChanged, this, [&]() { emitChangedSignal(); } );
109   connect( mPointBudgetSpinBox, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, [&]() { emitChangedSignal(); } );
110 
111   if ( !symbol ) // if we have a symbol, this was already handled in setSymbol above
112     rampAttributeChanged();
113 
114   mClassifiedRendererWidget = new QgsPointCloudClassifiedRendererWidget( layer, nullptr );
115   mClassifiedRendererWidget->setParent( this );
116   mClassifiedRenderingLayout->addWidget( mClassifiedRendererWidget );
117 
118   connect( mClassifiedRendererWidget, &QgsPointCloudClassifiedRendererWidget::widgetChanged, this, &QgsPointCloud3DSymbolWidget::emitChangedSignal );
119 }
120 
setSymbol(QgsPointCloud3DSymbol * symbol)121 void QgsPointCloud3DSymbolWidget::setSymbol( QgsPointCloud3DSymbol *symbol )
122 {
123   mBlockChangedSignals++;
124   if ( !symbol )
125   {
126     mRenderingStyleComboBox->setCurrentIndex( 0 );
127     mStackedWidget->setCurrentIndex( 0 );
128     mBlockChangedSignals--;
129     return;
130   }
131 
132   mRenderingStyleComboBox->setCurrentIndex( mRenderingStyleComboBox->findData( symbol->symbolType() ) );
133   mPointSizeSpinBox->setValue( symbol->pointSize() );
134 
135   if ( symbol->symbolType() == QLatin1String( "single-color" ) )
136   {
137     mStackedWidget->setCurrentIndex( 1 );
138     QgsSingleColorPointCloud3DSymbol *symb = dynamic_cast<QgsSingleColorPointCloud3DSymbol *>( symbol );
139     mSingleColorBtn->setColor( symb->singleColor() );
140   }
141   else if ( symbol->symbolType() == QLatin1String( "color-ramp" ) )
142   {
143     mStackedWidget->setCurrentIndex( 2 );
144     QgsColorRampPointCloud3DSymbol *symb = dynamic_cast<QgsColorRampPointCloud3DSymbol *>( symbol );
145 
146     // we will be restoring the existing ramp classes -- we don't want to regenerate any automatically!
147     mBlockSetMinMaxFromLayer = true;
148     mRenderingParameterComboBox->setAttribute( symb->attribute() );
149 
150     mColorRampShaderMinEdit->setValue( symb->colorRampShaderMin() );
151     mColorRampShaderMaxEdit->setValue( symb->colorRampShaderMax() );
152 
153     whileBlocking( mColorRampShaderWidget )->setFromShader( symb->colorRampShader() );
154     whileBlocking( mColorRampShaderWidget )->setMinimumMaximum( symb->colorRampShaderMin(), symb->colorRampShaderMax() );
155     mBlockSetMinMaxFromLayer = false;
156   }
157   else if ( symbol->symbolType() == QLatin1String( "rgb" ) )
158   {
159     mStackedWidget->setCurrentIndex( 3 );
160 
161     QgsRgbPointCloud3DSymbol *symb = dynamic_cast<QgsRgbPointCloud3DSymbol *>( symbol );
162     mRedAttributeComboBox->setAttribute( symb->redAttribute() );
163     mGreenAttributeComboBox->setAttribute( symb->greenAttribute() );
164     mBlueAttributeComboBox->setAttribute( symb->blueAttribute() );
165 
166     mDisableMinMaxWidgetRefresh++;
167     setMinMaxValue( symb->redContrastEnhancement(), mRedMinLineEdit, mRedMaxLineEdit );
168     setMinMaxValue( symb->greenContrastEnhancement(), mGreenMinLineEdit, mGreenMaxLineEdit );
169     setMinMaxValue( symb->blueContrastEnhancement(), mBlueMinLineEdit, mBlueMaxLineEdit );
170     mDisableMinMaxWidgetRefresh--;
171   }
172   else if ( symbol->symbolType() == QLatin1String( "classification" ) )
173   {
174     mStackedWidget->setCurrentIndex( 4 );
175     QgsClassificationPointCloud3DSymbol *symb = dynamic_cast<QgsClassificationPointCloud3DSymbol *>( symbol );
176     mClassifiedRendererWidget->setFromCategories( symb->categoriesList(), symb->attribute() );
177   }
178   else
179   {
180     mStackedWidget->setCurrentIndex( 0 );
181   }
182 
183   mBlockChangedSignals--;
184 }
185 
setDockMode(bool dockMode)186 void QgsPointCloud3DSymbolWidget::setDockMode( bool dockMode )
187 {
188   if ( mClassifiedRendererWidget )
189     mClassifiedRendererWidget->setDockMode( dockMode );
190 }
191 
symbol() const192 QgsPointCloud3DSymbol *QgsPointCloud3DSymbolWidget::symbol() const
193 {
194   QgsPointCloud3DSymbol *retSymb = nullptr;
195   const QString symbolType = mRenderingStyleComboBox->currentData().toString();
196 
197   if ( symbolType == QLatin1String( "single-color" ) )
198   {
199     QgsSingleColorPointCloud3DSymbol *symb = new QgsSingleColorPointCloud3DSymbol;
200     symb->setPointSize( mPointSizeSpinBox->value() );
201     symb->setSingleColor( mSingleColorBtn->color() );
202     retSymb = symb;
203   }
204   else if ( symbolType == QLatin1String( "color-ramp" ) )
205   {
206     QgsColorRampPointCloud3DSymbol *symb = new QgsColorRampPointCloud3DSymbol;
207     symb->setAttribute( mRenderingParameterComboBox->currentText() );
208     symb->setPointSize( mPointSizeSpinBox->value() );
209     symb->setColorRampShader( mColorRampShaderWidget->shader() );
210     symb->setColorRampShaderMinMax( mColorRampShaderMinEdit->value(), mColorRampShaderMaxEdit->value() );
211     retSymb = symb;
212   }
213   else if ( symbolType == QLatin1String( "rgb" ) )
214   {
215     QgsRgbPointCloud3DSymbol *symb = new QgsRgbPointCloud3DSymbol;
216     symb->setPointSize( mPointSizeSpinBox->value() );
217 
218     symb->setRedAttribute( mRedAttributeComboBox->currentAttribute() );
219     symb->setGreenAttribute( mGreenAttributeComboBox->currentAttribute() );
220     symb->setBlueAttribute( mBlueAttributeComboBox->currentAttribute() );
221 
222     setCustomMinMaxValues( symb );
223     retSymb = symb;
224   }
225   else if ( symbolType == QLatin1String( "classification" ) )
226   {
227     QgsClassificationPointCloud3DSymbol *symb = new QgsClassificationPointCloud3DSymbol;
228     symb->setPointSize( mPointSizeSpinBox->value() );
229 
230     symb->setAttribute( mClassifiedRendererWidget->attribute() );
231     symb->setCategoriesList( mClassifiedRendererWidget->categoriesList() );
232     retSymb = symb;
233   }
234 
235   return retSymb;
236 }
237 
setColorRampMinMax(double min,double max)238 void QgsPointCloud3DSymbolWidget::setColorRampMinMax( double min, double max )
239 {
240   whileBlocking( mColorRampShaderMinEdit )->setValue( min );
241   whileBlocking( mColorRampShaderMaxEdit )->setValue( max );
242 }
243 
createValidators()244 void QgsPointCloud3DSymbolWidget::createValidators()
245 {
246   mRedMinLineEdit->setValidator( new QgsDoubleValidator( mRedMinLineEdit ) );
247   mRedMaxLineEdit->setValidator( new QgsDoubleValidator( mRedMinLineEdit ) );
248   mGreenMinLineEdit->setValidator( new QgsDoubleValidator( mGreenMinLineEdit ) );
249   mGreenMaxLineEdit->setValidator( new QgsDoubleValidator( mGreenMinLineEdit ) );
250   mBlueMinLineEdit->setValidator( new QgsDoubleValidator( mBlueMinLineEdit ) );
251   mBlueMaxLineEdit->setValidator( new QgsDoubleValidator( mBlueMinLineEdit ) );
252 }
253 
setCustomMinMaxValues(QgsRgbPointCloud3DSymbol * symbol) const254 void QgsPointCloud3DSymbolWidget::setCustomMinMaxValues( QgsRgbPointCloud3DSymbol *symbol ) const
255 {
256   if ( !symbol )
257   {
258     return;
259   }
260 
261   if ( mContrastEnhancementAlgorithmComboBox->currentData().toInt() ==
262        QgsContrastEnhancement::NoEnhancement )
263   {
264     symbol->setRedContrastEnhancement( nullptr );
265     symbol->setGreenContrastEnhancement( nullptr );
266     symbol->setBlueContrastEnhancement( nullptr );
267     return;
268   }
269 
270   QgsContrastEnhancement *redEnhancement = nullptr;
271   QgsContrastEnhancement *greenEnhancement = nullptr;
272   QgsContrastEnhancement *blueEnhancement = nullptr;
273 
274   bool redMinOk, redMaxOk;
275   const double redMin = QgsDoubleValidator::toDouble( mRedMinLineEdit->text(), &redMinOk );
276   const double redMax = QgsDoubleValidator::toDouble( mRedMaxLineEdit->text(), &redMaxOk );
277   if ( redMinOk && redMaxOk && !mRedAttributeComboBox->currentAttribute().isEmpty() )
278   {
279     redEnhancement = new QgsContrastEnhancement( Qgis::DataType::UnknownDataType );
280     redEnhancement->setMinimumValue( redMin );
281     redEnhancement->setMaximumValue( redMax );
282   }
283 
284   bool greenMinOk, greenMaxOk;
285   const double greenMin = QgsDoubleValidator::toDouble( mGreenMinLineEdit->text(), &greenMinOk );
286   const double greenMax = QgsDoubleValidator::toDouble( mGreenMaxLineEdit->text(), &greenMaxOk );
287   if ( greenMinOk && greenMaxOk && !mGreenAttributeComboBox->currentAttribute().isEmpty() )
288   {
289     greenEnhancement = new QgsContrastEnhancement( Qgis::DataType::UnknownDataType );
290     greenEnhancement->setMinimumValue( greenMin );
291     greenEnhancement->setMaximumValue( greenMax );
292   }
293 
294   bool blueMinOk, blueMaxOk;
295   const double blueMin = QgsDoubleValidator::toDouble( mBlueMinLineEdit->text(), &blueMinOk );
296   const double blueMax = QgsDoubleValidator::toDouble( mBlueMaxLineEdit->text(), &blueMaxOk );
297   if ( blueMinOk && blueMaxOk && !mBlueAttributeComboBox->currentAttribute().isEmpty() )
298   {
299     blueEnhancement = new QgsContrastEnhancement( Qgis::DataType::UnknownDataType );
300     blueEnhancement->setMinimumValue( blueMin );
301     blueEnhancement->setMaximumValue( blueMax );
302   }
303 
304   if ( redEnhancement )
305   {
306     redEnhancement->setContrastEnhancementAlgorithm( static_cast< QgsContrastEnhancement::ContrastEnhancementAlgorithm >(
307           ( mContrastEnhancementAlgorithmComboBox->currentData().toInt() ) ) );
308   }
309   if ( greenEnhancement )
310   {
311     greenEnhancement->setContrastEnhancementAlgorithm( static_cast< QgsContrastEnhancement::ContrastEnhancementAlgorithm >(
312           ( mContrastEnhancementAlgorithmComboBox->currentData().toInt() ) ) );
313   }
314   if ( blueEnhancement )
315   {
316     blueEnhancement->setContrastEnhancementAlgorithm( static_cast< QgsContrastEnhancement::ContrastEnhancementAlgorithm >(
317           ( mContrastEnhancementAlgorithmComboBox->currentData().toInt() ) ) );
318   }
319   symbol->setRedContrastEnhancement( redEnhancement );
320   symbol->setGreenContrastEnhancement( greenEnhancement );
321   symbol->setBlueContrastEnhancement( blueEnhancement );
322 }
323 
minMaxModified()324 void QgsPointCloud3DSymbolWidget::minMaxModified()
325 {
326   if ( !mDisableMinMaxWidgetRefresh )
327   {
328     if ( ( QgsContrastEnhancement::ContrastEnhancementAlgorithm )( mContrastEnhancementAlgorithmComboBox->currentData().toInt() ) == QgsContrastEnhancement::NoEnhancement )
329     {
330       mContrastEnhancementAlgorithmComboBox->setCurrentIndex(
331         mContrastEnhancementAlgorithmComboBox->findData( ( int ) QgsContrastEnhancement::StretchToMinimumMaximum ) );
332     }
333     emitChangedSignal();
334   }
335 }
336 
setMinMaxValue(const QgsContrastEnhancement * ce,QLineEdit * minEdit,QLineEdit * maxEdit)337 void QgsPointCloud3DSymbolWidget::setMinMaxValue( const QgsContrastEnhancement *ce, QLineEdit *minEdit, QLineEdit *maxEdit )
338 {
339   if ( !minEdit || !maxEdit )
340   {
341     return;
342   }
343 
344   if ( !ce )
345   {
346     minEdit->clear();
347     maxEdit->clear();
348     return;
349   }
350 
351   minEdit->setText( QLocale().toString( ce->minimumValue() ) );
352   maxEdit->setText( QLocale().toString( ce->maximumValue() ) );
353 
354   // QgsMultiBandColorRenderer is using individual contrast enhancements for each
355   // band, but this widget GUI has one for all
356   mContrastEnhancementAlgorithmComboBox->setCurrentIndex( mContrastEnhancementAlgorithmComboBox->findData(
357         static_cast< int >( ce->contrastEnhancementAlgorithm() ) ) );
358 }
359 
reloadColorRampShaderMinMax()360 void QgsPointCloud3DSymbolWidget::reloadColorRampShaderMinMax()
361 {
362   const double min = mColorRampShaderMinEdit->value();
363   const double max = mColorRampShaderMaxEdit->value();
364   mColorRampShaderWidget->setMinimumMaximum( min, max );
365   mColorRampShaderWidget->classify();
366 }
367 
onRenderingStyleChanged()368 void QgsPointCloud3DSymbolWidget::onRenderingStyleChanged()
369 {
370   if ( mBlockChangedSignals )
371     return;
372 
373   mStackedWidget->setCurrentIndex( mRenderingStyleComboBox->currentIndex() );
374 
375   // copy settings from 2d renderer, if possible!
376   if ( mLayer )
377   {
378     const QString newSymbolType = mRenderingStyleComboBox->currentData().toString();
379     if ( newSymbolType == QLatin1String( "color-ramp" ) && mLayer->renderer()->type() == QLatin1String( "ramp" ) )
380     {
381       const QgsPointCloudAttributeByRampRenderer *renderer2d = dynamic_cast< const QgsPointCloudAttributeByRampRenderer * >( mLayer->renderer() );
382       mBlockChangedSignals++;
383       mRenderingParameterComboBox->setAttribute( renderer2d->attribute() );
384       mColorRampShaderMinEdit->setValue( renderer2d->minimum() );
385       mColorRampShaderMaxEdit->setValue( renderer2d->maximum() );
386       whileBlocking( mColorRampShaderWidget )->setFromShader( renderer2d->colorRampShader() );
387       whileBlocking( mColorRampShaderWidget )->setMinimumMaximum( renderer2d->minimum(), renderer2d->maximum() );
388       mBlockChangedSignals--;
389     }
390     else if ( newSymbolType == QLatin1String( "rgb" ) )
391     {
392       const QgsPointCloudRgbRenderer *renderer2d = dynamic_cast< const QgsPointCloudRgbRenderer * >( mLayer->renderer() );
393       mBlockChangedSignals++;
394       if ( renderer2d )
395       {
396         mRedAttributeComboBox->setAttribute( renderer2d->redAttribute() );
397         mGreenAttributeComboBox->setAttribute( renderer2d->greenAttribute() );
398         mBlueAttributeComboBox->setAttribute( renderer2d->blueAttribute() );
399 
400         mDisableMinMaxWidgetRefresh++;
401         setMinMaxValue( renderer2d->redContrastEnhancement(), mRedMinLineEdit, mRedMaxLineEdit );
402         setMinMaxValue( renderer2d->greenContrastEnhancement(), mGreenMinLineEdit, mGreenMaxLineEdit );
403         setMinMaxValue( renderer2d->blueContrastEnhancement(), mBlueMinLineEdit, mBlueMaxLineEdit );
404         mDisableMinMaxWidgetRefresh--;
405       }
406       else
407       {
408         if ( mRedAttributeComboBox->findText( QStringLiteral( "Red" ) ) > -1 && mRedAttributeComboBox->findText( QStringLiteral( "Green" ) ) > -1 &&
409              mRedAttributeComboBox->findText( QStringLiteral( "Blue" ) ) > -1 )
410         {
411           mRedAttributeComboBox->setAttribute( QStringLiteral( "Red" ) );
412           mGreenAttributeComboBox->setAttribute( QStringLiteral( "Green" ) );
413           mBlueAttributeComboBox->setAttribute( QStringLiteral( "Blue" ) );
414         }
415         else
416         {
417           mRedAttributeComboBox->setCurrentIndex( mRedAttributeComboBox->count() > 1 ? 1 : 0 );
418           mGreenAttributeComboBox->setCurrentIndex( mGreenAttributeComboBox->count() > 2 ? 2 : 0 );
419           mBlueAttributeComboBox->setCurrentIndex( mBlueAttributeComboBox->count() > 3 ? 3 : 0 );
420         }
421         redAttributeChanged();
422         greenAttributeChanged();
423         blueAttributeChanged();
424       }
425 
426       ( void )( renderer2d );
427       mBlockChangedSignals--;
428     }
429     else if ( newSymbolType == QLatin1String( "classification" ) )
430     {
431       const QgsPointCloudClassifiedRenderer *renderer2d = dynamic_cast< const QgsPointCloudClassifiedRenderer * >( mLayer->renderer() );
432       mBlockChangedSignals++;
433       if ( renderer2d )
434       {
435         mClassifiedRendererWidget->setFromCategories( renderer2d->categories(), renderer2d->attribute() );
436       }
437       else
438       {
439         mClassifiedRendererWidget->setFromCategories( QgsPointCloudClassifiedRenderer::defaultCategories(), QString() );
440       }
441 
442       ( void )( renderer2d );
443       mBlockChangedSignals--;
444     }
445   }
446 
447   emitChangedSignal();
448 }
449 
emitChangedSignal()450 void QgsPointCloud3DSymbolWidget::emitChangedSignal()
451 {
452   if ( mBlockChangedSignals )
453     return;
454 
455   emit changed();
456 }
457 
rampAttributeChanged()458 void QgsPointCloud3DSymbolWidget::rampAttributeChanged()
459 {
460   if ( mLayer && mLayer->dataProvider() )
461   {
462     const QVariant min = mLayer->dataProvider()->metadataStatistic( mRenderingParameterComboBox->currentAttribute(), QgsStatisticalSummary::Min );
463     const QVariant max = mLayer->dataProvider()->metadataStatistic( mRenderingParameterComboBox->currentAttribute(), QgsStatisticalSummary::Max );
464     if ( min.isValid() && max.isValid() )
465     {
466       mProviderMin = min.toDouble();
467       mProviderMax = max.toDouble();
468     }
469     else
470     {
471       mProviderMin = std::numeric_limits< double >::quiet_NaN();
472       mProviderMax = std::numeric_limits< double >::quiet_NaN();
473     }
474 
475     if ( mRenderingParameterComboBox->currentAttribute() == QLatin1String( "Z" ) )
476     {
477       const double zScale = static_cast< const QgsPointCloudLayerElevationProperties * >( mLayer->elevationProperties() )->zScale();
478       const double zOffset = static_cast< const QgsPointCloudLayerElevationProperties * >( mLayer->elevationProperties() )->zOffset();
479       mProviderMin = mProviderMin * zScale + zOffset;
480       mProviderMax = mProviderMax * zScale + zOffset;
481     }
482   }
483   if ( !mBlockSetMinMaxFromLayer )
484     setMinMaxFromLayer();
485   mScalarRecalculateMinMaxButton->setEnabled( !std::isnan( mProviderMin ) && !std::isnan( mProviderMax ) );
486   emitChangedSignal();
487 }
488 
setMinMaxFromLayer()489 void QgsPointCloud3DSymbolWidget::setMinMaxFromLayer()
490 {
491   if ( std::isnan( mProviderMin ) || std::isnan( mProviderMax ) )
492     return;
493 
494   mBlockMinMaxChanged = true;
495   mColorRampShaderMinEdit->setValue( mProviderMin );
496   mColorRampShaderMaxEdit->setValue( mProviderMax );
497   mBlockMinMaxChanged = false;
498 
499   minMaxChanged();
500 }
501 
minMaxChanged()502 void QgsPointCloud3DSymbolWidget::minMaxChanged()
503 {
504   if ( mBlockMinMaxChanged )
505     return;
506 
507   mColorRampShaderWidget->setMinimumMaximumAndClassify( mColorRampShaderMinEdit->value(), mColorRampShaderMaxEdit->value() );
508 }
509 
mRedMinLineEdit_textChanged(const QString &)510 void QgsPointCloud3DSymbolWidget::mRedMinLineEdit_textChanged( const QString & )
511 {
512   minMaxModified();
513 }
514 
mRedMaxLineEdit_textChanged(const QString &)515 void QgsPointCloud3DSymbolWidget::mRedMaxLineEdit_textChanged( const QString & )
516 {
517   minMaxModified();
518 }
519 
mGreenMinLineEdit_textChanged(const QString &)520 void QgsPointCloud3DSymbolWidget::mGreenMinLineEdit_textChanged( const QString & )
521 {
522   minMaxModified();
523 }
524 
mGreenMaxLineEdit_textChanged(const QString &)525 void QgsPointCloud3DSymbolWidget::mGreenMaxLineEdit_textChanged( const QString & )
526 {
527   minMaxModified();
528 }
529 
mBlueMinLineEdit_textChanged(const QString &)530 void QgsPointCloud3DSymbolWidget::mBlueMinLineEdit_textChanged( const QString & )
531 {
532   minMaxModified();
533 }
534 
mBlueMaxLineEdit_textChanged(const QString &)535 void QgsPointCloud3DSymbolWidget::mBlueMaxLineEdit_textChanged( const QString & )
536 {
537   minMaxModified();
538 }
539 
redAttributeChanged()540 void QgsPointCloud3DSymbolWidget::redAttributeChanged()
541 {
542   if ( mLayer && mLayer->dataProvider() )
543   {
544     const QVariant max = mLayer->dataProvider()->metadataStatistic( mRedAttributeComboBox->currentAttribute(), QgsStatisticalSummary::Max );
545     if ( max.isValid() )
546     {
547       const int maxValue = max.toInt();
548       mDisableMinMaxWidgetRefresh++;
549       mRedMinLineEdit->setText( QLocale().toString( 0 ) );
550 
551       // try and guess suitable range from input max values -- we don't just take the provider max value directly here, but rather see if it's
552       // likely to be 8 bit or 16 bit color values
553       mRedMaxLineEdit->setText( QLocale().toString( maxValue > 255 ? 65535 : 255 ) );
554       mDisableMinMaxWidgetRefresh--;
555       emitChangedSignal();
556     }
557   }
558 }
559 
greenAttributeChanged()560 void QgsPointCloud3DSymbolWidget::greenAttributeChanged()
561 {
562   if ( mLayer && mLayer->dataProvider() )
563   {
564     const QVariant max = mLayer->dataProvider()->metadataStatistic( mGreenAttributeComboBox->currentAttribute(), QgsStatisticalSummary::Max );
565     if ( max.isValid() )
566     {
567       const int maxValue = max.toInt();
568       mDisableMinMaxWidgetRefresh++;
569       mGreenMinLineEdit->setText( QLocale().toString( 0 ) );
570 
571       // try and guess suitable range from input max values -- we don't just take the provider max value directly here, but rather see if it's
572       // likely to be 8 bit or 16 bit color values
573       mGreenMaxLineEdit->setText( QLocale().toString( maxValue > 255 ? 65535 : 255 ) );
574       mDisableMinMaxWidgetRefresh--;
575       emitChangedSignal();
576     }
577   }
578 }
579 
blueAttributeChanged()580 void QgsPointCloud3DSymbolWidget::blueAttributeChanged()
581 {
582   if ( mLayer && mLayer->dataProvider() )
583   {
584     const QVariant max = mLayer->dataProvider()->metadataStatistic( mBlueAttributeComboBox->currentAttribute(), QgsStatisticalSummary::Max );
585     if ( max.isValid() )
586     {
587       const int maxValue = max.toInt();
588       mDisableMinMaxWidgetRefresh++;
589       mBlueMinLineEdit->setText( QLocale().toString( 0 ) );
590 
591       // try and guess suitable range from input max values -- we don't just take the provider max value directly here, but rather see if it's
592       // likely to be 8 bit or 16 bit color values
593       mBlueMaxLineEdit->setText( QLocale().toString( maxValue > 255 ? 65535 : 255 ) );
594       mDisableMinMaxWidgetRefresh--;
595       emitChangedSignal();
596     }
597   }
598 }
599 
setMaximumScreenError(double maxScreenError)600 void QgsPointCloud3DSymbolWidget::setMaximumScreenError( double maxScreenError )
601 {
602   whileBlocking( mMaxScreenErrorSpinBox )->setValue( maxScreenError );
603 }
604 
maximumScreenError() const605 double QgsPointCloud3DSymbolWidget::maximumScreenError() const
606 {
607   return mMaxScreenErrorSpinBox->value();
608 }
609 
setShowBoundingBoxes(bool showBoundingBoxes)610 void QgsPointCloud3DSymbolWidget::setShowBoundingBoxes( bool showBoundingBoxes )
611 {
612   whileBlocking( mShowBoundingBoxesCheckBox )->setChecked( showBoundingBoxes );
613 }
614 
setPointBudget(double budget)615 void QgsPointCloud3DSymbolWidget::setPointBudget( double budget )
616 {
617   whileBlocking( mPointBudgetSpinBox )->setValue( budget );
618 }
619 
pointBudget() const620 double QgsPointCloud3DSymbolWidget::pointBudget() const
621 {
622   return mPointBudgetSpinBox->value();
623 }
624 
setPointCloudSize(int size)625 void QgsPointCloud3DSymbolWidget::setPointCloudSize( int size )
626 {
627   mPointCloudSizeLabel->setText( QStringLiteral( "%1 points" ).arg( size ) );
628 }
629 
showBoundingBoxes() const630 bool QgsPointCloud3DSymbolWidget::showBoundingBoxes() const
631 {
632   return mShowBoundingBoxesCheckBox->isChecked();
633 }
634 
connectChildPanels(QgsPanelWidget * parent)635 void QgsPointCloud3DSymbolWidget::connectChildPanels( QgsPanelWidget *parent )
636 {
637   parent->connectChildPanel( mClassifiedRendererWidget );
638 }
639