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
82 if ( !index.isValid() || mCategories.empty() )
84 return Qt::ItemIsDropEnabled;
87 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable;
88 if ( index.column() == 1 || index.column() == 2 || index.column() == 3 )
90 flags |= Qt::ItemIsEditable;
95Qt::DropActions QgsPointCloudClassifiedRendererModel::supportedDropActions()
const
97 return Qt::MoveAction;
100QVariant QgsPointCloudClassifiedRendererModel::data(
const QModelIndex &index,
int role )
const
102 if ( !index.isValid() || mCategories.empty() )
109 case Qt::CheckStateRole:
111 if ( index.column() == 0 )
113 return category.
renderState() ? Qt::Checked : Qt::Unchecked;
118 case Qt::DisplayRole:
119 case Qt::ToolTipRole:
121 switch ( index.column() )
124 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
126 return QString::number( category.
value() );
128 return category.
label();
130 const float value = mPercentages.value( category.
value(), -1 );
134 else if ( value != 0 && std::round( value * 10 ) < 1 )
135 str = QStringLiteral(
"< " ) + QLocale().toString( 0.1,
'f', 1 );
137 str = QLocale().toString( mPercentages.value( category.
value() ),
'f', 1 );
143 case Qt::DecorationRole:
145 if ( index.column() == 0 )
148 QPixmap pix( iconSize, iconSize );
149 pix.fill( category.
color() );
155 case Qt::TextAlignmentRole:
157 if ( index.column() == 0 )
158 return static_cast<Qt::Alignment::Int
>( Qt::AlignHCenter );
159 if ( index.column() == 4 )
160 return static_cast<Qt::Alignment::Int
>( Qt::AlignRight );
161 return static_cast<Qt::Alignment::Int
>( Qt::AlignLeft );
166 switch ( index.column() )
169 return category.
pointSize() > 0 ? QString::number( category.
pointSize() ) : QString();
171 return QString::number( category.
value() );
173 return category.
label();
182bool QgsPointCloudClassifiedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
184 if ( !index.isValid() )
187 if ( index.column() == 0 && role == Qt::CheckStateRole )
189 mCategories[index.row()].setRenderState( value == Qt::Checked );
190 emit dataChanged( index, index );
191 emit categoriesChanged();
195 if ( role != Qt::EditRole )
198 switch ( index.column() )
202 const double size = value.toDouble();
203 mCategories[index.row()].setPointSize( size );
208 const int val = value.toInt();
209 mCategories[index.row()].setValue( val );
214 mCategories[index.row()].setLabel( value.toString() );
221 emit dataChanged( index, index );
222 emit categoriesChanged();
226QVariant QgsPointCloudClassifiedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
228 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 5 )
231 lst << tr(
"Color" ) << tr(
"Size" ) << tr(
"Value" ) << tr(
"Legend" ) << tr(
"Percentage" );
232 return lst.value( section );
237int QgsPointCloudClassifiedRendererModel::rowCount(
const QModelIndex &parent )
const
239 if ( parent.isValid() )
243 return mCategories.size();
246int QgsPointCloudClassifiedRendererModel::columnCount(
const QModelIndex &index )
const
252QModelIndex QgsPointCloudClassifiedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
254 if ( hasIndex( row, column, parent ) )
256 return createIndex( row, column );
258 return QModelIndex();
261QModelIndex QgsPointCloudClassifiedRendererModel::parent(
const QModelIndex &index )
const
264 return QModelIndex();
267QStringList QgsPointCloudClassifiedRendererModel::mimeTypes()
const
270 types << mMimeFormat;
274QMimeData *QgsPointCloudClassifiedRendererModel::mimeData(
const QModelIndexList &indexes )
const
276 QMimeData *mimeData =
new QMimeData();
277 QByteArray encodedData;
279 QDataStream stream( &encodedData, QIODevice::WriteOnly );
282 const auto constIndexes = indexes;
283 for (
const QModelIndex &index : constIndexes )
285 if ( !index.isValid() || index.column() != 0 )
288 stream << index.row();
290 mimeData->setData( mMimeFormat, encodedData );
294bool QgsPointCloudClassifiedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex &parent )
298 if ( action != Qt::MoveAction )
301 if ( !data->hasFormat( mMimeFormat ) )
304 QByteArray encodedData = data->data( mMimeFormat );
305 QDataStream stream( &encodedData, QIODevice::ReadOnly );
308 while ( !stream.atEnd() )
316 std::sort( rows.begin(), rows.end() );
322 to = mCategories.size();
323 for (
int i = rows.size() - 1; i >= 0; i-- )
329 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
331 mCategories.move( rows[i], t );
335 for (
int j = 0; j < i; j++ )
337 if ( to < rows[j] && rows[i] > rows[j] )
344 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
345 emit categoriesChanged();
350void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
352 std::sort( rows.begin(), rows.end() );
353 for (
int i = rows.size() - 1; i >= 0; i-- )
355 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
356 mCategories.removeAt( rows[i] );
359 emit categoriesChanged();
362void QgsPointCloudClassifiedRendererModel::removeAllRows()
364 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
367 emit categoriesChanged();
370void QgsPointCloudClassifiedRendererModel::setCategoryColor(
int row,
const QColor &color )
372 mCategories[row].setColor( color );
373 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
374 emit categoriesChanged();
377void QgsPointCloudClassifiedRendererModel::setCategoryPointSize(
int row,
double size )
379 mCategories[row].setPointSize( size );
380 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
381 emit categoriesChanged();
385QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
389void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element,
const QStyleOption *option, QPainter *painter,
const QWidget *widget )
const
391 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
393 QStyleOption opt( *option );
394 opt.rect.setLeft( 0 );
396 opt.rect.setHeight( 0 );
398 opt.rect.setRight( widget->width() );
399 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
402 QProxyStyle::drawPrimitive( element, option, painter, widget );
411 mAttributeComboBox->setAllowEmptyAttributeName(
true );
414 mModel =
new QgsPointCloudClassifiedRendererModel(
this );
418 mAttributeComboBox->setLayer( layer );
420 setFromRenderer( layer->
renderer() );
423 viewCategories->setModel( mModel );
424 viewCategories->resizeColumnToContents( 0 );
425 viewCategories->resizeColumnToContents( 1 );
426 viewCategories->resizeColumnToContents( 2 );
427 viewCategories->resizeColumnToContents( 3 );
429 viewCategories->setStyle(
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
432 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged,
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
433 connect( mModel, &QgsPointCloudClassifiedRendererModel::rowsMoved,
this, &QgsPointCloudClassifiedRendererWidget::rowsMoved );
435 connect( viewCategories, &QAbstractItemView::doubleClicked,
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
436 connect( btnAddCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
437 connect( btnDeleteCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
438 connect( btnDeleteAllCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
439 connect( btnAddCategory, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
441 contextMenu =
new QMenu( tr(
"Options" ),
this );
442 contextMenu->addAction( tr(
"Change &Color…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
443 contextMenu->addAction( tr(
"Change &Opacity…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
444 contextMenu->addAction( tr(
"Change &Size…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
446 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
447 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
448 connect( viewCategories, &QTreeView::customContextMenuRequested,
this, [=]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
453 return new QgsPointCloudClassifiedRendererWidget( layer, style );
463 auto renderer = std::make_unique<QgsPointCloudClassifiedRenderer>();
464 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
465 renderer->setCategories( mModel->categories() );
467 return renderer.release();
472 return mModel->categories();
475QString QgsPointCloudClassifiedRendererWidget::attribute()
477 return mAttributeComboBox->currentAttribute();
480void QgsPointCloudClassifiedRendererWidget::attributeChanged()
482 if ( mBlockChangedSignal )
485 mBlockChangedSignal =
true;
486 mModel->removeAllRows();
487 mBlockChangedSignal =
false;
491void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
493 if ( mBlockChangedSignal )
496 updateCategoriesPercentages();
497 emit widgetChanged();
500void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked(
const QModelIndex &idx )
502 if ( idx.isValid() && idx.column() == 0 )
503 changeCategoryColor();
506void QgsPointCloudClassifiedRendererWidget::addCategories()
508 if ( !mLayer || !mLayer->dataProvider() )
512 const QString currentAttribute = mAttributeComboBox->currentAttribute();
517 const bool isClassificationAttribute = ( 0 == currentAttribute.compare( QStringLiteral(
"Classification" ), Qt::CaseInsensitive ) );
518 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 ) );
520 QList<int> providerCategories = stats.
classesOf( currentAttribute );
524 if ( isBooleanAttribute && ( providerCategories.isEmpty() || stats.
sampledPointsCount() < mLayer->pointCount() ) )
525 providerCategories = { 0, 1 };
529 mBlockChangedSignal =
true;
530 for (
const int &providerCategory : std::as_const( providerCategories ) )
536 if (
c.value() == providerCategory )
547 if ( isClassificationAttribute )
551 if (
c.value() == providerCategory )
559 mModel->addCategory( category );
561 mBlockChangedSignal =
false;
565void QgsPointCloudClassifiedRendererWidget::addCategory()
571 mModel->addCategory( cat );
574void QgsPointCloudClassifiedRendererWidget::deleteCategories()
576 const QList<int> categoryIndexes = selectedCategories();
577 mModel->deleteRows( categoryIndexes );
580void QgsPointCloudClassifiedRendererWidget::deleteAllCategories()
582 mModel->removeAllRows();
587 mBlockChangedSignal =
true;
590 mModel->setRendererCategories( classifiedRenderer->categories() );
591 mAttributeComboBox->setAttribute( classifiedRenderer->attribute() );
597 mBlockChangedSignal =
false;
601void QgsPointCloudClassifiedRendererWidget::setFromCategories(
QgsPointCloudCategoryList categories,
const QString &attribute )
603 mBlockChangedSignal =
true;
604 mModel->setRendererCategories( categories );
605 if ( !attribute.isEmpty() )
607 mAttributeComboBox->setAttribute( attribute );
613 mBlockChangedSignal =
false;
617void QgsPointCloudClassifiedRendererWidget::initialize()
619 if ( mAttributeComboBox->findText( QStringLiteral(
"Classification" ) ) > -1 )
621 mAttributeComboBox->setAttribute( QStringLiteral(
"Classification" ) );
625 mAttributeComboBox->setCurrentIndex( mAttributeComboBox->count() > 1 ? 1 : 0 );
627 mModel->removeAllRows();
631void QgsPointCloudClassifiedRendererWidget::changeCategoryColor()
633 const QList<int> categoryList = selectedCategories();
634 if ( categoryList.isEmpty() )
645 colorWidget->
setPanelTitle( categoryList.count() == 1 ? category.
label() : tr(
"Select Color" ) );
650 for (
int row : categoryList )
652 mModel->setCategoryColor( row, newColor );
660 if ( newColor.isValid() )
662 for (
int row : categoryList )
664 mModel->setCategoryColor( row, newColor );
670void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
672 const QList<int> categoryList = selectedCategories();
673 if ( categoryList.isEmpty() )
678 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
681 const double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
684 for (
int row : categoryList )
687 QColor color = category.
color();
688 color.setAlphaF( opacity / 100.0 );
689 mModel->setCategoryColor( row, color );
694void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
696 const QList<int> categoryList = selectedCategories();
697 if ( categoryList.isEmpty() )
702 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
705 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 );
708 for (
int row : categoryList )
710 mModel->setCategoryPointSize( row, size );
715void QgsPointCloudClassifiedRendererWidget::rowsMoved()
717 viewCategories->selectionModel()->clear();
720QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
723 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
724 for (
const QModelIndex &r : selectedRows )
728 rows.append( r.row() );
734int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
736 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
737 if ( !idx.isValid() )
742void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
744 QMap<int, float> percentages;
755 if ( classes.contains( category.
value() ) || statsExact )
756 percentages.insert( category.
value(), (
double ) classes.value( category.
value() ) / pointCount * 100 );
758 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