[QGIS Commit] r10879 - in trunk/qgis: images/themes/default src/app src/ui

svn_qgis at osgeo.org svn_qgis at osgeo.org
Wed Jun 3 07:33:59 EDT 2009


Author: mhugent
Date: 2009-06-03 07:33:58 -0400 (Wed, 03 Jun 2009)
New Revision: 10879

Added:
   trunk/qgis/images/themes/default/mActionFromSelectedFeature.png
   trunk/qgis/images/themes/default/mActionMergeFeatures.png
   trunk/qgis/images/themes/default/mActionRemoveSelectedFeature.png
   trunk/qgis/src/app/qgsmergeattributesdialog.cpp
   trunk/qgis/src/app/qgsmergeattributesdialog.h
   trunk/qgis/src/ui/qgsmergeattributesdialogbase.ui
Modified:
   trunk/qgis/src/app/CMakeLists.txt
   trunk/qgis/src/app/qgisapp.cpp
   trunk/qgis/src/app/qgisapp.h
Log:
New functionality for merging features

Added: trunk/qgis/images/themes/default/mActionFromSelectedFeature.png
===================================================================
(Binary files differ)


Property changes on: trunk/qgis/images/themes/default/mActionFromSelectedFeature.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/qgis/images/themes/default/mActionMergeFeatures.png
===================================================================
(Binary files differ)


Property changes on: trunk/qgis/images/themes/default/mActionMergeFeatures.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/qgis/images/themes/default/mActionRemoveSelectedFeature.png
===================================================================
(Binary files differ)


Property changes on: trunk/qgis/images/themes/default/mActionRemoveSelectedFeature.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Modified: trunk/qgis/src/app/CMakeLists.txt
===================================================================
--- trunk/qgis/src/app/CMakeLists.txt	2009-06-03 07:06:25 UTC (rev 10878)
+++ trunk/qgis/src/app/CMakeLists.txt	2009-06-03 11:33:58 UTC (rev 10879)
@@ -41,6 +41,7 @@
   qgsmaptoolvertexedit.cpp
   qgsmeasuredialog.cpp
   qgsmeasuretool.cpp
+  qgsmergeattributesdialog.cpp
   qgsnewhttpconnection.cpp
   qgsnumericsortlistviewitem.cpp
   qgsogrsublayersdialog.cpp
@@ -129,6 +130,7 @@
 
   qgsmeasuretool.h
   qgsmeasuredialog.h
+  qgsmergeattributesdialog.h
   qgsnewhttpconnection.h
   qgsoptions.h
   qgsogrsublayersdialog.h

Modified: trunk/qgis/src/app/qgisapp.cpp
===================================================================
--- trunk/qgis/src/app/qgisapp.cpp	2009-06-03 07:06:25 UTC (rev 10878)
+++ trunk/qgis/src/app/qgisapp.cpp	2009-06-03 11:33:58 UTC (rev 10879)
@@ -114,6 +114,7 @@
 #include "qgsmapoverviewcanvas.h"
 #include "qgsmaprenderer.h"
 #include "qgsmaptip.h"
+#include "qgsmergeattributesdialog.h"
 #include "qgsmessageviewer.h"
 #include "qgsoptions.h"
 #include "qgspastetransformations.h"
@@ -703,7 +704,13 @@
   connect( mActionDeletePart, SIGNAL( triggered() ), this, SLOT( deletePart() ) );
   mActionDeletePart->setEnabled( 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
 
   mActionPan = new QAction( getThemeIcon( "mActionPan.png" ), tr( "Pan Map" ), this );
@@ -1053,6 +1060,7 @@
   mMapToolGroup->addAction( mActionDeleteRing );
   mActionDeletePart->setCheckable( true );
   mMapToolGroup->addAction( mActionDeletePart );
+  mMapToolGroup->addAction( mActionMergeFeatures);
 }
 
 void QgisApp::createMenus()
@@ -1141,6 +1149,7 @@
   mEditMenu->addAction( mActionAddIsland );
   mEditMenu->addAction( mActionDeleteRing );
   mEditMenu->addAction( mActionDeletePart );
+  mEditMenu->addAction( mActionMergeFeatures );
 
   if ( layout == QDialogButtonBox::GnomeLayout || layout == QDialogButtonBox::MacLayout )
   {
@@ -1345,6 +1354,7 @@
   mAdvancedDigitizeToolBar->addAction( mActionAddIsland );
   mAdvancedDigitizeToolBar->addAction( mActionDeleteRing );
   mAdvancedDigitizeToolBar->addAction( mActionDeletePart );
+  mAdvancedDigitizeToolBar->addAction( mActionMergeFeatures );
   mToolbarMenu->addAction( mAdvancedDigitizeToolBar->toggleViewAction() );
 
 
@@ -4085,6 +4095,139 @@
   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<QgsVectorLayer*>(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<int> )
+    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<QgsFeature>
+    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 @@
         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 @@
           mActionSplitFeatures->setEnabled( true );
           mActionSimplifyFeature->setEnabled( true );
           mActionDeletePart->setEnabled( true );
+
         }
         else
         {

Modified: trunk/qgis/src/app/qgisapp.h
===================================================================
--- trunk/qgis/src/app/qgisapp.h	2009-06-03 07:06:25 UTC (rev 10878)
+++ trunk/qgis/src/app/qgisapp.h	2009-06-03 11:33:58 UTC (rev 10879)
@@ -41,6 +41,7 @@
 class QgisAppInterface;
 class QgsClipboard;
 class QgsComposer;
+class QgsGeometry;
 class QgsHelpViewer;
 class QgsFeature;
 
@@ -63,6 +64,7 @@
 #include <QPointer>
 
 #include "qgsconfig.h"
+#include "qgsfeature.h"
 #include "qgspoint.h"
 
 /*! \class QgisApp
@@ -509,6 +511,8 @@
     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 @@
     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 @@
     QAction *mActionSimplifyFeature;
     QAction *mActionDeleteRing;
     QAction *mActionDeletePart;
+    QAction *mActionMergeFeatures;
     QAction *mActionEditSeparator3;
 
     QAction *mActionPan;

Added: trunk/qgis/src/app/qgsmergeattributesdialog.cpp
===================================================================
--- trunk/qgis/src/app/qgsmergeattributesdialog.cpp	                        (rev 0)
+++ trunk/qgis/src/app/qgsmergeattributesdialog.cpp	2009-06-03 11:33:58 UTC (rev 10879)
@@ -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 <limits>
+#include <QComboBox>
+
+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<QComboBox*>(sender());
+    if(!senderComboBox)
+    {
+        return;
+    }
+    int column = findComboColumn(senderComboBox);
+    refreshMergedValue(column);
+}
+
+void QgsMergeAttributesDialog::selectedRowChanged()
+{
+    //find out selected row
+    QList<QTableWidgetItem *> 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<QComboBox*>(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<double>::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<double>::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<double> 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<QTableWidgetItem *> 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<QComboBox*>(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<QTableWidgetItem *> 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<QComboBox*>(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;
+}
+

Added: trunk/qgis/src/app/qgsmergeattributesdialog.h
===================================================================
--- trunk/qgis/src/app/qgsmergeattributesdialog.h	                        (rev 0)
+++ trunk/qgis/src/app/qgsmergeattributesdialog.h	2009-06-03 11:33:58 UTC (rev 10879)
@@ -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

Added: trunk/qgis/src/ui/qgsmergeattributesdialogbase.ui
===================================================================
--- trunk/qgis/src/ui/qgsmergeattributesdialogbase.ui	                        (rev 0)
+++ trunk/qgis/src/ui/qgsmergeattributesdialogbase.ui	2009-06-03 11:33:58 UTC (rev 10879)
@@ -0,0 +1,134 @@
+<ui version="4.0" >
+ <class>QgsMergeAttributesDialogBase</class>
+ <widget class="QDialog" name="QgsMergeAttributesDialogBase" >
+  <property name="geometry" >
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>450</width>
+    <height>382</height>
+   </rect>
+  </property>
+  <property name="windowTitle" >
+   <string>Merge feature attributes</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout" >
+   <item row="0" column="0" >
+    <widget class="QTableWidget" name="mTableWidget" />
+   </item>
+   <item row="1" column="0" >
+    <layout class="QHBoxLayout" name="horizontalLayout" >
+     <item>
+      <widget class="QPushButton" name="mFromSelectedPushButton" >
+       <property name="sizePolicy" >
+        <sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text" >
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="mTakeSelectedAttributesLabel" >
+       <property name="text" >
+        <string>Take attributes from selected feature</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer" >
+       <property name="orientation" >
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0" >
+        <size>
+         <width>58</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item row="2" column="0" >
+    <layout class="QHBoxLayout" name="horizontalLayout_2" >
+     <item>
+      <widget class="QPushButton" name="mRemoveFeatureFromSelectionButton" >
+       <property name="text" >
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="mRemoveFeatureFromSelectionLabel" >
+       <property name="text" >
+        <string>Remove feature from selection</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_2" >
+       <property name="orientation" >
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0" >
+        <size>
+         <width>98</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item row="3" column="0" >
+    <widget class="QDialogButtonBox" name="buttonBox" >
+     <property name="orientation" >
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons" >
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>QgsMergeAttributesDialogBase</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel" >
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel" >
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>QgsMergeAttributesDialogBase</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel" >
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel" >
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>



More information about the QGIS-commit mailing list