19#include "moc_qgspointcloudclassifiedrendererwidget.cpp"
33#include <QInputDialog>
37QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
38 : QAbstractItemModel( parent )
39 , mMimeFormat( QStringLiteral(
"application/x-qgspointcloudclassifiedrenderermodel" ) )
45 if ( !mCategories.empty() )
47 beginRemoveRows( QModelIndex(), 0, std::max< int >( mCategories.size() - 1, 0 ) );
51 if ( categories.size() > 0 )
53 beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
54 mCategories = categories;
61 const int idx = mCategories.size();
62 beginInsertRows( QModelIndex(), idx, idx );
63 mCategories.append( cat );
66 emit categoriesChanged();
71 const int row = index.row();
72 if ( row >= mCategories.size() )
76 return mCategories.at( row );
79Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags(
const QModelIndex &index )
const
81 if ( !index.isValid() || mCategories.empty() )
83 return Qt::ItemIsDropEnabled;
86 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
87 if ( index.column() == 1 ||
88 index.column() == 2 ||
91 flags |= Qt::ItemIsEditable;
96Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions()
const
98 return Qt::MoveAction;
101QVariant QgsPointCloudClassifiedRendererModel::data(
const QModelIndex &index,
int role )
const
103 if ( !index.isValid() || mCategories.empty() )
110 case Qt::CheckStateRole:
112 if ( index.column() == 0 )
114 return category.
renderState() ? Qt::Checked : Qt::Unchecked;
119 case Qt::DisplayRole:
120 case Qt::ToolTipRole:
122 switch ( index.column() )
125 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
127 return QString::number( category.
value() );
129 return category.
label();
131 const float value = mPercentages.value( category.
value(), -1 );
135 else if ( value != 0 && std::round( value * 10 ) < 1 )
136 str = QStringLiteral(
"< " ) + QLocale().toString( 0.1,
'f', 1 );
138 str = QLocale().toString( mPercentages.value( category.
value() ),
'f', 1 );
144 case Qt::DecorationRole:
146 if ( index.column() == 0 )
149 QPixmap pix( iconSize, iconSize );
150 pix.fill( category.
color() );
156 case Qt::TextAlignmentRole:
158 if ( index.column() == 0 )
159 return static_cast<Qt::Alignment::Int
>( Qt::AlignHCenter );
160 if ( index.column() == 4 )
161 return static_cast<Qt::Alignment::Int
>( Qt::AlignRight );
162 return static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
167 switch ( index.column() )
170 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
172 return QString::number( category.
value() );
174 return category.
label();
183bool QgsPointCloudClassifiedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
185 if ( !index.isValid() )
188 if ( index.column() == 0 && role == Qt::CheckStateRole )
190 mCategories[ index.row() ].setRenderState( value == Qt::Checked );
191 emit dataChanged( index, index );
192 emit categoriesChanged();
196 if ( role != Qt::EditRole )
199 switch ( index.column() )
203 const double size = value.toDouble();
204 mCategories[ index.row() ].setPointSize( size );
209 const int val = value.toInt();
210 mCategories[ index.row() ].setValue( val );
215 mCategories[ index.row() ].setLabel( value.toString() );
222 emit dataChanged( index, index );
223 emit categoriesChanged();
227QVariant QgsPointCloudClassifiedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
229 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
232 lst << tr(
"Color" ) << tr(
"Size" ) << tr(
"Value" ) << tr(
"Legend" ) << tr(
"Percentage" );
233 return lst.value( section );
238int QgsPointCloudClassifiedRendererModel::rowCount(
const QModelIndex &parent )
const
240 if ( parent.isValid() )
244 return mCategories.size();
247int QgsPointCloudClassifiedRendererModel::columnCount(
const QModelIndex &index )
const
253QModelIndex QgsPointCloudClassifiedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
255 if ( hasIndex( row, column, parent ) )
257 return createIndex( row, column );
259 return QModelIndex();
262QModelIndex QgsPointCloudClassifiedRendererModel::parent(
const QModelIndex &index )
const
265 return QModelIndex();
268QStringList QgsPointCloudClassifiedRendererModel::mimeTypes()
const
271 types << mMimeFormat;
275QMimeData *QgsPointCloudClassifiedRendererModel::mimeData(
const QModelIndexList &indexes )
const
277 QMimeData *mimeData =
new QMimeData();
278 QByteArray encodedData;
280 QDataStream stream( &encodedData, QIODevice::WriteOnly );
283 const auto constIndexes = indexes;
284 for (
const QModelIndex &index : constIndexes )
286 if ( !index.isValid() || index.column() != 0 )
289 stream << index.row();
291 mimeData->setData( mMimeFormat, encodedData );
295bool QgsPointCloudClassifiedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex &parent )
299 if ( action != Qt::MoveAction )
302 if ( !data->hasFormat( mMimeFormat ) )
305 QByteArray encodedData = data->data( mMimeFormat );
306 QDataStream stream( &encodedData, QIODevice::ReadOnly );
309 while ( !stream.atEnd() )
316 int to = parent.row();
320 to = mCategories.size();
321 for (
int i = rows.size() - 1; i >= 0; i-- )
327 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
329 mCategories.move( rows[i], t );
333 for (
int j = 0; j < i; j++ )
335 if ( to < rows[j] && rows[i] > rows[j] )
342 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
343 emit categoriesChanged();
347void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
349 std::sort( rows.begin(), rows.end() );
350 for (
int i = rows.size() - 1; i >= 0; i-- )
352 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
353 mCategories.removeAt( rows[i] );
356 emit categoriesChanged();
359void QgsPointCloudClassifiedRendererModel::removeAllRows()
361 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
364 emit categoriesChanged();
367void QgsPointCloudClassifiedRendererModel::setCategoryColor(
int row,
const QColor &color )
369 mCategories[row].setColor( color );
370 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
371 emit categoriesChanged();
374void QgsPointCloudClassifiedRendererModel::setCategoryPointSize(
int row,
double size )
376 mCategories[row].setPointSize( size );
377 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
378 emit categoriesChanged();
382QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
386void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element,
const QStyleOption *option, QPainter *painter,
const QWidget *widget )
const
388 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
390 QStyleOption opt( *option );
391 opt.rect.setLeft( 0 );
393 opt.rect.setHeight( 0 );
395 opt.rect.setRight( widget->width() );
396 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
399 QProxyStyle::drawPrimitive( element, option, painter, widget );
408 mAttributeComboBox->setAllowEmptyAttributeName(
true );
411 mModel =
new QgsPointCloudClassifiedRendererModel(
this );
415 mAttributeComboBox->setLayer( layer );
417 setFromRenderer( layer->
renderer() );
420 viewCategories->setModel( mModel );
421 viewCategories->resizeColumnToContents( 0 );
422 viewCategories->resizeColumnToContents( 1 );
423 viewCategories->resizeColumnToContents( 2 );
424 viewCategories->resizeColumnToContents( 3 );
426 viewCategories->setStyle(
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
429 this, &QgsPointCloudClassifiedRendererWidget::attributeChanged );
430 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged,
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
432 connect( viewCategories, &QAbstractItemView::doubleClicked,
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
433 connect( btnAddCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
434 connect( btnDeleteCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
435 connect( btnDeleteAllCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
436 connect( btnAddCategory, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
438 contextMenu =
new QMenu( tr(
"Options" ),
this );
439 contextMenu->addAction( tr(
"Change &Color…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
440 contextMenu->addAction( tr(
"Change &Opacity…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
441 contextMenu->addAction( tr(
"Change &Size…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
443 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
444 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
445 connect( viewCategories, &QTreeView::customContextMenuRequested,
this, [ = ]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
450 return new QgsPointCloudClassifiedRendererWidget( layer, style );
460 std::unique_ptr< QgsPointCloudClassifiedRenderer > renderer = std::make_unique< QgsPointCloudClassifiedRenderer >();
461 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
462 renderer->setCategories( mModel->categories() );
464 return renderer.release();
469 return mModel->categories();
472QString QgsPointCloudClassifiedRendererWidget::attribute()
474 return mAttributeComboBox->currentAttribute();
477void QgsPointCloudClassifiedRendererWidget::attributeChanged()
479 if ( mBlockChangedSignal )
482 mBlockChangedSignal =
true;
483 mModel->removeAllRows();
484 mBlockChangedSignal =
false;
488void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
490 if ( mBlockChangedSignal )
493 updateCategoriesPercentages();
494 emit widgetChanged();
497void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked(
const QModelIndex &idx )
499 if ( idx.isValid() && idx.column() == 0 )
500 changeCategoryColor();
503void QgsPointCloudClassifiedRendererWidget::addCategories()
505 if ( !mLayer || !mLayer->dataProvider() )
509 const QString currentAttribute = mAttributeComboBox->currentAttribute();
514 const bool isClassificationAttribute = ( 0 == currentAttribute.compare( QStringLiteral(
"Classification" ), Qt::CaseInsensitive ) );
515 const bool isBooleanAttribute = ( 0 == currentAttribute.compare( QStringLiteral(
"Synthetic" ), Qt::CaseInsensitive ) ||
516 0 == currentAttribute.compare( QStringLiteral(
"KeyPoint" ), Qt::CaseInsensitive ) ||
517 0 == currentAttribute.compare( QStringLiteral(
"Withheld" ), Qt::CaseInsensitive ) ||
518 0 == currentAttribute.compare( QStringLiteral(
"Overlap" ), Qt::CaseInsensitive ) );
520 QList<int> providerCategories = stats.
classesOf( currentAttribute );
524 if ( isBooleanAttribute &&
525 ( providerCategories.isEmpty() || stats.
sampledPointsCount() < mLayer->pointCount() ) )
526 providerCategories = { 0, 1 };
530 mBlockChangedSignal =
true;
531 for (
const int &providerCategory : std::as_const( providerCategories ) )
537 if (
c.value() == providerCategory )
548 if ( isClassificationAttribute )
552 if (
c.value() == providerCategory )
560 mModel->addCategory( category );
562 mBlockChangedSignal =
false;
566void QgsPointCloudClassifiedRendererWidget::addCategory()
572 mModel->addCategory( cat );
575void QgsPointCloudClassifiedRendererWidget::deleteCategories()
577 const QList<int> categoryIndexes = selectedCategories();
578 mModel->deleteRows( categoryIndexes );
581void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
583 mModel->removeAllRows();
588 mBlockChangedSignal =
true;
591 mModel->setRendererCategories( classifiedRenderer->categories() );
592 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
598 mBlockChangedSignal =
false;
602void QgsPointCloudClassifiedRendererWidget::setFromCategories(
QgsPointCloudCategoryList categories,
const QString &attribute )
604 mBlockChangedSignal =
true;
605 mModel->setRendererCategories( categories );
606 if ( !attribute.isEmpty() )
608 mAttributeComboBox->setAttribute( attribute );
614 mBlockChangedSignal =
false;
618void QgsPointCloudClassifiedRendererWidget::initialize()
620 if ( mAttributeComboBox->findText( QStringLiteral(
"Classification" ) ) > -1 )
622 mAttributeComboBox->setAttribute( QStringLiteral(
"Classification" ) );
626 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
628 mModel->removeAllRows();
632void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
634 const QList<int> categoryList = selectedCategories();
635 if ( categoryList.isEmpty() )
646 colorWidget->
setPanelTitle( categoryList.count() == 1 ? category.
label() : tr(
"Select Color" ) );
652 for (
int row : categoryList )
654 mModel->setCategoryColor( row, newColor );
662 if ( newColor.isValid() )
664 for (
int row : categoryList )
666 mModel->setCategoryColor( row, newColor );
672void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
674 const QList<int> categoryList = selectedCategories();
675 if ( categoryList.isEmpty() )
680 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
683 const double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
686 for (
int row : categoryList )
689 QColor color = category.
color();
690 color.setAlphaF( opacity / 100.0 );
691 mModel->setCategoryColor( row, color );
696void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
698 const QList<int> categoryList = selectedCategories();
699 if ( categoryList.isEmpty() )
704 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
707 const double size = QInputDialog::getDouble(
this, tr(
"Point Size" ), tr(
"Change point size (set to 0 to reset to default point size)" ), oldSize, 0.0, 42.0, 1, &ok );
710 for (
int row : categoryList )
712 mModel->setCategoryPointSize( row, size );
717QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
720 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
721 for (
const QModelIndex &r : selectedRows )
725 rows.append( r.row() );
731int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
733 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
734 if ( !idx.isValid() )
739void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
741 QMap < int, float > percentages;
752 if ( classes.contains( category.
value() ) || statsExact )
753 percentages.insert( category.
value(), (
double ) classes.value( category.
value() ) / pointCount * 100 );
755 mModel->updateCategoriesPercentages( percentages );
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application's color scheme registry, used for managing color schemes.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), bool allowOpacity=false)
Returns a color selection from a color dialog.
QColor fetchRandomStyleColor() const
Returns a random color for use with a new symbol style (e.g.
void attributeChanged(const QString &name)
Emitted when the currently selected attribute changes.
@ Char
Character attributes.
Represents an individual category (class) from a QgsPointCloudClassifiedRenderer.
int value() const
Returns the value corresponding to this category.
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
QColor color() const
Returns the color which will be used to render this category.
double pointSize() const
Returns the point size for this category.
QString label() const
Returns the label for this category, which is used to represent the category within legends and the l...
Renders point clouds by a classification attribute.
Represents a map layer supporting display of point clouds.
QgsPointCloudRenderer * renderer()
Returns the 2D renderer for the point cloud.
static QgsPointCloudCategoryList classificationAttributeCategories(const QgsPointCloudLayer *layer)
Returns a list of categories using the available Classification classes of a specified layer,...
Abstract base class for 2d point cloud renderers.
Class used to store statistics of a point cloud dataset.
QMap< int, int > availableClasses(const QString &attribute) const
Returns a map containing the count of each class of the attribute attribute If no matching statistic ...
QList< int > classesOf(const QString &attribute) const
Returns a list of existing classes which are present for the specified attribute.
int sampledPointsCount() const
Returns the number of points used to calculate the statistics.
A QProxyStyle subclass which correctly sets the base style to match the QGIS application style,...
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
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
QList< QgsPointCloudCategory > QgsPointCloudCategoryList