[QGIS Commit] r14606 - in trunk/qgis/python/plugins/fTools: . tools

svn_qgis at osgeo.org svn_qgis at osgeo.org
Sat Nov 13 12:30:06 EST 2010


Author: cfarmer
Date: 2010-11-13 09:30:06 -0800 (Sat, 13 Nov 2010)
New Revision: 14606

Added:
   trunk/qgis/python/plugins/fTools/tools/doValidate.py
Modified:
   trunk/qgis/python/plugins/fTools/fTools.py
   trunk/qgis/python/plugins/fTools/tools/doVisual.py
Log:
[FEATURE] completely new geometry checking tool for fTools. Significantly (big time) faster, more relevant error messages, and now supports zooming to errors. Thanks to jef for the new QgsGeometry.validateGeometry function. Fixes #3169 and #2611.

Modified: trunk/qgis/python/plugins/fTools/fTools.py
===================================================================
--- trunk/qgis/python/plugins/fTools/fTools.py	2010-11-13 17:16:08 UTC (rev 14605)
+++ trunk/qgis/python/plugins/fTools/fTools.py	2010-11-13 17:30:06 UTC (rev 14606)
@@ -46,7 +46,7 @@
 import doIntersectLines, doJoinAttributes, doSelectByLocation, doVectorSplit, doMeanCoords
 import doPointDistance, doPointsInPolygon, doRandom, doRandPoints, doRegPoints, doDefineProj
 import doReProject, doSpatialJoin, doSubsetSelect, doSumLines, doVectorGrid, doMergeShapes
-import doAbout
+import doAbout, doValidate
 
 class fToolsPlugin:
   def __init__( self,iface ):
@@ -257,7 +257,8 @@
     d.exec_()
 
   def docheckGeom( self ):
-    d = doVisual.VisualDialog( self.iface, 1 )
+    d = doValidate.ValidateDialog(self.iface)
+    d.show()
     d.exec_()
 
   def domultiToSingle( self ):

Added: trunk/qgis/python/plugins/fTools/tools/doValidate.py
===================================================================
--- trunk/qgis/python/plugins/fTools/tools/doValidate.py	                        (rev 0)
+++ trunk/qgis/python/plugins/fTools/tools/doValidate.py	2010-11-13 17:30:06 UTC (rev 14606)
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+from qgis.core import *
+from ui_frmVisual import Ui_Dialog
+import ftools_utils
+import math
+
+class ValidateDialog( QDialog, Ui_Dialog ):
+  def __init__(self, iface):
+    QDialog.__init__(self)
+    self.iface = iface
+    self.setupUi(self)
+    self.setModal(False) # we want to be able to interact with the featuresmc.extent().width()
+    # adjust user interface
+    self.setWindowTitle( self.tr( "Check geometry validity" ) )
+    self.cmbField.setVisible( False )
+    self.label.setVisible( False )
+    self.useSelected.setVisible( True )
+    self.label_2.setText( self.tr( "Geometry errors" ) )
+    self.label_4.setText( self.tr( "Total encountered errors" ) )
+    self.partProgressBar.setVisible( False )
+    self.tblUnique.setSelectionMode(QAbstractItemView.SingleSelection)
+    self.tblUnique.setSelectionBehavior(QAbstractItemView.SelectRows)
+    # populate list of available layers
+    myList = ftools_utils.getLayerNames( [ QGis.Point, QGis.Line, QGis.Polygon ] )
+    self.connect(self.tblUnique, SIGNAL("currentItemChanged(QTableWidgetItem*, QTableWidgetItem*)" ), 
+    self.zoomToError)
+    self.inShape.addItems( myList )
+    self.cancel_close = self.buttonBox_2.button(QDialogButtonBox.Close)
+    self.buttonOk = self.buttonBox_2.button(QDialogButtonBox.Ok)
+    self.progressBar.setValue(0)
+    self.storedScale = self.iface.mapCanvas().scale()
+    
+  def keyPressEvent( self, e ):
+    if ( e.modifiers() == Qt.ControlModifier or \
+         e.modifiers() == Qt.MetaModifier ) and \
+         e.key() == Qt.Key_C:
+      #selection = self.tblUnique.selectedItems()
+      items = QString()
+      for row in range( self.tblUnique.rowCount() ):
+        items.append( self.tblUnique.item( row, 0 ).text()
+        + "," + self.tblUnique.item( row, 1 ).text() + "\n" )
+      if not items.isEmpty():
+        clip_board = QApplication.clipboard()
+        clip_board.setText( items )
+    else:
+      QDialog.keyPressEvent( self, e )
+
+  def accept( self ):
+    if self.inShape.currentText() == "":
+      QMessageBox.information( self, self.tr("Error!"), self.tr( "Please specify input vector layer" ) )
+    elif self.cmbField.isVisible() and self.cmbField.currentText() == "":
+      QMessageBox.information( self, self.tr("Error!"), self.tr( "Please specify input field" ) )
+    else:
+      self.validate( self.inShape.currentText(), self.useSelected.checkState() )
+      
+  def zoomToError(self, curr, prev):
+      if curr is None:
+        return
+      row = curr.row() # if we clicked in the first column, we want the second
+      item = self.tblUnique.item(row, 1)
+      if not item.data(Qt.UserRole) is None:
+        mc = self.iface.mapCanvas()
+        x = item.data(Qt.UserRole).toPyObject().x()
+        y = item.data(Qt.UserRole).toPyObject().y()
+        mc.zoomToPreviousExtent()
+        scale = mc.scale()
+        rect = QgsRectangle(float(x)-(4.0/scale),float(y)-(4.0/scale),
+                            float(x)+(4.0/scale),float(y)+(4.0/scale))
+        # Set the extent to our new rectangle
+        mc.setExtent(rect)
+        # Refresh the map
+        mc.refresh()
+
+  def validate( self,  myLayer, mySelection ):
+    vlayer = ftools_utils.getVectorLayerByName( myLayer )
+    self.tblUnique.clearContents()
+    self.tblUnique.setRowCount( 0 )
+    self.lstCount.clear()
+    self.buttonOk.setEnabled( False )
+    self.testThread = validateThread( self.iface.mainWindow(), self, vlayer, mySelection )
+    QObject.connect( self.testThread, SIGNAL( "runFinished(PyQt_PyObject)" ), self.runFinishedFromThread )
+    QObject.connect( self.testThread, SIGNAL( "runStatus(PyQt_PyObject)" ), self.runStatusFromThread )
+    QObject.connect( self.testThread, SIGNAL( "runRange(PyQt_PyObject)" ), self.runRangeFromThread )
+    self.cancel_close.setText( self.tr("Cancel") )
+    QObject.connect( self.cancel_close, SIGNAL( "clicked()" ), self.cancelThread )
+    QApplication.setOverrideCursor( Qt.WaitCursor )
+    self.testThread.start()
+    return True
+
+  def cancelThread( self ):
+    self.testThread.stop()
+    QApplication.restoreOverrideCursor()
+    self.buttonOk.setEnabled( True )
+    
+  def runFinishedFromThread( self, output ):
+    self.testThread.stop()
+    QApplication.restoreOverrideCursor()
+    self.buttonOk.setEnabled( True )
+    self.tblUnique.setColumnCount( 2 )
+    count = 0
+    for rec in output:
+      if len(rec[1]) < 1:
+        continue
+      where = None
+      for err in rec[1]: # for each error we find
+        self.tblUnique.insertRow(count)
+        fidItem = QTableWidgetItem( str(rec[0]) )
+        self.tblUnique.setItem( count, 0, fidItem )
+        if err.hasWhere(): # if there is a location associated with the error
+          where = err.where()
+        message = err.what()
+        errItem = QTableWidgetItem( message )
+        errItem.setData(Qt.UserRole, QVariant(where))
+        self.tblUnique.setItem( count, 1, errItem )
+        count += 1
+    self.tblUnique.setHorizontalHeaderLabels( [ self.tr("Feature"), self.tr("Error(s)") ] )
+    self.tblUnique.horizontalHeader().setResizeMode( 0, QHeaderView.ResizeToContents )
+    self.tblUnique.horizontalHeader().show()
+    self.tblUnique.horizontalHeader().setResizeMode( 1, QHeaderView.Stretch )
+    self.tblUnique.resizeRowsToContents()
+    self.lstCount.insert(str(count))
+    self.cancel_close.setText( "Close" )
+    QObject.disconnect( self.cancel_close, SIGNAL( "clicked()" ), self.cancelThread )
+    return True
+    
+  def runStatusFromThread( self, status ):
+    self.progressBar.setValue( status )
+        
+  def runRangeFromThread( self, range_vals ):
+    self.progressBar.setRange( range_vals[ 0 ], range_vals[ 1 ] )
+
+class validateThread( QThread ):
+  def __init__( self, parentThread, parentObject, vlayer, mySelection ):
+    QThread.__init__( self, parentThread )
+    self.parent = parentObject
+    self.running = False
+    self.vlayer = vlayer
+    self.mySelection = mySelection
+
+  def run( self ):
+    self.running = True
+    output = self.check_geometry( self.vlayer )
+    self.emit( SIGNAL( "runFinished(PyQt_PyObject)" ), output )
+    self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 )
+
+  def stop(self):
+    self.running = False
+
+  def check_geometry( self, vlayer ):
+    lstErrors = []
+    if self.mySelection:
+      layer = vlayer.selectedFeatures()
+      nFeat = len(layer)
+    else:
+      layer = vlayer
+      layer.select([]) # select all features, and ignore attributes
+      nFeat = layer.featureCount()
+    nElement = 0
+    if nFeat > 0:
+      self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 )
+      self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, nFeat ) )
+    for feat in layer:
+      if not self.running:
+        return list()
+      geom = QgsGeometry(feat.geometry()) # ger reference to geometry
+      self.emit(SIGNAL("runStatus(PyQt_PyObject)"), nElement)
+      nElement += 1
+      lstErrors.append((feat.id(), list(geom.validateGeometry())))
+    self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), nFeat )
+    return lstErrors

Modified: trunk/qgis/python/plugins/fTools/tools/doVisual.py
===================================================================
--- trunk/qgis/python/plugins/fTools/tools/doVisual.py	2010-11-13 17:16:08 UTC (rev 14605)
+++ trunk/qgis/python/plugins/fTools/tools/doVisual.py	2010-11-13 17:30:06 UTC (rev 14606)
@@ -65,14 +65,7 @@
       self.visual( self.inShape.currentText(), self.cmbField.currentText(), self.useSelected.checkState() )
   
   def manageGui( self ):
-    if self.myFunction == 1: # Check geometry validity
-      self.setWindowTitle( self.tr( "Check geometry validity" ) )
-      self.cmbField.setVisible( False )
-      self.label.setVisible( False )
-      self.useSelected.setVisible( False )
-      self.label_2.setText( self.tr( "Geometry errors" ) )
-      self.label_4.setText( self.tr( "Total encountered errors" ) )
-    elif self.myFunction == 2: # List unique values
+    if self.myFunction == 2: # List unique values
       self.setWindowTitle( self.tr( "List unique values" ) )
       self.label_2.setText( self.tr( "Unique values" ) )
       self.label_4.setText(self.tr(  "Total unique values" ) )
@@ -93,16 +86,14 @@
       self.lstCount.setVisible( False )
       self.resize( 381, 200 )
     self.inShape.clear()
-    if self.myFunction == 1:
-      myList = ftools_utils.getLayerNames( [ QGis.Polygon ] )
-    elif self.myFunction == 4:
+    if self.myFunction == 4:
       myList = ftools_utils.getLayerNames( [ QGis.Point ] )
     else:
       myList = ftools_utils.getLayerNames( [ QGis.Point, QGis.Line, QGis.Polygon ] )    
     self.inShape.addItems( myList )
     return
 
-#1:  Check geometry
+#1:  Check geometry (disabled)
 #2:  List unique values
 #3:  Basic statistics
 #4:  Nearest neighbour analysis
@@ -192,9 +183,8 @@
 
   def run( self ):
     self.running = True
-    if self.myFunction == 1: # Check geometry
-      ( lst, cnt ) = self.check_geometry( self.vlayer )
-    elif self.myFunction == 2: # List unique values
+    # note that 1 used to be associated with check_geometry
+    if self.myFunction == 2: # List unique values
       ( lst, cnt ) = self.list_unique_values( self.vlayer, self.myField )
     elif self.myFunction == 3: # Basic statistics
       ( lst, cnt ) = self.basic_statistics( self.vlayer, self.myField )
@@ -422,128 +412,3 @@
     lstStats.append( self.tr( "N:" ) + unicode( nVal ) )
     lstStats.append( self.tr( "Z-Score:" ) + unicode( zscore ) )
     return ( lstStats, [] )
-
-  def check_geometry( self, vlayer ):
-    vprovider = vlayer.dataProvider()
-    allAttrs = vprovider.attributeIndexes()
-    vprovider.select( allAttrs )
-    feat = QgsFeature()
-    geom = QgsGeometry()
-    count = 0
-    lstErrors = []
-    nFeat = vprovider.featureCount()
-    nElement = 0
-    if nFeat > 0:
-      self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 )
-      self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, nFeat ) )
-
-    while vprovider.nextFeature( feat ):
-      geom = QgsGeometry( feat.geometry() )
-      self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), nElement )
-      nElement += 1
-      if geom.isMultipart():
-        polygons = geom.asMultiPolygon()
-        for polygon in polygons:
-          if not self.isHoleNested( polygon ):
-            lstErrors.append( self.tr( "Feature %1 contains an unnested hole" ).arg( unicode( feat.id() ) ) )
-            count += 1
-          if not self.isPolygonClosed( polygon ):
-            lstErrors.append( self.tr( "Feature %1 is not closed" ).arg( unicode( feat.id() ) ) )
-            count += 1
-          if self.isSelfIntersecting( polygon ):
-            lstErrors.append( self.tr( "Feature %1 is self intersecting" ).arg( unicode( feat.id() ) ) )
-            count += 1
-          if not self.isCorrectOrientation( polygon ):
-            lstErrors.append( self.tr( "Feature %1 has incorrect node ordering" ).arg( unicode( feat.id() ) ) )
-            count += 1
-          
-      else:
-        geom = geom.asPolygon()
-        if not self.isHoleNested( geom ):
-          lstErrors.append( self.tr( "Feature %1 contains an unnested hole" ).arg( unicode( feat.id() ) ) )
-          count += 1
-        if not self.isPolygonClosed( geom ):
-          lstErrors.append( self.tr( "Feature %1 is not closed" ).arg( unicode( feat.id() ) ) )
-          count += 1
-        if self.isSelfIntersecting( geom ):
-          lstErrors.append( self.tr( "Feature %1 is self intersecting" ).arg( unicode( feat.id() ) ) )
-          count += 1
-        if not self.isCorrectOrientation( geom ):
-          lstErrors.append( self.tr( "Feature %1 has incorrect node ordering" ).arg( unicode( feat.id() ) ) )
-          count += 1
-    self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), nFeat )
-    return ( lstErrors, count )
-
-  def isHoleNested( self, polygon ):
-    if len( polygon ) <= 1:
-      return True
-    else:
-      outer = polygon[ 0 ]
-      for i in polygon[ 1: len( polygon ) ]:
-        if not self.arePointsInside( i, outer ):
-          return False
-    return True
-
-  def arePointsInside( self, inner, outer ):
-    outer = QgsGeometry().fromPolygon( [ outer ] )
-    for j in inner:
-      if not outer.contains(j):
-        return False
-    return True
-
-  def isPolygonClosed( self, polygon ):
-    for i in polygon:
-      first = i[ 0 ]
-      last = i[ len( i )-1 ]
-      if not first == last:
-        return False
-    return True
-
-  def isSelfIntersecting( self, polygon ):
-    cPart = 0
-    for h in polygon:
-      cPart += len(h)
-
-    self.emit( SIGNAL( "runPartRange(PyQt_PyObject)" ), ( 0, cPart ) )
-
-    nPart = 0
-    for h in polygon:
-      for i in range( 0, len(h)-1 ):
-        self.emit( SIGNAL( "runPartStatus(PyQt_PyObject)" ), nPart )
-
-        count = 0
-        for j in range( i+1, len(h)-1 ):
-          if QgsGeometry().fromPolyline( [ h[ i ], h[ i + 1 ] ] ).intersects( QgsGeometry().fromPolyline( [ h[ j ], h[ j + 1 ] ] ) ):
-            count += 1
-
-        if (i==0 and count>2) or (i>0 and count>1):
-          self.emit( SIGNAL( "runPartStatus(PyQt_PyObject)" ), cPart )
-          return True
-
-        nPart += 1
-
-    self.emit( SIGNAL( "runPartStatus(PyQt_PyObject)" ), cPart )
-
-    return False
-
-  def isCorrectOrientation( self, polygon ):
-    outer = True
-    for h in polygon:
-      if outer:
-        outer = False
-        if not self.isClockwise( h ):
-          return False
-      else:
-        if self.isClockwise(h):
-          return False
-    return True
-
-  def isClockwise( self, temp ):
-    area = 0
-    for pt in range( 0, len( temp ) -1 ):
-      area += ( temp[ pt ].x() * temp[ pt + 1 ].y() - temp[ pt + 1 ].x() * temp[ pt ].y() )
-    area = area / 2
-    if area <= 0:
-      return True
-    else:
-      return False



More information about the QGIS-commit mailing list