mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-27 00:33:48 -05:00
463 lines
20 KiB
Python
463 lines
20 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 Qt, QObject, SIGNAL, QThread
|
|
from PyQt4.QtGui import QDialog, QApplication, QDialogButtonBox, QMessageBox, QTableWidgetItem, QHeaderView
|
|
from qgis.core import QGis, QgsFeature, QgsDistanceArea, QgsFeatureRequest
|
|
|
|
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):
|
|
if ":" not in result[rec]:
|
|
tmp = result[rec].split(u"\uff1a")
|
|
else:
|
|
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 = sorted(values)
|
|
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, [])
|