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 || index.column() == 2 || index.column() == 3 )
89 flags |= Qt::ItemIsEditable;
94Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions()
const
96 return Qt::MoveAction;
99QVariant QgsPointCloudClassifiedRendererModel::data(
const QModelIndex &index,
int role )
const
101 if ( !index.isValid() || mCategories.empty() )
108 case Qt::CheckStateRole:
110 if ( index.column() == 0 )
112 return category.
renderState() ? Qt::Checked : Qt::Unchecked;
117 case Qt::DisplayRole:
118 case Qt::ToolTipRole:
120 switch ( index.column() )
123 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
125 return QString::number( category.
value() );
127 return category.
label();
129 const float value = mPercentages.value( category.
value(), -1 );
133 else if ( value != 0 && std::round( value * 10 ) < 1 )
134 str = QStringLiteral(
"< " ) + QLocale().toString( 0.1,
'f', 1 );
136 str = QLocale().toString( mPercentages.value( category.
value() ),
'f', 1 );
142 case Qt::DecorationRole:
144 if ( index.column() == 0 )
147 QPixmap pix( iconSize, iconSize );
148 pix.fill( category.
color() );
154 case Qt::TextAlignmentRole:
156 if ( index.column() == 0 )
157 return static_cast<Qt::Alignment::Int
>( Qt::AlignHCenter );
158 if ( index.column() == 4 )
159 return static_cast<Qt::Alignment::Int
>( Qt::AlignRight );
160 return static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
165 switch ( index.column() )
168 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
170 return QString::number( category.
value() );
172 return category.
label();
181bool QgsPointCloudClassifiedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
183 if ( !index.isValid() )
186 if ( index.column() == 0 && role == Qt::CheckStateRole )
188 mCategories[index.row()].setRenderState( value == Qt::Checked );
189 emit dataChanged( index, index );
190 emit categoriesChanged();
194 if ( role != Qt::EditRole )
197 switch ( index.column() )
201 const double size = value.toDouble();
202 mCategories[index.row()].setPointSize( size );
207 const int val = value.toInt();
208 mCategories[index.row()].setValue( val );
213 mCategories[index.row()].setLabel( value.toString() );
220 emit dataChanged( index, index );
221 emit categoriesChanged();
225QVariant QgsPointCloudClassifiedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
227 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
230 lst << tr(
"Color" ) << tr(
"Size" ) << tr(
"Value" ) << tr(
"Legend" ) << tr(
"Percentage" );
231 return lst.value( section );
236int QgsPointCloudClassifiedRendererModel::rowCount(
const QModelIndex &parent )
const
238 if ( parent.isValid() )
242 return mCategories.size();
245int QgsPointCloudClassifiedRendererModel::columnCount(
const QModelIndex &index )
const
251QModelIndex QgsPointCloudClassifiedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
253 if ( hasIndex( row, column, parent ) )
255 return createIndex( row, column );
257 return QModelIndex();
260QModelIndex QgsPointCloudClassifiedRendererModel::parent(
const QModelIndex &index )
const
263 return QModelIndex();
266QStringList QgsPointCloudClassifiedRendererModel::mimeTypes()
const
269 types << mMimeFormat;
273QMimeData *QgsPointCloudClassifiedRendererModel::mimeData(
const QModelIndexList &indexes )
const
275 QMimeData *mimeData =
new QMimeData();
276 QByteArray encodedData;
278 QDataStream stream( &encodedData, QIODevice::WriteOnly );
281 const auto constIndexes = indexes;
282 for (
const QModelIndex &index : constIndexes )
284 if ( !index.isValid() || index.column() != 0 )
287 stream << index.row();
289 mimeData->setData( mMimeFormat, encodedData );
293bool QgsPointCloudClassifiedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex &parent )
297 if ( action != Qt::MoveAction )
300 if ( !data->hasFormat( mMimeFormat ) )
303 QByteArray encodedData = data->data( mMimeFormat );
304 QDataStream stream( &encodedData, QIODevice::ReadOnly );
307 while ( !stream.atEnd() )
314 int to = parent.row();
318 to = mCategories.size();
319 for (
int i = rows.size() - 1; i >= 0; i-- )
325 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
327 mCategories.move( rows[i], t );
331 for (
int j = 0; j < i; j++ )
333 if ( to < rows[j] && rows[i] > rows[j] )
340 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
341 emit categoriesChanged();
345void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
347 std::sort( rows.begin(), rows.end() );
348 for (
int i = rows.size() - 1; i >= 0; i-- )
350 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
351 mCategories.removeAt( rows[i] );
354 emit categoriesChanged();
357void QgsPointCloudClassifiedRendererModel::removeAllRows()
359 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
362 emit categoriesChanged();
365void QgsPointCloudClassifiedRendererModel::setCategoryColor(
int row,
const QColor &color )
367 mCategories[row].setColor( color );
368 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
369 emit categoriesChanged();
372void QgsPointCloudClassifiedRendererModel::setCategoryPointSize(
int row,
double size )
374 mCategories[row].setPointSize( size );
375 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
376 emit categoriesChanged();
380QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
384void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element,
const QStyleOption *option, QPainter *painter,
const QWidget *widget )
const
386 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
388 QStyleOption opt( *option );
389 opt.rect.setLeft( 0 );
391 opt.rect.setHeight( 0 );
393 opt.rect.setRight( widget->width() );
394 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
397 QProxyStyle::drawPrimitive( element, option, painter, widget );
406 mAttributeComboBox->setAllowEmptyAttributeName(
true );
409 mModel =
new QgsPointCloudClassifiedRendererModel(
this );
413 mAttributeComboBox->setLayer( layer );
415 setFromRenderer( layer->
renderer() );
418 viewCategories->setModel( mModel );
419 viewCategories->resizeColumnToContents( 0 );
420 viewCategories->resizeColumnToContents( 1 );
421 viewCategories->resizeColumnToContents( 2 );
422 viewCategories->resizeColumnToContents( 3 );
424 viewCategories->setStyle(
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
427 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged,
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
429 connect( viewCategories, &QAbstractItemView::doubleClicked,
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
430 connect( btnAddCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
431 connect( btnDeleteCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
432 connect( btnDeleteAllCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
433 connect( btnAddCategory, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
435 contextMenu =
new QMenu( tr(
"Options" ),
this );
436 contextMenu->addAction( tr(
"Change &Color…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
437 contextMenu->addAction( tr(
"Change &Opacity…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
438 contextMenu->addAction( tr(
"Change &Size…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
440 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
441 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
442 connect( viewCategories, &QTreeView::customContextMenuRequested,
this, [=]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
447 return new QgsPointCloudClassifiedRendererWidget( layer, style );
457 std::unique_ptr<QgsPointCloudClassifiedRenderer> renderer = std::make_unique<QgsPointCloudClassifiedRenderer>();
458 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
459 renderer->setCategories( mModel->categories() );
461 return renderer.release();
466 return mModel->categories();
469QString QgsPointCloudClassifiedRendererWidget::attribute()
471 return mAttributeComboBox->currentAttribute();
474void QgsPointCloudClassifiedRendererWidget::attributeChanged()
476 if ( mBlockChangedSignal )
479 mBlockChangedSignal =
true;
480 mModel->removeAllRows();
481 mBlockChangedSignal =
false;
485void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
487 if ( mBlockChangedSignal )
490 updateCategoriesPercentages();
491 emit widgetChanged();
494void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked(
const QModelIndex &idx )
496 if ( idx.isValid() && idx.column() == 0 )
497 changeCategoryColor();
500void QgsPointCloudClassifiedRendererWidget::addCategories()
502 if ( !mLayer || !mLayer->dataProvider() )
506 const QString currentAttribute = mAttributeComboBox->currentAttribute();
511 const bool isClassificationAttribute = ( 0 == currentAttribute.compare( QStringLiteral(
"Classification" ), Qt::CaseInsensitive ) );
512 const bool isBooleanAttribute = ( 0 == currentAttribute.compare( QStringLiteral(
"Synthetic" ), Qt::CaseInsensitive ) || 0 == currentAttribute.compare( QStringLiteral(
"KeyPoint" ), Qt::CaseInsensitive ) || 0 == currentAttribute.compare( QStringLiteral(
"Withheld" ), Qt::CaseInsensitive ) || 0 == currentAttribute.compare( QStringLiteral(
"Overlap" ), Qt::CaseInsensitive ) );
514 QList<int> providerCategories = stats.
classesOf( currentAttribute );
518 if ( isBooleanAttribute && ( providerCategories.isEmpty() || stats.
sampledPointsCount() < mLayer->pointCount() ) )
519 providerCategories = { 0, 1 };
523 mBlockChangedSignal =
true;
524 for (
const int &providerCategory : std::as_const( providerCategories ) )
530 if (
c.value() == providerCategory )
541 if ( isClassificationAttribute )
545 if (
c.value() == providerCategory )
553 mModel->addCategory( category );
555 mBlockChangedSignal =
false;
559void QgsPointCloudClassifiedRendererWidget::addCategory()
565 mModel->addCategory( cat );
568void QgsPointCloudClassifiedRendererWidget::deleteCategories()
570 const QList<int> categoryIndexes = selectedCategories();
571 mModel->deleteRows( categoryIndexes );
574void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
576 mModel->removeAllRows();
581 mBlockChangedSignal =
true;
584 mModel->setRendererCategories( classifiedRenderer->categories() );
585 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
591 mBlockChangedSignal =
false;
595void QgsPointCloudClassifiedRendererWidget::setFromCategories(
QgsPointCloudCategoryList categories,
const QString &attribute )
597 mBlockChangedSignal =
true;
598 mModel->setRendererCategories( categories );
599 if ( !attribute.isEmpty() )
601 mAttributeComboBox->setAttribute( attribute );
607 mBlockChangedSignal =
false;
611void QgsPointCloudClassifiedRendererWidget::initialize()
613 if ( mAttributeComboBox->findText( QStringLiteral(
"Classification" ) ) > -1 )
615 mAttributeComboBox->setAttribute( QStringLiteral(
"Classification" ) );
619 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
621 mModel->removeAllRows();
625void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
627 const QList<int> categoryList = selectedCategories();
628 if ( categoryList.isEmpty() )
639 colorWidget->
setPanelTitle( categoryList.count() == 1 ? category.
label() : tr(
"Select Color" ) );
644 for (
int row : categoryList )
646 mModel->setCategoryColor( row, newColor );
654 if ( newColor.isValid() )
656 for (
int row : categoryList )
658 mModel->setCategoryColor( row, newColor );
664void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
666 const QList<int> categoryList = selectedCategories();
667 if ( categoryList.isEmpty() )
672 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
675 const double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
678 for (
int row : categoryList )
681 QColor color = category.
color();
682 color.setAlphaF( opacity / 100.0 );
683 mModel->setCategoryColor( row, color );
688void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
690 const QList<int> categoryList = selectedCategories();
691 if ( categoryList.isEmpty() )
696 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
699 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 );
702 for (
int row : categoryList )
704 mModel->setCategoryPointSize( row, size );
709QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
712 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
713 for (
const QModelIndex &r : selectedRows )
717 rows.append( r.row() );
723int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
725 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
726 if ( !idx.isValid() )
731void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
733 QMap<int, float> percentages;
744 if ( classes.contains( category.
value() ) || statsExact )
745 percentages.insert( category.
value(), (
double ) classes.value( category.
value() ) / pointCount * 100 );
747 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