# -*- coding: utf-8 -*- #----------------------------------------------------------- # # fTools # Copyright (C) 2008-2011 Carson Farmer # EMAIL: carson.farmer (at) gmail.com # WEB : http://www.ftools.ca/fTools.html # # A collection of data management and analysis tools for vector data # #----------------------------------------------------------- # # licensed under the terms of GNU GPL 2 # # 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. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # #--------------------------------------------------------------------- from PyQt4.QtCore import * from PyQt4.QtGui import * from qgis.core import * from ui_frmVisual import Ui_Dialog import ftools_utils import math class VisualDialog( QDialog, Ui_Dialog ): def __init__( self, iface, function ): QDialog.__init__( self, iface.mainWindow() ) self.iface = iface self.setupUi( self ) self.myFunction = function ## Set object visibility to False if tool is not Check geometry self.ckBoxShpError.hide() self.browseShpError.hide() self.lineEditShpError.hide() self.label_6.hide() self.line.hide() self.addToCanvasCheck.hide() self.buttonBox_2.setOrientation(Qt.Horizontal) if self.myFunction == 2 or self.myFunction == 3: QObject.connect( self.inShape, SIGNAL( "currentIndexChanged(QString)" ), self.update ) self.manageGui() self.cancel_close = self.buttonBox_2.button( QDialogButtonBox.Close ) self.buttonOk = self.buttonBox_2.button( QDialogButtonBox.Ok ) self.progressBar.setValue( 0 ) self.partProgressBar.setValue( 0 ) self.partProgressBar.setVisible( False ) def keyPressEvent( self, e ): ''' Reimplemented key press event: ''' if ( e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.MetaModifier ) and e.key() == Qt.Key_C: #selection = self.tblUnique.selectedItems() items = "" if self.myFunction in ( 1, 2 ): for rec in range( self.tblUnique.rowCount() ): items += self.tblUnique.item( rec, 0 ).text() + "\n" else: for rec in range( self.tblUnique.rowCount() ): items += self.tblUnique.item( rec, 0 ).text() + ":" + self.tblUnique.item( rec, 1 ).text() + "\n" if items: clip_board = QApplication.clipboard() clip_board.setText( items ) else: QDialog.keyPressEvent( self, e ) def update( self ): self.cmbField.clear() inputLayer = unicode( self.inShape.currentText() ) if inputLayer != "": changedLayer = ftools_utils.getVectorLayerByName( inputLayer ) changedField = changedLayer.dataProvider().fields() # for Basic statistics (with or without selection) if self.myFunction == 3: if changedLayer.selectedFeatureCount() != 0: self.useSelected.setCheckState( Qt.Checked ) else: self.useSelected.setCheckState( Qt.Unchecked ) # add all fields in combobox because now we can work with text fields too for f in changedField: self.cmbField.addItem( unicode( f.name() ) ) 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.visual( self.inShape.currentText(), self.cmbField.currentText(), self.useSelected.checkState() ) def manageGui( self ): 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" ) ) self.useSelected.setVisible( False ) elif self.myFunction == 3: # Basic statistics self.setWindowTitle( self.tr( "Basics statistics" ) ) self.label_2.setText( self.tr( "Statistics output" ) ) self.label_4.setVisible( False ) self.lstCount.setVisible( False ) self.resize( 381, 400 ) elif self.myFunction == 4: # Nearest neighbour analysis self.setWindowTitle( self.tr( "Nearest neighbour analysis" ) ) self.cmbField.setVisible( False ) self.label.setVisible( False ) self.useSelected.setVisible( False ) self.label_2.setText( self.tr( "Nearest neighbour statistics" ) ) self.label_4.setVisible( False ) self.lstCount.setVisible( False ) self.resize( 381, 200 ) self.inShape.clear() 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 (disabled) #2: List unique values #3: Basic statistics #4: Nearest neighbour analysis def visual( self, myLayer, myField, mySelection ): vlayer = ftools_utils.getVectorLayerByName( myLayer ) self.tblUnique.clearContents() self.tblUnique.setRowCount( 0 ) self.lstCount.clear() self.buttonOk.setEnabled( False ) self.testThread = visualThread( self.iface.mainWindow(), self, self.myFunction, vlayer, myField, 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 ) QObject.connect( self.testThread, SIGNAL( "runPartRange(PyQt_PyObject)" ), self.runPartRangeFromThread ) QObject.connect( self.testThread, SIGNAL( "runPartStatus(PyQt_PyObject)" ), self.runPartStatusFromThread ) 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 ) result = output[ 0 ] numRows = len( result ) self.tblUnique.setRowCount( numRows ) if self.myFunction in ( 1, 2 ): self.tblUnique.setColumnCount( 1 ) for rec in range( numRows ): item = QTableWidgetItem( result[ rec ] ) self.tblUnique.setItem( rec, 0, item ) else: self.tblUnique.setColumnCount( 2 ) for rec in range( numRows ): tmp = result[ rec ].split( ":" ) item = QTableWidgetItem( tmp[ 0 ] ) self.tblUnique.setItem( rec, 0, item ) item = QTableWidgetItem( tmp[ 1 ] ) self.tblUnique.setItem( rec, 1, item ) self.tblUnique.setHorizontalHeaderLabels( [ self.tr("Parameter"), self.tr("Value") ] ) self.tblUnique.horizontalHeader().setResizeMode( 1, QHeaderView.ResizeToContents ) self.tblUnique.horizontalHeader().show() self.tblUnique.horizontalHeader().setResizeMode( 0, QHeaderView.Stretch ) self.tblUnique.resizeRowsToContents() self.lstCount.insert( unicode( output[ 1 ] ) ) 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 ] ) def runPartStatusFromThread( self, status ): self.partProgressBar.setValue( status ) if status >= self.part_max: self.partProgressBar.setVisible( False ) def runPartRangeFromThread( self, range_vals ): self.part_max = range_vals[ 1 ] self.partProgressBar.setVisible( True ) self.partProgressBar.setRange( range_vals[ 0 ], range_vals[ 1 ] ) class visualThread( QThread ): def __init__( self, parentThread, parentObject, function, vlayer, myField, mySelection ): QThread.__init__( self, parentThread ) self.parent = parentObject self.running = False self.myFunction = function self.vlayer = vlayer self.myField = myField self.mySelection = mySelection def run( self ): self.running = True # 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 ) elif self.myFunction == 4: # Nearest neighbour analysis ( lst, cnt ) = self.nearest_neighbour_analysis( self.vlayer ) self.emit( SIGNAL( "runFinished(PyQt_PyObject)" ), ( lst, cnt ) ) self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 ) def stop(self): self.running = False def list_unique_values( self, vlayer, myField ): vprovider = vlayer.dataProvider() index = vprovider.fieldNameIndex( myField ) unique = ftools_utils.getUniqueValues( vprovider, int( index ) ) lstUnique = [] nFeat = len( unique ) nElement = 0 if nFeat > 0: self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 ) self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, nFeat ) ) for item in unique: nElement += 1 self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), nElement ) lstUnique.append(unicode(item).strip()) lstCount = len( unique ) return ( lstUnique, lstCount ) def basic_statistics( self, vlayer, myField ): vprovider = vlayer.dataProvider() index = vprovider.fieldNameIndex( myField ) feat = QgsFeature() sumVal = 0.0 meanVal = 0.0 nVal = 0.0 values = [] first = True nElement = 0 # determine selected field type if ftools_utils.getFieldType( vlayer, myField ) in ( 'String', 'varchar', 'char', 'text'): fillVal = 0 emptyVal = 0 if self.mySelection: # only selected features selection = vlayer.selectedFeatures() nFeat = vlayer.selectedFeatureCount() if nFeat > 0: self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 ) self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, nFeat ) ) for f in selection: try: lenVal = float( len( f[ index ] ) ) except TypeError: lenVal = 0 if first: minVal = lenVal maxVal = lenVal first = False else: if lenVal < minVal: minVal = lenVal if lenVal > maxVal: maxVal = lenVal if lenVal != 0.00: fillVal += 1 else: emptyVal += 1 values.append( lenVal ) sumVal = sumVal + lenVal nElement += 1 self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), nElement ) else: # there is no selection, process the whole layer nFeat = vprovider.featureCount() if nFeat > 0: self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 ) self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, nFeat ) ) fit = vprovider.getFeatures() while fit.nextFeature( feat ): try: lenVal = float( len( feat[ index ] ) ) except TypeError: lenVal = 0 if first: minVal = lenVal maxVal = lenVal first = False else: if lenVal < minVal: minVal = lenVal if lenVal > maxVal: maxVal = lenVal if lenVal != 0.00: fillVal += 1 else: emptyVal += 1 values.append( lenVal ) sumVal = sumVal + lenVal nElement += 1 self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), nElement ) nVal= float( len( values ) ) if nVal > 0: meanVal = sumVal / nVal lstStats = [] lstStats.append( self.tr( "Max. len:" ) + unicode( maxVal ) ) lstStats.append( self.tr( "Min. len:" ) + unicode( minVal ) ) lstStats.append( self.tr( "Mean. len:" ) + unicode( meanVal ) ) lstStats.append( self.tr( "Filled:" ) + unicode( fillVal ) ) lstStats.append( self.tr( "Empty:" ) + unicode( emptyVal ) ) lstStats.append( self.tr( "N:" ) + unicode( nVal ) ) return ( lstStats, [] ) else: return ( ["Error:No features selected!"], [] ) else: # numeric field stdVal = 0.00 cvVal = 0.00 rangeVal = 0.00 medianVal = 0.00 maxVal = 0.00 minVal = 0.00 if self.mySelection: # only selected features selection = vlayer.selectedFeatures() nFeat = vlayer.selectedFeatureCount() uniqueVal = ftools_utils.getUniqueValuesCount( vlayer, index, True ) if nFeat > 0: self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 ) self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, nFeat ) ) for f in selection: value = float( f[ index ] ) if first: minVal = value maxVal = value first = False else: if value < minVal: minVal = value if value > maxVal: maxVal = value values.append( value ) sumVal = sumVal + value nElement += 1 self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), nElement ) else: # there is no selection, process the whole layer nFeat = vprovider.featureCount() uniqueVal = ftools_utils.getUniqueValuesCount( vlayer, index, False ) if nFeat > 0: self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 ) self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, nFeat ) ) fit = vprovider.getFeatures() while fit.nextFeature( feat ): value = float( feat[ index ] ) if first: minVal = value maxVal = value first = False else: if value < minVal: minVal = value if value > maxVal: maxVal = value values.append( value ) sumVal = sumVal + value nElement += 1 self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), nElement ) nVal= float( len( values ) ) if nVal > 0.00: rangeVal = maxVal - minVal meanVal = sumVal / nVal if meanVal != 0.00: for val in values: stdVal += ( ( val - meanVal ) * ( val - meanVal ) ) stdVal = math.sqrt( stdVal / nVal ) cvVal = stdVal / meanVal if nVal > 1: lstVal = values lstVal.sort() if ( nVal % 2 ) == 0: medianVal = 0.5 * ( lstVal[ int( ( nVal - 1 ) / 2 ) ] + lstVal[ int( ( nVal ) / 2 ) ] ) else: medianVal = lstVal[ int( ( nVal + 1 ) / 2 - 1 ) ] lstStats = [] lstStats.append( self.tr( "Mean:" ) + unicode( meanVal ) ) lstStats.append( self.tr( "StdDev:" ) + unicode( stdVal ) ) lstStats.append( self.tr( "Sum:" ) + unicode( sumVal) ) lstStats.append( self.tr( "Min:" ) + unicode( minVal ) ) lstStats.append( self.tr( "Max:" ) + unicode( maxVal ) ) lstStats.append( self.tr( "N:" ) + unicode( nVal ) ) lstStats.append( self.tr( "CV:" ) + unicode( cvVal ) ) lstStats.append( self.tr( "Number of unique values:" ) + unicode( uniqueVal ) ) lstStats.append( self.tr( "Range:" ) + unicode( rangeVal ) ) lstStats.append( self.tr( "Median:" ) + unicode( medianVal ) ) return ( lstStats, [] ) else: return ( ["Error:No features selected!"], [] ) def nearest_neighbour_analysis( self, vlayer ): vprovider = vlayer.dataProvider() sumDist = 0.00 distance = QgsDistanceArea() A = vlayer.extent() A = float( A.width() * A.height() ) index = ftools_utils.createIndex( vprovider ) nFeat = vprovider.featureCount() nElement = 0 if nFeat > 0: self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), 0 ) self.emit( SIGNAL( "runRange(PyQt_PyObject)" ), ( 0, nFeat ) ) feat = QgsFeature() neighbour = QgsFeature() fit = vprovider.getFeatures() while fit.nextFeature( feat ): neighbourID = index.nearestNeighbor( feat.geometry().asPoint(), 2 )[ 1 ] vprovider.getFeatures( QgsFeatureRequest().setFilterFid( neighbourID ).setSubsetOfAttributes( [] ) ).nextFeature( neighbour ) nearDist = distance.measureLine( neighbour.geometry().asPoint(), feat.geometry().asPoint() ) sumDist += nearDist nElement += 1 self.emit( SIGNAL( "runStatus(PyQt_PyObject)" ), nElement ) nVal = vprovider.featureCount() do = float( sumDist) / nVal de = float( 0.5 / math.sqrt( nVal / A ) ) d = float( do / de ) SE = float( 0.26136 / math.sqrt( ( nVal * nVal ) / A ) ) zscore = float( ( do - de ) / SE ) lstStats = [] lstStats.append( self.tr( "Observed mean distance:" ) + unicode( do ) ) lstStats.append( self.tr( "Expected mean distance:" ) + unicode( de ) ) lstStats.append( self.tr( "Nearest neighbour index:" ) + unicode( d ) ) lstStats.append( self.tr( "N:" ) + unicode( nVal ) ) lstStats.append( self.tr( "Z-Score:" ) + unicode( zscore ) ) return ( lstStats, [] )