From dbaf7bd4546bbda676aaaa7718f5baec35efae8e Mon Sep 17 00:00:00 2001 From: mhugent Date: Wed, 3 Jun 2009 11:33:58 +0000 Subject: [PATCH] New functionality for merging features git-svn-id: http://svn.osgeo.org/qgis/trunk@10879 c8812cc2-4d05-0410-92ff-de0c093fc19c --- .../default/mActionFromSelectedFeature.png | Bin 0 -> 4267 bytes .../themes/default/mActionMergeFeatures.png | Bin 0 -> 1724 bytes .../default/mActionRemoveSelectedFeature.png | Bin 0 -> 4267 bytes src/app/CMakeLists.txt | 2 + src/app/qgisapp.cpp | 155 +++++ src/app/qgisapp.h | 8 + src/app/qgsmergeattributesdialog.cpp | 532 ++++++++++++++++++ src/app/qgsmergeattributesdialog.h | 78 +++ src/ui/qgsmergeattributesdialogbase.ui | 134 +++++ 9 files changed, 909 insertions(+) create mode 100644 images/themes/default/mActionFromSelectedFeature.png create mode 100644 images/themes/default/mActionMergeFeatures.png create mode 100644 images/themes/default/mActionRemoveSelectedFeature.png create mode 100644 src/app/qgsmergeattributesdialog.cpp create mode 100644 src/app/qgsmergeattributesdialog.h create mode 100644 src/ui/qgsmergeattributesdialogbase.ui diff --git a/images/themes/default/mActionFromSelectedFeature.png b/images/themes/default/mActionFromSelectedFeature.png new file mode 100644 index 0000000000000000000000000000000000000000..bc7b66fda17aba127b5a48a560d1ccb1b90858d0 GIT binary patch literal 4267 zcmeHK&1(};5TD)drcK&lpcYhu^rDvvqEsn}pd~d8Vv*uOZ{o$PQctBOG@kS#*qeIr zAbJog{sG=S=|u#45xpsbhqP{!{dmsIe&lVkyWLF$FTV0R^JeDFZ)SG(ZQlLMOVwiG zPyqmnwZ+OZ(rKDwIsCt}y1I=-Sy`^m!{+<)cUz!V zrUH+j9zfyfo!X^^(a(B0uTRasd36GyG*hdbzj8INOqag=LW_Sp6oQ|7^i>GQ2D`lr zj&0*~z_Dn0yu2nk+LGR8Jyw8Zv`^Y&^IEW+^?{8E&WD?R)`~K>@l7b|2u$tEp+j_P z3YQX((=uIZTPR1~VjEA!YtP=Z3+&Mk*>X^rfN+pEEpXaDK;VRrw>b!nz)u}5L3y$O zrU^Vd9RPW@rFFaQ>H+(Fb5c_!zv2I_3$CWF8gBi!P1}r>dZc{T{SSG$NEOfntS1)W)?BEK_U&4uWJs zKz<-TMex1=LeNqGXAX8FC+F-GDQ8MFVN#`JJy(wd$KF7l@S<>ufcDPRwhUEy5z#M!Q#OwM0M(DM$7-OZ46bQv0B#uA-7!P^TLH-6#CJMYs1h;w zpb2VQZ#0`tpY5addOeUO*#$;DjJ!XjI{gq&!6Al&58JjK(b+Lldx4YBDQliX^#CG* zy<>o?Dxep}LW!kagdV}R!k>o7y%W^h-iWlz=K&1EH|eADepxwG1;|klRsy&T*vp41 z5ErGqi3(A`6N<{7X4tJ3j1-HprkN^0;QZX_%ad{>fs+fBzaA-^`QQ|Qo*%5?qF_P? zQPTUpwsx1ZrvTR1*Q0zT${LMEwEpAnKg%g7vLl*h#P-0k`lr5!NN2lc*}^m}fnPO&SqsDH^w|w6 zkb59h0m{h#Awd~xTJMjEEIFsM4fTKwp0UE|&?2n30%$zP9c#%L#zY)4?nUA(H zEfdBk_y=|bM6!qvqS@wFaDkxP5M4+@HyyrUxVI$M@r?*=h)O!TQDq(UMakCASrPWI gAFOVBIQ_5AQi0VO{leKNuaD54+QL$0bN+hs4=$c23;+NC literal 0 HcmV?d00001 diff --git a/images/themes/default/mActionMergeFeatures.png b/images/themes/default/mActionMergeFeatures.png new file mode 100644 index 0000000000000000000000000000000000000000..727482a30b1924cc8d2c031c5400cb8add41a18a GIT binary patch literal 1724 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 false ); + mActionMergeFeatures = new QAction( getThemeIcon("mActionMergeFeatures.png"), tr("Merge selected features"), this); + shortcuts->registerAction(mActionMergeFeatures); + mActionMergeFeatures->setStatusTip( tr("Merge selected features")); + connect( mActionMergeFeatures, SIGNAL(triggered()), this, SLOT(mergeSelectedFeatures())); + mActionMergeFeatures->setEnabled(false); + // View Menu Items @@ -1053,6 +1060,7 @@ void QgisApp::createActionGroups() mMapToolGroup->addAction( mActionDeleteRing ); mActionDeletePart->setCheckable( true ); mMapToolGroup->addAction( mActionDeletePart ); + mMapToolGroup->addAction( mActionMergeFeatures); } void QgisApp::createMenus() @@ -1141,6 +1149,7 @@ void QgisApp::createMenus() mEditMenu->addAction( mActionAddIsland ); mEditMenu->addAction( mActionDeleteRing ); mEditMenu->addAction( mActionDeletePart ); + mEditMenu->addAction( mActionMergeFeatures ); if ( layout == QDialogButtonBox::GnomeLayout || layout == QDialogButtonBox::MacLayout ) { @@ -1345,6 +1354,7 @@ void QgisApp::createToolBars() mAdvancedDigitizeToolBar->addAction( mActionAddIsland ); mAdvancedDigitizeToolBar->addAction( mActionDeleteRing ); mAdvancedDigitizeToolBar->addAction( mActionDeletePart ); + mAdvancedDigitizeToolBar->addAction( mActionMergeFeatures ); mToolbarMenu->addAction( mAdvancedDigitizeToolBar->toggleViewAction() ); @@ -4085,6 +4095,139 @@ void QgisApp::deletePart() mMapCanvas->setMapTool( mMapTools.mDeletePart ); } +QgsGeometry* QgisApp::unionGeometries(const QgsVectorLayer* vl, QgsFeatureList& featureList) const +{ + if(!vl || featureList.size() < 2) + { + return 0; + } + + QgsGeometry* unionGeom = featureList[0].geometry(); + QgsGeometry* backupPtr = 0; //pointer to delete intermediate results + if(!unionGeom) + { + return 0; + } + + for(int i = 1; i < featureList.size(); ++i) + { + QgsGeometry* currentGeom = featureList[i].geometry(); + if(currentGeom) + { + backupPtr = unionGeom; + unionGeom = unionGeom->combine(currentGeom); + if(i > 1) //delete previous intermediate results + { + delete backupPtr; + backupPtr = 0; + } + } + } + return unionGeom; +} + +void QgisApp::mergeSelectedFeatures() +{ + //get active layer (hopefully vector) + QgsMapLayer* activeMapLayer = activeLayer(); + if(!activeMapLayer) + { + QMessageBox::information(0, tr("No active layer"), tr("No active layer found. Please select a layer in the layer list")); + return; + } + QgsVectorLayer* vl = dynamic_cast(activeMapLayer); + if(!vl) + { + QMessageBox::information(0, tr("Active layer is not vector"), tr("The merge features tool only works on vector layers. Please select a vector layer from the layer list")); + return; + } + if(!vl->isEditable()) + { + QMessageBox::information(0, tr("Layer not editable"), tr("Merging features can only be done for layers in editing mode. To use the merge tool, go to Layer->Toggle editing")); + return; + } + + //get selected feature ids (as a QSet ) + const QgsFeatureIds& featureIdSet = vl->selectedFeaturesIds(); + if(featureIdSet.size() < 2) + { + QMessageBox::information(0, "Not enough features selected", tr("The merge tool requires at least two selected features")); + return; + } + + //get initial selection (may be altered by attribute merge dialog later) + QgsFeatureList featureList = vl->selectedFeatures(); //get QList + QgsGeometry* unionGeom = unionGeometries(vl, featureList); + if(!unionGeom) + { + return; + } + + //make a first geometry union and notify the user straight away if the union geometry type does not match the layer one + QGis::WkbType originalType = vl->wkbType(); + QGis::WkbType newType = unionGeom->wkbType(); + if(unionGeom->wkbType() != vl->wkbType()) + { + QMessageBox::critical(0, "Union operation canceled", tr("The union operation would result in a geometry type that is not compatible with the current layer and therefore is canceled")); + delete unionGeom; + return; + } + + //merge the attributes together + QgsMergeAttributesDialog d(featureList, vl, mapCanvas()); + if(d.exec() == QDialog::Rejected) + { + return; + } + + QgsFeatureList featureListAfter = vl->selectedFeatures(); + + if(featureListAfter.size() < 2) + { + QMessageBox::information(0, "Not enough features selected", tr("The merge tool requires at least two selected features")); + delete unionGeom; + return; + } + + //if the user changed the feature selection in the merge dialog, we need to repead the union and check the type + if(featureList.size() != featureListAfter.size()) + { + delete unionGeom; + unionGeom = unionGeometries(vl, featureListAfter); + if(!unionGeom) + { + return; + } + + originalType = vl->wkbType(); + newType = unionGeom->wkbType(); + if(unionGeom->wkbType() != vl->wkbType()) + { + QMessageBox::critical(0, "Union operation canceled", tr("The union operation would result in a geometry type that is not compatible with the current layer and therefore is canceled")); + delete unionGeom; + return; + } + } + + //create new feature + QgsFeature newFeature; + newFeature.setGeometry(unionGeom); + newFeature.setAttributeMap(d.mergedAttributesMap()); + + QgsFeatureList::const_iterator feature_it = featureListAfter.constBegin(); + for(; feature_it != featureListAfter.constEnd(); ++feature_it) + { + vl->deleteFeature(feature_it->id()); + } + + vl->addFeature(newFeature, false); + + if(mapCanvas()) + { + mapCanvas()->refresh(); + } +} + void QgisApp::splitFeatures() { mMapCanvas->setMapTool( mMapTools.mSplitFeatures ); @@ -5314,6 +5457,17 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionCutFeatures->setEnabled( false ); } + //merge tool needs editable layer and provider with the capability of adding and deleting features + if ( vlayer->isEditable() && (dprovider->capabilities() & QgsVectorDataProvider::DeleteFeatures) \ + && QgsVectorDataProvider::AddFeatures) + { + mActionMergeFeatures->setEnabled(layerHasSelection); + } + else + { + mActionMergeFeatures->setEnabled( false ); + } + // moving enabled if geometry changes are supported if ( vlayer->isEditable() && dprovider->capabilities() & QgsVectorDataProvider::ChangeGeometries ) { @@ -5366,6 +5520,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer ) mActionSplitFeatures->setEnabled( true ); mActionSimplifyFeature->setEnabled( true ); mActionDeletePart->setEnabled( true ); + } else { diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index eae9d07dd48..e5e7aa9071a 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -41,6 +41,7 @@ class QValidator; class QgisAppInterface; class QgsClipboard; class QgsComposer; +class QgsGeometry; class QgsHelpViewer; class QgsFeature; @@ -63,6 +64,7 @@ class QgsVectorLayer; #include #include "qgsconfig.h" +#include "qgsfeature.h" #include "qgspoint.h" /*! \class QgisApp @@ -509,6 +511,8 @@ class QgisApp : public QMainWindow void deleteRing(); //! deletes part of polygon void deletePart(); + //! merges the selected features together + void mergeSelectedFeatures(); //! activates the selection tool void select(); @@ -655,6 +659,9 @@ class QgisApp : public QMainWindow void pasteTransformations(); //! check to see if file is dirty and if so, prompt the user th save it bool saveDirty(); + /** Helper function to union several geometries together (used in function mergeSelectedFeatures) + @return 0 in case of error*/ + QgsGeometry* unionGeometries(const QgsVectorLayer* vl, QgsFeatureList& featureList) const; /// QgisApp aren't copyable QgisApp( QgisApp const & ); @@ -719,6 +726,7 @@ class QgisApp : public QMainWindow QAction *mActionSimplifyFeature; QAction *mActionDeleteRing; QAction *mActionDeletePart; + QAction *mActionMergeFeatures; QAction *mActionEditSeparator3; QAction *mActionPan; diff --git a/src/app/qgsmergeattributesdialog.cpp b/src/app/qgsmergeattributesdialog.cpp new file mode 100644 index 00000000000..9e11f0c05a4 --- /dev/null +++ b/src/app/qgsmergeattributesdialog.cpp @@ -0,0 +1,532 @@ +/*************************************************************************** + qgsmergeattributesdialog.cpp + ---------------------------- + begin : May 2009 + copyright : (C) 2009 by Marco Hugentobler + email : marco dot hugentobler at karto dot baug dot ethz dot ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#include "qgsmergeattributesdialog.h" +#include "qgisapp.h" +#include "qgsfield.h" +#include "qgsmapcanvas.h" +#include "qgsrubberband.h" +#include "qgsvectorlayer.h" +#include +#include + +QgsMergeAttributesDialog::QgsMergeAttributesDialog(const QgsFeatureList& features, QgsVectorLayer* vl, QgsMapCanvas* canvas, QWidget * parent, Qt::WindowFlags f): QDialog(parent, f), mFeatureList(features), mVectorLayer(vl), mMapCanvas(canvas), mSelectionRubberBand(0) +{ + setupUi(this); + createTableWidgetContents(); + + QHeaderView* verticalHeader = mTableWidget->verticalHeader(); + if(verticalHeader) + { + QObject::connect(mTableWidget, SIGNAL(itemSelectionChanged ()), this, SLOT(selectedRowChanged())); + } + mTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); + mTableWidget->setSelectionMode(QAbstractItemView::SingleSelection); + + mFromSelectedPushButton->setIcon(QgisApp::getThemeIcon("mActionFromSelectedFeature.png")); + mRemoveFeatureFromSelectionButton->setIcon(QgisApp::getThemeIcon("mActionRemoveSelectedFeature.png")); +} + +QgsMergeAttributesDialog::QgsMergeAttributesDialog(): QDialog() +{ + setupUi(this); +} + +QgsMergeAttributesDialog::~QgsMergeAttributesDialog() +{ + delete mSelectionRubberBand; +} + +void QgsMergeAttributesDialog::createTableWidgetContents() +{ + //get information about attributes from vector layer + if(!mVectorLayer) + { + return; + } + const QgsFieldMap& fieldMap = mVectorLayer->pendingFields(); + + //combo box row, attributes titles, feature values and current merge results + mTableWidget->setRowCount(mFeatureList.size() + 2); + mTableWidget->setColumnCount(fieldMap.size()); + + //create combo boxes + for(int i = 0; i < fieldMap.size(); ++i) + { + mTableWidget->setCellWidget(0, i, createMergeComboBox(fieldMap[i].type())); + } + + QgsFieldMap::const_iterator fieldIt = fieldMap.constBegin(); + + //insert attribute names + QStringList horizontalHeaderLabels; + for(; fieldIt != fieldMap.constEnd(); ++fieldIt) + { + horizontalHeaderLabels << fieldIt.value().name(); + } + mTableWidget->setHorizontalHeaderLabels(horizontalHeaderLabels); + + //insert the attribute values + int currentRow = 1; + QStringList verticalHeaderLabels; //the id column is in the + verticalHeaderLabels << tr("Id"); + + for(int i = 0; i < mFeatureList.size(); ++i) + { + verticalHeaderLabels << QString::number(mFeatureList[i].id()); + QgsAttributeMap currentAttributeMap = mFeatureList[i].attributeMap(); + QgsAttributeMap::const_iterator currentMapIt = currentAttributeMap.constBegin(); + int col = 0; + for(; currentMapIt != currentAttributeMap.constEnd(); ++currentMapIt) + { + QTableWidgetItem* attributeValItem = new QTableWidgetItem(currentMapIt.value().toString()); + attributeValItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + mTableWidget->setItem(currentRow, col, attributeValItem); + ++col; + } + ++currentRow; + } + + //merge + verticalHeaderLabels << tr("Merge"); + mTableWidget->setVerticalHeaderLabels(verticalHeaderLabels); + + + //insert currently merged values + for(int i = 0; i < fieldMap.size(); ++i) + { + refreshMergedValue(i); + } +} + +QComboBox* QgsMergeAttributesDialog::createMergeComboBox(QVariant::Type columnType) const +{ + QComboBox* newComboBox = new QComboBox(); + //add items for feature + QgsFeatureList::const_iterator f_it = mFeatureList.constBegin(); + for(; f_it != mFeatureList.constEnd(); ++f_it) + { + newComboBox->addItem(tr("feature %1").arg(f_it->id())); + } + + if(columnType == QVariant::Double || columnType == QVariant::Int) + { + newComboBox->addItem(tr("Minimum")); + newComboBox->addItem(tr("Maximum")); + newComboBox->addItem(tr("Median")); + } + else if(columnType == QVariant::String) + { + newComboBox->addItem(tr("Concatenation")); + } + if(columnType == QVariant::Double) + { + newComboBox->addItem(tr("Mean")); + } + + QObject::connect(newComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(comboValueChanged(const QString&))); + return newComboBox; +} + +int QgsMergeAttributesDialog::findComboColumn(QComboBox* c) const +{ + for(int i = 0; i < mTableWidget->columnCount(); ++i) + { + if(mTableWidget->cellWidget(0, i) == c) + { + return i; + } + } + return -1; +} + +void QgsMergeAttributesDialog::comboValueChanged(const QString & text) +{ + QComboBox* senderComboBox = dynamic_cast(sender()); + if(!senderComboBox) + { + return; + } + int column = findComboColumn(senderComboBox); + refreshMergedValue(column); +} + +void QgsMergeAttributesDialog::selectedRowChanged() +{ + //find out selected row + QList selectionList = mTableWidget->selectedItems(); + if(selectionList.size() < 1) + { + delete mSelectionRubberBand; + mSelectionRubberBand = 0; + return; + } + + int row = selectionList[0]->row(); + + if(!mTableWidget || !mMapCanvas || !mVectorLayer || row < 1 || row >= (mTableWidget->rowCount())) + { + return; + } + + //read the feature id + QTableWidgetItem* idItem = mTableWidget->verticalHeaderItem(row); + if(!idItem) + { + return; + } + + bool conversionSuccess = false; + int featureIdToSelect = idItem->text().toInt(&conversionSuccess); + if(!conversionSuccess) + { + //the merge result row was selected + delete mSelectionRubberBand; + mSelectionRubberBand = 0; + return; + } + createRubberBandForFeature(featureIdToSelect); +} + +void QgsMergeAttributesDialog::refreshMergedValue(int col) +{ + //get QComboBox + QWidget* cellWidget = mTableWidget->cellWidget(0, col); + if(!cellWidget) + { + return; + } + QComboBox* comboBox = dynamic_cast(cellWidget); + if(!comboBox) + { + return; + } + + //evaluate behaviour (feature value or min / max / mean ) + QString mergeBehaviourString = comboBox->currentText(); + QString evalText; //text that has to be inserted into merge result field + if(mergeBehaviourString == tr("Minimum")) + { + evalText = minimumAttributeString(col); + } + else if(mergeBehaviourString == tr("Maximum")) + { + evalText = maximumAttributeString(col); + } + else if(mergeBehaviourString == tr("Mean")) + { + evalText = meanAttributeString(col); + } + else if(mergeBehaviourString == tr("Median")) + { + evalText = medianAttributeString(col); + } + else if(mergeBehaviourString == tr("Concatenation")) + { + evalText = concatenationAttributeString(col); + } + else //an existing feature value + { + int featureId = mergeBehaviourString.split(" ").at(1).toInt(); //probably not very robust for translations... + evalText = featureAttributeString(featureId, col); + } + + //insert string into table widget + QTableWidgetItem* newTotalItem = new QTableWidgetItem(evalText); + newTotalItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + mTableWidget->setItem(mTableWidget->rowCount() - 1, col, newTotalItem); +} + +QString QgsMergeAttributesDialog::featureAttributeString(int featureId, int col) +{ + QString resultText; + for(int i = 0; i < mFeatureList.size(); ++i) + { + int currentFid = mFeatureList[i].id(); + if(currentFid == featureId) + { + QTableWidgetItem* currentItem = mTableWidget->item(i+1, col); + if(!currentItem) + { + continue; + } + resultText = currentItem->text(); + } + } + return resultText; +} + +QString QgsMergeAttributesDialog::minimumAttributeString(int col) +{ + double minimumValue = std::numeric_limits::max(); + double currentValue; + bool conversion = false; + int numberOfConsideredFeatures = 0; + + for(int i = 0; i < mFeatureList.size(); ++i) + { + currentValue = mTableWidget->item(i+1, col)->text().toDouble(&conversion); + if(conversion) + { + if(currentValue < minimumValue) + { + minimumValue = currentValue; + ++numberOfConsideredFeatures; + } + } + } + + if(numberOfConsideredFeatures < 1) + { + return QString(); + } + + return QString::number(minimumValue, 'f'); +} + +QString QgsMergeAttributesDialog::maximumAttributeString(int col) +{ + double maximumValue = -std::numeric_limits::max(); + double currentValue; + bool conversion = false; + int numberOfConsideredFeatures = 0; + + for(int i = 0; i < mFeatureList.size(); ++i) + { + currentValue = mTableWidget->item(i+1, col)->text().toDouble(&conversion); + if(conversion) + { + if(currentValue > maximumValue) + { + maximumValue = currentValue; + ++numberOfConsideredFeatures; + } + } + } + + if(numberOfConsideredFeatures < 1) + { + return QString(); + } + + return QString::number(maximumValue, 'f'); +} + +QString QgsMergeAttributesDialog::meanAttributeString(int col) +{ + int numberOfConsideredFeatures = 0; + double currentValue; + double sum = 0; + bool conversion = false; + + for(int i = 0; i < mFeatureList.size(); ++i) + { + currentValue = mTableWidget->item(i+1, col)->text().toDouble(&conversion); + if(conversion) + { + sum += currentValue; + ++numberOfConsideredFeatures; + } + } + double mean = sum / numberOfConsideredFeatures; + return QString::number(mean, 'f'); +} + +QString QgsMergeAttributesDialog::medianAttributeString(int col) +{ + //bring all values into a list and sort + QList valueList; + double currentValue; + bool conversionSuccess; + + for(int i = 0; i < mFeatureList.size(); ++i) + { + currentValue = mTableWidget->item(i+1, col)->text().toDouble(&conversionSuccess); + if(!conversionSuccess) + { + continue; + } + valueList.push_back(currentValue); + } + qSort(valueList); + + double medianValue; + int size = valueList.size(); + bool even = (size % 2) < 1; + if(even) + { + medianValue = (valueList[size / 2 - 1] + valueList[size / 2]) / 2; + } + else //odd + { + medianValue = valueList[(size + 1) / 2 - 1]; + } + return QString::number(medianValue, 'f'); +} + +QString QgsMergeAttributesDialog::concatenationAttributeString(int col) +{ + QStringList concatString; + for(int i = 0; i < mFeatureList.size(); ++i) + { + concatString << mTableWidget->item(i+1, col)->text(); + } + return concatString.join(","); //todo: make separator user configurable +} + +void QgsMergeAttributesDialog::on_mFromSelectedPushButton_clicked() +{ + //find the selected feature + if(!mVectorLayer) + { + return; + } + + //find out feature id of selected row + QList selectionList = mTableWidget->selectedItems(); + if(selectionList.size() < 1) + { + return; + } + + //assume all selected items to be in the same row + QTableWidgetItem* selectedItem = selectionList[0]; + int selectedRow = selectedItem->row(); + QTableWidgetItem* selectedHeaderItem = mTableWidget->verticalHeaderItem(selectedRow); + if(!selectedHeaderItem) + { + return; + } + + bool conversionSuccess; + int featureId = selectedHeaderItem->text().toInt(&conversionSuccess); + if(!conversionSuccess) + { + return; + } + + for(int i = 0; i < mTableWidget->columnCount(); ++i) + { + QComboBox* currentComboBox = dynamic_cast(mTableWidget->cellWidget(0, i)); + if(currentComboBox) + { + currentComboBox->setCurrentIndex(currentComboBox->findText(tr("feature %1").arg(featureId))); + } + } +} + +void QgsMergeAttributesDialog::on_mRemoveFeatureFromSelectionButton_clicked() +{ + if(!mVectorLayer) + { + return; + } + + //find out feature id of selected row + QList selectionList = mTableWidget->selectedItems(); + if(selectionList.size() < 1) + { + return; + } + + //assume all selected items to be in the same row + QTableWidgetItem* selectedItem = selectionList[0]; + int selectedRow = selectedItem->row(); + QTableWidgetItem* selectedHeaderItem = mTableWidget->verticalHeaderItem(selectedRow); + if(!selectedHeaderItem) + { + return; + } + + bool conversionSuccess; + int featureId = selectedHeaderItem->text().toInt(&conversionSuccess); + if(!conversionSuccess) + { + selectedRowChanged(); + return; + } + + mTableWidget->removeRow(selectedRow); + selectedRowChanged(); + + //remove feature from the vector layer selection + QgsFeatureIds selectedIds = mVectorLayer->selectedFeaturesIds(); + selectedIds.remove(featureId); + mVectorLayer->setSelectedFeatures(selectedIds); + mMapCanvas->repaint(); + + //remove feature option from the combo box (without altering the current merge values) + for(int i = 0; i < mTableWidget->columnCount(); ++i) + { + QComboBox* currentComboBox = dynamic_cast(mTableWidget->cellWidget(0, i)); + if(currentComboBox) + { + currentComboBox->blockSignals(true); + currentComboBox->removeItem(currentComboBox->findText(tr("feature %1").arg(featureId))); + currentComboBox->blockSignals(false); + } + } + + //finally remove the feature from mFeatureList + QgsFeatureList::iterator f_it = mFeatureList.begin(); + for(; f_it != mFeatureList.end(); ++f_it) + { + if(f_it->id() == featureId) + { + mFeatureList.erase(f_it); + break; + } + } +} + +void QgsMergeAttributesDialog::createRubberBandForFeature(int featureId) +{ + //create rubber band to highlight the feature + delete mSelectionRubberBand; + mSelectionRubberBand = new QgsRubberBand(mMapCanvas, mVectorLayer->geometryType() == QGis::Polygon); + mSelectionRubberBand->setColor(QColor(255, 0, 0)); + QgsFeature featureToSelect; + mVectorLayer->featureAtId(featureId, featureToSelect, true, false); + mSelectionRubberBand->setToGeometry( featureToSelect.geometry(), mVectorLayer ); +} + +QgsAttributeMap QgsMergeAttributesDialog::mergedAttributesMap() const +{ + QgsAttributeMap resultMap; + if(mFeatureList.size() < 1) + { + return resultMap; //return empty map + } + + resultMap = mFeatureList[0].attributeMap(); + //go through all the items and replace the values in the attribute map + for(int i = 0; i < resultMap.size(); ++i) + { + QTableWidgetItem* currentItem = mTableWidget->item(mFeatureList.size() + 1, i); + if(!currentItem) + { + continue; + } + QString mergedString = currentItem->text(); + QVariant newValue(mergedString); + resultMap.insert(i, newValue); + } + + return resultMap; +} + diff --git a/src/app/qgsmergeattributesdialog.h b/src/app/qgsmergeattributesdialog.h new file mode 100644 index 00000000000..7dc42c30772 --- /dev/null +++ b/src/app/qgsmergeattributesdialog.h @@ -0,0 +1,78 @@ +/*************************************************************************** + qgsmergeattributesdialog.h + -------------------------- + begin : May 2009 + copyright : (C) 2009 by Marco Hugentobler + email : marco dot hugentobler at karto dot baug dot ethz dot ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#ifndef QGSMERGEATTRIBUTESDIALOG_H +#define QGSMERGEATTRIBUTESDIALOG_H + +#include "ui_qgsmergeattributesdialogbase.h" +#include "qgsfeature.h" + +class QgsMapCanvas; +class QgsRubberBand; +class QgsVectorLayer; +class QComboBox; + + +/**A dialog to insert the merge behaviour for attributes (e.g. for the union features editing tool)*/ +class QgsMergeAttributesDialog: public QDialog, private Ui::QgsMergeAttributesDialogBase +{ + Q_OBJECT + public: + QgsMergeAttributesDialog(const QgsFeatureList& features, QgsVectorLayer* vl, QgsMapCanvas* canvas, QWidget * parent = 0, Qt::WindowFlags f = 0); + ~QgsMergeAttributesDialog(); + QgsAttributeMap mergedAttributesMap() const; + + private slots: + void comboValueChanged(const QString & text); + void selectedRowChanged(); + void on_mFromSelectedPushButton_clicked(); + void on_mRemoveFeatureFromSelectionButton_clicked(); + + private: + QgsMergeAttributesDialog(); //default constructor forbidden + void createTableWidgetContents(); + /**Create new combo box with the options for featureXX / mean / min / max */ + QComboBox* createMergeComboBox(QVariant::Type columnType) const; + /**Returns the table widget column index of a combo box + @return the column index or -1 in case of error*/ + int findComboColumn(QComboBox* c) const; + /**Calculates the merged value of a column (depending on the selected merge behaviour) and inserts the value in the corresponding cell*/ + void refreshMergedValue(int col); + /**Inserts the attribute value of a specific feature into the row of merged attributes*/ + QString featureAttributeString(int featureId, int col); + /**Calculates and inserts the minimum attribute value of a column*/ + QString minimumAttributeString(int col); + /**Calculates and inserts the maximum value of a column*/ + QString maximumAttributeString(int col); + /**Calculates and inserts the mean value of a column*/ + QString meanAttributeString(int col); + /**Calculates and inserts the median value of a column*/ + QString medianAttributeString(int col); + /**Appends the values of the features for the final value*/ + QString concatenationAttributeString(int col); + /**Sets mSelectionRubberBand to a new feature*/ + void createRubberBandForFeature(int featureId); + + QgsFeatureList mFeatureList; + QgsVectorLayer* mVectorLayer; + QgsMapCanvas* mMapCanvas; + /**Item that highlights the selected feature in the merge table*/ + QgsRubberBand* mSelectionRubberBand; +}; + +#endif // QGSMERGEATTRIBUTESDIALOG_H diff --git a/src/ui/qgsmergeattributesdialogbase.ui b/src/ui/qgsmergeattributesdialogbase.ui new file mode 100644 index 00000000000..cb9c8acfbb8 --- /dev/null +++ b/src/ui/qgsmergeattributesdialogbase.ui @@ -0,0 +1,134 @@ + + QgsMergeAttributesDialogBase + + + + 0 + 0 + 450 + 382 + + + + Merge feature attributes + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + Take attributes from selected feature + + + + + + + Qt::Horizontal + + + + 58 + 20 + + + + + + + + + + + + + + + + + + + Remove feature from selection + + + + + + + Qt::Horizontal + + + + 98 + 20 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + QgsMergeAttributesDialogBase + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QgsMergeAttributesDialogBase + reject() + + + 316 + 260 + + + 286 + 274 + + + + +