32#include <QInputDialog>
36QgsPointCloudClassifiedRendererModel::QgsPointCloudClassifiedRendererModel( QObject *parent )
37 : QAbstractItemModel( parent )
38 , mMimeFormat( QStringLiteral(
"application/x-qgspointcloudclassifiedrenderermodel" ) )
44 if ( !mCategories.empty() )
46 beginRemoveRows( QModelIndex(), 0, std::max< int >( mCategories.size() - 1, 0 ) );
50 if ( categories.size() > 0 )
52 beginInsertRows( QModelIndex(), 0, categories.size() - 1 );
53 mCategories = categories;
60 const int idx = mCategories.size();
61 beginInsertRows( QModelIndex(), idx, idx );
62 mCategories.append( cat );
65 emit categoriesChanged();
70 const int row = index.row();
71 if ( row >= mCategories.size() )
75 return mCategories.at( row );
78Qt::ItemFlags QgsPointCloudClassifiedRendererModel::flags(
const QModelIndex &index )
const
80 if ( !index.isValid() || mCategories.empty() )
82 return Qt::ItemIsDropEnabled;
85 Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable;
86 if ( index.column() == 1 ||
87 index.column() == 2 ||
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() )
315 int to = parent.row();
319 to = mCategories.size();
320 for (
int i = rows.size() - 1; i >= 0; i-- )
326 if ( !( rows[i] < 0 || rows[i] >= mCategories.size() || t < 0 || t >= mCategories.size() ) )
328 mCategories.move( rows[i], t );
332 for (
int j = 0; j < i; j++ )
334 if ( to < rows[j] && rows[i] > rows[j] )
341 emit dataChanged( createIndex( 0, 0 ), createIndex( mCategories.size(), 0 ) );
342 emit categoriesChanged();
346void QgsPointCloudClassifiedRendererModel::deleteRows( QList<int> rows )
348 std::sort( rows.begin(), rows.end() );
349 for (
int i = rows.size() - 1; i >= 0; i-- )
351 beginRemoveRows( QModelIndex(), rows[i], rows[i] );
352 mCategories.removeAt( rows[i] );
355 emit categoriesChanged();
358void QgsPointCloudClassifiedRendererModel::removeAllRows()
360 beginRemoveRows( QModelIndex(), 0, mCategories.size() - 1 );
363 emit categoriesChanged();
366void QgsPointCloudClassifiedRendererModel::setCategoryColor(
int row,
const QColor &color )
368 mCategories[row].setColor( color );
369 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
370 emit categoriesChanged();
373void QgsPointCloudClassifiedRendererModel::setCategoryPointSize(
int row,
double size )
375 mCategories[row].setPointSize( size );
376 emit dataChanged( createIndex( row, 0 ), createIndex( row, 0 ) );
377 emit categoriesChanged();
381QgsPointCloudClassifiedRendererViewStyle::QgsPointCloudClassifiedRendererViewStyle( QWidget *parent )
385void QgsPointCloudClassifiedRendererViewStyle::drawPrimitive( PrimitiveElement element,
const QStyleOption *option, QPainter *painter,
const QWidget *widget )
const
387 if ( element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull() )
389 QStyleOption opt( *option );
390 opt.rect.setLeft( 0 );
392 opt.rect.setHeight( 0 );
394 opt.rect.setRight( widget->width() );
395 QProxyStyle::drawPrimitive( element, &opt, painter, widget );
398 QProxyStyle::drawPrimitive( element, option, painter, widget );
407 mAttributeComboBox->setAllowEmptyAttributeName(
true );
410 mModel =
new QgsPointCloudClassifiedRendererModel(
this );
414 mAttributeComboBox->setLayer( layer );
416 setFromRenderer( layer->
renderer() );
419 viewCategories->setModel( mModel );
420 viewCategories->resizeColumnToContents( 0 );
421 viewCategories->resizeColumnToContents( 1 );
422 viewCategories->resizeColumnToContents( 2 );
423 viewCategories->resizeColumnToContents( 3 );
425 viewCategories->setStyle(
new QgsPointCloudClassifiedRendererViewStyle( viewCategories ) );
428 this, &QgsPointCloudClassifiedRendererWidget::attributeChanged );
429 connect( mModel, &QgsPointCloudClassifiedRendererModel::categoriesChanged,
this, &QgsPointCloudClassifiedRendererWidget::emitWidgetChanged );
431 connect( viewCategories, &QAbstractItemView::doubleClicked,
this, &QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked );
432 connect( btnAddCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategories );
433 connect( btnDeleteCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteCategories );
434 connect( btnDeleteAllCategories, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::deleteAllCategories );
435 connect( btnAddCategory, &QAbstractButton::clicked,
this, &QgsPointCloudClassifiedRendererWidget::addCategory );
437 contextMenu =
new QMenu( tr(
"Options" ),
this );
438 contextMenu->addAction( tr(
"Change &Color…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryColor );
439 contextMenu->addAction( tr(
"Change &Opacity…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity );
440 contextMenu->addAction( tr(
"Change &Size…" ),
this, &QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize );
442 viewCategories->setContextMenuPolicy( Qt::CustomContextMenu );
443 viewCategories->setSelectionMode( QAbstractItemView::ExtendedSelection );
444 connect( viewCategories, &QTreeView::customContextMenuRequested,
this, [ = ]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
449 return new QgsPointCloudClassifiedRendererWidget( layer, style );
459 std::unique_ptr< QgsPointCloudClassifiedRenderer > renderer = std::make_unique< QgsPointCloudClassifiedRenderer >();
460 renderer->setAttribute( mAttributeComboBox->currentAttribute() );
461 renderer->setCategories( mModel->categories() );
463 return renderer.release();
468 return mModel->categories();
471QString QgsPointCloudClassifiedRendererWidget::attribute()
473 return mAttributeComboBox->currentAttribute();
476void QgsPointCloudClassifiedRendererWidget::attributeChanged()
478 if ( mBlockChangedSignal )
481 mBlockChangedSignal =
true;
482 mModel->removeAllRows();
483 mBlockChangedSignal =
false;
487void QgsPointCloudClassifiedRendererWidget::emitWidgetChanged()
489 if ( mBlockChangedSignal )
492 updateCategoriesPercentages();
493 emit widgetChanged();
496void QgsPointCloudClassifiedRendererWidget::categoriesDoubleClicked(
const QModelIndex &idx )
498 if ( idx.isValid() && idx.column() == 0 )
499 changeCategoryColor();
502void QgsPointCloudClassifiedRendererWidget::addCategories()
504 if ( !mLayer || !mLayer->dataProvider() )
508 const QString currentAttribute = mAttributeComboBox->currentAttribute();
513 const bool isClassificationAttribute = ( 0 == currentAttribute.compare( QStringLiteral(
"Classification" ), Qt::CaseInsensitive ) );
514 const bool isBooleanAttribute = ( 0 == currentAttribute.compare( QStringLiteral(
"Synthetic" ), Qt::CaseInsensitive ) ||
515 0 == currentAttribute.compare( QStringLiteral(
"KeyPoint" ), Qt::CaseInsensitive ) ||
516 0 == currentAttribute.compare( QStringLiteral(
"Withheld" ), Qt::CaseInsensitive ) ||
517 0 == currentAttribute.compare( QStringLiteral(
"Overlap" ), Qt::CaseInsensitive ) );
519 QList<int> providerCategories = stats.
classesOf( currentAttribute );
523 if ( isBooleanAttribute &&
524 ( 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" ) );
651 for (
int row : categoryList )
653 mModel->setCategoryColor( row, newColor );
661 if ( newColor.isValid() )
663 for (
int row : categoryList )
665 mModel->setCategoryColor( row, newColor );
671void QgsPointCloudClassifiedRendererWidget::changeCategoryOpacity()
673 const QList<int> categoryList = selectedCategories();
674 if ( categoryList.isEmpty() )
679 const double oldOpacity = mModel->categories().value( categoryList.first() ).color().alphaF() * 100.0;
682 const double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change symbol opacity [%]" ), oldOpacity, 0.0, 100.0, 1, &ok );
685 for (
int row : categoryList )
688 QColor color = category.
color();
689 color.setAlphaF( opacity / 100.0 );
690 mModel->setCategoryColor( row, color );
695void QgsPointCloudClassifiedRendererWidget::changeCategoryPointSize()
697 const QList<int> categoryList = selectedCategories();
698 if ( categoryList.isEmpty() )
703 const double oldSize = mModel->categories().value( categoryList.first() ).pointSize();
706 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 );
709 for (
int row : categoryList )
711 mModel->setCategoryPointSize( row, size );
716QList<int> QgsPointCloudClassifiedRendererWidget::selectedCategories()
719 const QModelIndexList selectedRows = viewCategories->selectionModel()->selectedRows();
720 for (
const QModelIndex &r : selectedRows )
724 rows.append( r.row() );
730int QgsPointCloudClassifiedRendererWidget::currentCategoryRow()
732 const QModelIndex idx = viewCategories->selectionModel()->currentIndex();
733 if ( !idx.isValid() )
738void QgsPointCloudClassifiedRendererWidget::updateCategoriesPercentages()
740 QMap < int, float > percentages;
751 if ( classes.contains( category.
value() ) || statsExact )
752 percentages.insert( category.
value(), (
double ) classes.value( category.
value() ) / pointCount * 100 );
754 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