[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