32#include <QInputDialog>
36#include "moc_qgspointcloudclassifiedrendererwidget.cpp"
38using namespace Qt::StringLiterals;
42QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
43 : QAbstractItemModel( parent )
44 , mMimeFormat( u
"application/x-qgspointcloudclassifiedrenderermodel"_s )
50 if ( !mCategories.empty() )
52 beginRemoveRows( QModelIndex(), 0, std::max<int>( mCategories.size() - 1, 0 ) );
56 if ( categories.size() > 0 )
58 beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
59 mCategories = categories;
66 const int idx = mCategories.size();
67 beginInsertRows( QModelIndex(), idx, idx );
68 mCategories.append( cat );
71 emit categoriesChanged();
76 const int row = index.row();
77 if ( row >= mCategories.size() )
81 return mCategories.at( row );
84Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags(
const QModelIndex &index )
const
87 if ( !index.isValid() || mCategories.empty() )
89 return Qt::ItemIsDropEnabled;
92 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable;
93 if ( index.column() == 1 || index.column() == 2 || index.column() == 3 )
95 flags |= Qt::ItemIsEditable;
100Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions()
const
102 return Qt::MoveAction;
105QVariant QgsPointCloudClassifiedRendererModel::data(
const QModelIndex &index,
int role )
const
107 if ( !index.isValid() || mCategories.empty() )
114 case Qt::CheckStateRole:
116 if ( index.column() == 0 )
118 return category.
renderState() ? Qt::Checked : Qt::Unchecked;
123 case Qt::DisplayRole:
124 case Qt::ToolTipRole:
126 switch ( index.column() )
129 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
131 return QString::number( category.
value() );
133 return category.
label();
135 const float value = mPercentages.value( category.
value(), -1 );
139 else if ( value != 0 && std::round( value * 10 ) < 1 )
140 str = u
"< "_s + QLocale().toString( 0.1,
'f', 1 );
142 str = QLocale().toString( mPercentages.value( category.
value() ),
'f', 1 );
148 case Qt::DecorationRole:
150 if ( index.column() == 0 )
153 QPixmap pix( iconSize, iconSize );
154 pix.fill( category.
color() );
160 case Qt::TextAlignmentRole:
162 if ( index.column() == 0 )
163 return static_cast<Qt::Alignment::Int
>( Qt::AlignHCenter );
164 if ( index.column() == 4 )
165 return static_cast<Qt::Alignment::Int
>( Qt::AlignRight );
166 return static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
171 switch ( index.column() )
174 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
176 return QString::number( category.
value() );
178 return category.
label();
187bool QgsPointCloudClassifiedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
189 if ( !index.isValid() )
192 if ( index.column() == 0 && role == Qt::CheckStateRole )
194 mCategories[index.row()].setRenderState( value == Qt::Checked );
195 emit dataChanged( index, index );
196 emit categoriesChanged();
200 if ( role != Qt::EditRole )
203 switch ( index.column() )
207 const double size = value.toDouble();
208 mCategories[index.row()].setPointSize( size );
213 const int val = value.toInt();
214 mCategories[index.row()].setValue( val );
219 mCategories[index.row()].setLabel( value.toString() );
226 emit dataChanged( index, index );
227 emit categoriesChanged();
231QVariant QgsPointCloudClassifiedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
233 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
236 lst << tr(
"Color" ) << tr(
"Size" ) << tr(
"Value" ) << tr(
"Legend" ) << tr(
"Percentage" );
237 return lst.value( section );
242int QgsPointCloudClassifiedRendererModel::rowCount(
const QModelIndex &parent )
const
244 if ( parent.isValid() )
248 return mCategories.size();
251int QgsPointCloudClassifiedRendererModel::columnCount(
const QModelIndex &index )
const
257QModelIndex QgsPointCloudClassifiedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
259 if ( hasIndex( row, column, parent ) )
261 return createIndex( row, column );
263 return QModelIndex();
266QModelIndex QgsPointCloudClassifiedRendererModel::parent(
const QModelIndex &index )
const
269 return QModelIndex();
272QStringList QgsPointCloudClassifiedRendererModel::mimeTypes()
const
275 types << mMimeFormat;
279QMimeData *QgsPointCloudClassifiedRendererModel::mimeData(
const QModelIndexList &indexes )
const
281 QMimeData *mimeData =
new QMimeData();
282 QByteArray encodedData;
284 QDataStream stream( &encodedData, QIODevice::WriteOnly );
287 const auto constIndexes = indexes;
288 for (
const QModelIndex &index : constIndexes )
290 if ( !index.isValid() || index.column() != 0 )
293 stream << index.row();
295 mimeData->setData( mMimeFormat, encodedData );
299bool QgsPointCloudClassifiedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex &parent )
303 if ( action != Qt::MoveAction )
306 if ( !data->hasFormat( mMimeFormat ) )
309 QByteArray encodedData = data->data( mMimeFormat );
310 QDataStream stream( &encodedData, QIODevice::ReadOnly );
313 while ( !stream.atEnd() )
321 std::sort( rows.begin(), rows.end() );
327 to = mCategories.size();
328 for (
int i = rows.size() - 1; i >= 0; i-- )
334 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
336 mCategories.move( rows[i], t );
340 for (
int j = 0; j < i; j++ )
342 if ( to < rows[j] && rows[i] > rows[j] )
349 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
350 emit categoriesChanged();
355void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
357 std::sort( rows.begin(), rows.end() );
358 for (
int i = rows.size() - 1; i >= 0; i-- )
360 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
361 mCategories.removeAt( rows[i] );
364 emit categoriesChanged();
367void QgsPointCloudClassifiedRendererModel::removeAllRows()
369 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
372 emit categoriesChanged();
375void QgsPointCloudClassifiedRendererModel::setCategoryColor(
int row,
const QColor &color )
377 mCategories[row].setColor( color );
378 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
379 emit categoriesChanged();
382void QgsPointCloudClassifiedRendererModel::setCategoryPointSize(
int row,
double size )
384 mCategories[row].setPointSize( size );
385 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
386 emit categoriesChanged();
390QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
394void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element,
const QStyleOption *option, QPainter *painter,
const QWidget *widget )
const
396 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
398 QStyleOption opt( *option );
399 opt.rect.setLeft( 0 );
401 opt.rect.setHeight( 0 );
403 opt.rect.setRight( widget->width() );
404 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
407 QProxyStyle::drawPrimitive( element, option, painter, widget );
416 mAttributeComboBox->setAllowEmptyAttributeName(
true );
419 mModel =
new QgsPointCloudClassifiedRendererModel(
this );
423 mAttributeComboBox->setLayer( layer );
425 setFromRenderer( layer->
renderer() );
428 viewCategories->setModel( mModel );
429 viewCategories->resizeColumnToContents( 0 );
430 viewCategories->resizeColumnToContents( 1 );
431 viewCategories->resizeColumnToContents( 2 );
432 viewCategories->resizeColumnToContents( 3 );
434 viewCategories->setStyle(
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
437 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged,
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
438 connect( mModel, &QgsPointCloudClassifiedRendererModel::rowsMoved,
this, &QgsPointCloudClassifiedRendererWidget::rowsMoved );
440 connect( viewCategories, &QAbstractItemView::doubleClicked,
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
441 connect( btnAddCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
442 connect( btnDeleteCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
443 connect( btnDeleteAllCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
444 connect( btnAddCategory, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
446 contextMenu =
new QMenu( tr(
"Options" ),
this );
447 contextMenu->addAction( tr(
"Change &Color…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
448 contextMenu->addAction( tr(
"Change &Opacity…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
449 contextMenu->addAction( tr(
"Change &Size…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
451 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
452 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
453 connect( viewCategories, &QTreeView::customContextMenuRequested,
this, [
this]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
458 return new QgsPointCloudClassifiedRendererWidget( layer, style );
468 auto renderer = std::make_unique<QgsPointCloudClassifiedRenderer>();
469 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
470 renderer->setCategories( mModel->categories() );
472 return renderer.release();
477 return mModel->categories();
480QString QgsPointCloudClassifiedRendererWidget::attribute()
const
482 return mAttributeComboBox->currentAttribute();
485void QgsPointCloudClassifiedRendererWidget::attributeChanged()
487 if ( mBlockChangedSignal )
490 mBlockChangedSignal =
true;
491 mModel->removeAllRows();
492 mBlockChangedSignal =
false;
496void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
498 if ( mBlockChangedSignal )
501 updateCategoriesPercentages();
502 emit widgetChanged();
505void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked(
const QModelIndex &idx )
507 if ( idx.isValid() && idx.column() == 0 )
508 changeCategoryColor();
511void QgsPointCloudClassifiedRendererWidget::addCategories()
513 if ( !mLayer || !mLayer->dataProvider() )
517 const QString currentAttribute = mAttributeComboBox->currentAttribute();
522 const bool isClassificationAttribute = ( 0 == currentAttribute.compare( u
"Classification"_s, Qt::CaseInsensitive ) );
523 const bool isBooleanAttribute = ( 0 == currentAttribute.compare( u
"Synthetic"_s, Qt::CaseInsensitive ) || 0 == currentAttribute.compare( u
"KeyPoint"_s, Qt::CaseInsensitive ) || 0 == currentAttribute.compare( u
"Withheld"_s, Qt::CaseInsensitive ) || 0 == currentAttribute.compare( u
"Overlap"_s, Qt::CaseInsensitive ) );
525 QList<int> providerCategories = stats.
classesOf( currentAttribute );
529 if ( isBooleanAttribute && ( providerCategories.isEmpty() || stats.
sampledPointsCount() < mLayer->pointCount() ) )
530 providerCategories = { 0, 1 };
534 mBlockChangedSignal =
true;
537 if ( isClassificationAttribute && providerCategories.isEmpty() )
539 for ( const QgsPointCloudCategory &c : defaultLayerCategories )
541 providerCategories.append( c.value() );
545 for (
const int &providerCategory : std::as_const( providerCategories ) )
551 if (
c.value() == providerCategory )
562 if ( isClassificationAttribute )
566 if (
c.value() == providerCategory )
574 mModel->addCategory( category );
576 mBlockChangedSignal =
false;
580void QgsPointCloudClassifiedRendererWidget::addCategory()
586 mModel->addCategory( cat );
589void QgsPointCloudClassifiedRendererWidget::deleteCategories()
591 const QList<int> categoryIndexes = selectedCategories();
592 mModel->deleteRows( categoryIndexes );
595void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
597 mModel->removeAllRows();
602 mBlockChangedSignal =
true;
605 mModel->setRendererCategories( classifiedRenderer->categories() );
606 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
612 mBlockChangedSignal =
false;
616void QgsPointCloudClassifiedRendererWidget::setFromCategories(
QgsPointCloudCategoryList categories,
const QString &attribute )
618 mBlockChangedSignal =
true;
619 mModel->setRendererCategories( categories );
620 if ( !attribute.isEmpty() )
622 mAttributeComboBox->setAttribute( attribute );
628 mBlockChangedSignal =
false;
632void QgsPointCloudClassifiedRendererWidget::initialize()
634 if ( mAttributeComboBox->findText( u
"Classification"_s ) > -1 )
636 mAttributeComboBox->setAttribute( u
"Classification"_s );
640 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
642 mModel->removeAllRows();
646void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
648 const QList<int> categoryList = selectedCategories();
649 if ( categoryList.isEmpty() )
660 colorWidget->
setPanelTitle( categoryList.count() == 1 ? category.
label() : tr(
"Select Color" ) );
665 for (
int row : categoryList )
667 mModel->setCategoryColor( row, newColor );
675 if ( newColor.isValid() )
677 for (
int row : categoryList )
679 mModel->setCategoryColor( row, newColor );
685void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
687 const QList<int> categoryList = selectedCategories();
688 if ( categoryList.isEmpty() )
693 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
696 const double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
699 for (
int row : categoryList )
702 QColor color = category.
color();
703 color.setAlphaF( opacity / 100.0 );
704 mModel->setCategoryColor( row, color );
709void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
711 const QList<int> categoryList = selectedCategories();
712 if ( categoryList.isEmpty() )
717 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
720 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 );
723 for (
int row : categoryList )
725 mModel->setCategoryPointSize( row, size );
730void QgsPointCloudClassifiedRendererWidget::rowsMoved()
732 viewCategories->selectionModel()->clear();
735QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
738 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
739 for (
const QModelIndex &r : selectedRows )
743 rows.append( r.row() );
749int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
751 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
752 if ( !idx.isValid() )
757void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
759 QMap<int, float> percentages;
770 if ( classes.contains( category.
value() ) || statsExact )
771 percentages.insert( category.
value(), (
double ) classes.value( category.
value() ) / pointCount * 100 );
773 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.
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.
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,...
A database of saved style entities, including symbols, color ramps, text formats and others.
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