mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
444 lines
18 KiB
Python
444 lines
18 KiB
Python
# -*- 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.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.append( self.tblUnique.item( rec, 0 ).text() + "\n" )
|
|
else:
|
|
for rec in range( self.tblUnique.rowCount() ):
|
|
items.append( self.tblUnique.item( rec, 0 ).text() + ":" + self.tblUnique.item( rec, 1 ).text() + "\n" )
|
|
if not items.isEmpty():
|
|
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(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:
|
|
atMap = f.attributes()
|
|
lenVal = float( len( atMap[ index ] ) )
|
|
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 ):
|
|
atMap = feat.attributes()
|
|
lenVal = float( len( atMap[ index ] ) )
|
|
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:
|
|
atMap = f.attributes()
|
|
value = float( atMap[ 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 ):
|
|
atMap = feat.attributes()
|
|
value = float( atMap[ 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 )
|
|
vprovider.rewind()
|
|
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, [] )
|