﻿#-----------------------------------------------------------
# 
# Create Point Distance Matrix
#
# A QGIS plugin for measuring distances between points. Measure 
# distances between two point layers, and output the results
# as: a) Standard distance matrix, b) Linear distance matrix
# or c) Summary distance matrix
#
# Copyright (C) 2008  Carson Farmer
#
# EMAIL: carson.farmer (at) gmail.com
# WEB  : www.geog.uvic.ca/spar/carson
#
#-----------------------------------------------------------
# 
# 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_frmPointDistance import Ui_Dialog
import csv
from math import *
class Dialog(QDialog, Ui_Dialog):
	def __init__(self, iface):
		QDialog.__init__(self)
		self.iface = iface
		# Set up the user interface from Designer.
		self.setupUi(self)
		QObject.connect(self.btnFile, SIGNAL("clicked()"), self.saveFile)
		QObject.connect(self.inPoint1, SIGNAL("currentIndexChanged(QString)"), self.update1)
		QObject.connect(self.inPoint2, SIGNAL("currentIndexChanged(QString)"), self.update2)
		# populate layer list
		self.setWindowTitle(self.tr("Distance matrix"))
		self.progressBar.setValue(0)
		mapCanvas = self.iface.mapCanvas()
		for i in range(mapCanvas.layerCount()):
			layer = mapCanvas.layer(i)
			if layer.type() == layer.VectorLayer:
				if layer.geometryType() == QGis.Point:
					self.inPoint1.addItem(layer.name())
					self.inPoint2.addItem(layer.name())

	def update1(self, inputLayer):
		changedLayer = self.getVectorLayerByName(unicode(inputLayer))
		changedField = self.getFieldList(changedLayer)
		for i in changedField:
			if changedField[i].type() == QVariant.Int or changedField[i].type() == QVariant.String:
				self.inField1.addItem(unicode(changedField[i].name()))

	def update2(self, inputLayer):
		changedLayer = self.getVectorLayerByName(unicode(inputLayer))
		changedField = self.getFieldList(changedLayer)
		for i in changedField:
			if changedField[i].type() == QVariant.Int or changedField[i].type() == QVariant.String:
				self.inField2.addItem(unicode(changedField[i].name()))

	def accept(self):
		if self.inPoint1.currentText() == "":
			QMessageBox.information(self, "Create Point Distance Matrix", "Please specify input point layer")
		elif self.outFile.text() == "":
			QMessageBox.information(self, "Create Point Distance Matrix", "Please specify output file")
		elif self.inPoint2.currentText() == "":
			QMessageBox.information(self, "Create Point Distance Matrix", "Please specify target point layer")
		elif self.inField1.currentText() == "":
			QMessageBox.information(self, "Create Point Distance Matrix", "Please specify input unique ID field")
		elif self.inField2.currentText() == "":
			QMessageBox.information(self, "Create Point Distance Matrix", "Please specify target unique ID field")
		else:
			point1 = self.inPoint1.currentText()
			point2 = self.inPoint2.currentText()
			field1 = self.inField1.currentText()
			field2 = self.inField2.currentText()
			outPath = self.outFile.text()
			if self.rdoLinear.isChecked(): matType = "Linear"
			elif self.rdoStandard.isChecked(): matType = "Standard"
			else: matType = "Summary"
			if self.chkNearest.isChecked(): nearest = self.spnNearest.value()
			else: nearest = nearest = 0
			if outPath.contains("\\"):
				outName = outPath.right((outPath.length() - outPath.lastIndexOf("\\")) - 1)
			else:
				outName = outPath.right((outPath.length() - outPath.lastIndexOf("/")) - 1)
			if outName.endsWith(".csv"):
				outName = outName.left(outName.length() - 4)
			self.outFile.clear()
			self.compute(point1, point2, field1, field2, outPath, matType, nearest, self.progressBar)
			self.progressBar.setValue(100)
			addToTOC = QMessageBox.information(self, "Create Point Distance Matrix", self.tr("Created output matrix:\n") + outPath)
		self.progressBar.setValue(0)

	def saveFile(self):
		self.outFile.clear()
		fileDialog = QFileDialog()
		outName = fileDialog.getSaveFileName(self, "Output Distance Matrix",".", "Delimited txt file (*.csv)")
		fileCheck = QFile(outName)
		filePath = QFileInfo(outName).absoluteFilePath()
		if filePath.right(4) != ".csv": filePath = filePath + ".csv"
		if not outName.isEmpty():
			self.outFile.insert(filePath)

	def compute(self, line1, line2, field1, field2, outPath, matType, nearest, progressBar):
		layer1 = self.getVectorLayerByName(line1)
		layer2 = self.getVectorLayerByName(line2)
		provider1 = layer1.dataProvider()
		provider2 = layer2.dataProvider()
		allAttrs = provider1.attributeIndexes()
		provider1.select(allAttrs)
		allAttrs = provider2.attributeIndexes()
		provider2.select(allAttrs)
		sindex = QgsSpatialIndex()
		inFeat = QgsFeature()
		while provider2.nextFeature(inFeat):
			sindex.insertFeature(inFeat)
		provider2.rewind()
		if nearest < 1: nearest = layer2.featureCount()
		else: nearest = nearest + 1
		index1 = provider1.fieldNameIndex(field1)
		index2 = provider2.fieldNameIndex(field2)
		sRs = provider1.crs()
		distArea = QgsDistanceArea()
		#use srs of the first layer (users should ensure that they are both in the same projection)
		#distArea.setSourceSRS(sRs)

		f = open(unicode(outPath), "wb")
		writer = csv.writer(f)
		if matType <> "Standard":
			if matType == "Linear":
				writer.writerow(["InputID", "TargetID", "Distance"])
			else:
				writer.writerow(["InputID", "MEAN", "STDDEV", "MIN", "MAX"])
			self.linearMatrix(writer, provider1, provider2, index1, index2, nearest, distArea, matType, sindex, progressBar)
		else:
			self.regularMatrix(writer, provider1, provider2, index1, index2, nearest, distArea, sindex, progressBar)
		f.close()

	def regularMatrix(self, writer, provider1, provider2, index1, index2, nearest, distArea, sindex, progressBar):
		inFeat = QgsFeature()
		outFeat = QgsFeature()
		inGeom = QgsGeometry()
		outGeom = QgsGeometry()
		first = True
		start = 15.00
		add = 85.00 / provider1.featureCount()
		while provider1.nextFeature(inFeat):
			inGeom = inFeat.geometry()
			inID = inFeat.attributeMap()[index1].toString()
			if first:
				featList = sindex.nearestNeighbor(inGeom.asPoint(), nearest)
				first = False
				data = ["ID"]
				for i in featList:
					provider2.featureAtId(int(i), outFeat, True, [index2])
					data.append(unicode(outFeat.attributeMap()[index2].toString()))
				writer.writerow(data)
			data = [unicode(inID)]
			for j in featList:
				provider2.featureAtId(int(j), outFeat, True)
				outGeom = outFeat.geometry()
				dist = distArea.measureLine(inGeom.asPoint(), outGeom.asPoint())
				data.append(float(dist))
			writer.writerow(data)
			start = start + add
			progressBar.setValue(start)
		del writer

	def linearMatrix(self, writer, provider1, provider2, index1, index2, nearest, distArea, matType, sindex, progressBar):
		inFeat = QgsFeature()
		outFeat = QgsFeature()
		inGeom = QgsGeometry()
		outGeom = QgsGeometry()
		start = 15.00
		add = 85.00 / provider1.featureCount()
		while provider1.nextFeature(inFeat):
			inGeom = inFeat.geometry()
			inID = inFeat.attributeMap()[index1].toString()
			featList = sindex.nearestNeighbor(inGeom.asPoint(), nearest)
			distList = []
			vari = 0.00
			for i in featList:
				provider2.featureAtId(int(i), outFeat, True, [index2])
				outID = outFeat.attributeMap()[index2].toString()
				outGeom = outFeat.geometry()
				dist = distArea.measureLine(inGeom.asPoint(), outGeom.asPoint())
				if dist > 0:
					if matType == "Linear": writer.writerow([unicode(inID), unicode(outID), float(dist)])
					else: distList.append(float(dist))
			if matType == "Summary":
				mean = sum(distList) / len(distList)
				for i in distList:
					vari = vari + ((i - mean)*(i - mean))
				vari = sqrt(vari / len(distList))
				writer.writerow([unicode(inID), float(mean), float(vari), float(min(distList)), float(max(distList))])
			start = start + add
			progressBar.setValue(start)
		del writer

	def getVectorLayerByName(self, myName):
		mc = self.iface.mapCanvas()
		nLayers = mc.layerCount()
		for l in range(nLayers):
			layer = mc.layer(l)
			if layer.name() == unicode(myName):
				vlayer = QgsVectorLayer(unicode(layer.source()),  unicode(myName),  unicode(layer.dataProvider().name()))
				if vlayer.isValid():
					return vlayer
				else:
					QMessageBox.information(self, "Locate Line Intersections", "Vector layer is not valid")

	def getFieldList(self, vlayer):
		fProvider = vlayer.dataProvider()
		feat = QgsFeature()
		allAttrs = fProvider.attributeIndexes()
		fProvider.select(allAttrs)
		myFields = fProvider.fields()
		return myFields
