Skip to content

Commit ebc2e6f

Browse files
author
cfarmer
committedMar 8, 2010
Updates distance matrix tool to allow non ascii characters in id fields. This is based on a workaround (from http://docs.python.org/library/csv.html), as the default Python csv function(s) do not support nonascii characters. Fixes #2496
git-svn-id: http://svn.osgeo.org/qgis/trunk@13019 c8812cc2-4d05-0410-92ff-de0c093fc19c
1 parent e41bb34 commit ebc2e6f

File tree

1 file changed

+230
-194
lines changed

1 file changed

+230
-194
lines changed
 
Lines changed: 230 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
#-----------------------------------------------------------
1+
# -*- coding: utf-8 -*-
2+
#-----------------------------------------------------------
23
#
34
# Create Point Distance Matrix
45
#
@@ -37,198 +38,233 @@
3738

3839
from qgis.core import *
3940
from ui_frmPointDistance import Ui_Dialog
40-
import csv
41+
import csv, codecs, cStringIO
4142
from math import *
43+
44+
class UnicodeWriter:
45+
"""
46+
A CSV writer which will write rows to CSV file "f",
47+
which is encoded in the given encoding.
48+
Taken from http://docs.python.org/library/csv.html
49+
to allow handling of nonascii output
50+
"""
51+
52+
def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
53+
# Redirect output to a queue
54+
self.queue = cStringIO.StringIO()
55+
self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
56+
self.stream = f
57+
self.encoder = codecs.getincrementalencoder(encoding)()
58+
59+
def writerow(self, row):
60+
try:
61+
self.writer.writerow([s.encode("utf-8") for s in row])
62+
except:
63+
self.writer.writerow(row)
64+
# Fetch UTF-8 output from the queue ...
65+
data = self.queue.getvalue()
66+
data = data.decode("utf-8")
67+
# ... and reencode it into the target encoding
68+
data = self.encoder.encode(data)
69+
# write to the target stream
70+
self.stream.write(data)
71+
# empty queue
72+
self.queue.truncate(0)
73+
74+
def writerows(self, rows):
75+
for row in rows:
76+
self.writerow(row)
77+
4278
class Dialog(QDialog, Ui_Dialog):
43-
def __init__(self, iface):
44-
QDialog.__init__(self)
45-
self.iface = iface
46-
# Set up the user interface from Designer.
47-
self.setupUi(self)
48-
QObject.connect(self.btnFile, SIGNAL("clicked()"), self.saveFile)
49-
QObject.connect(self.inPoint1, SIGNAL("currentIndexChanged(QString)"), self.update1)
50-
QObject.connect(self.inPoint2, SIGNAL("currentIndexChanged(QString)"), self.update2)
51-
# populate layer list
52-
self.setWindowTitle(self.tr("Distance matrix"))
53-
self.progressBar.setValue(0)
54-
mapCanvas = self.iface.mapCanvas()
55-
for i in range(mapCanvas.layerCount()):
56-
layer = mapCanvas.layer(i)
57-
if layer.type() == layer.VectorLayer:
58-
if layer.geometryType() == QGis.Point:
59-
self.inPoint1.addItem(layer.name())
60-
self.inPoint2.addItem(layer.name())
61-
62-
def update1(self, inputLayer):
63-
changedLayer = self.getVectorLayerByName(unicode(inputLayer))
64-
changedField = self.getFieldList(changedLayer)
65-
for i in changedField:
66-
if changedField[i].type() == QVariant.Int or changedField[i].type() == QVariant.String:
67-
self.inField1.addItem(unicode(changedField[i].name()))
68-
69-
def update2(self, inputLayer):
70-
changedLayer = self.getVectorLayerByName(unicode(inputLayer))
71-
changedField = self.getFieldList(changedLayer)
72-
for i in changedField:
73-
if changedField[i].type() == QVariant.Int or changedField[i].type() == QVariant.String:
74-
self.inField2.addItem(unicode(changedField[i].name()))
75-
76-
def accept(self):
77-
if self.inPoint1.currentText() == "":
78-
QMessageBox.information(self, self.tr("Create Point Distance Matrix"), self.tr("Please specify input point layer"))
79-
elif self.outFile.text() == "":
80-
QMessageBox.information(self, self.tr("Create Point Distance Matrix"), self.tr("Please specify output file"))
81-
elif self.inPoint2.currentText() == "":
82-
QMessageBox.information(self, self.tr("Create Point Distance Matrix"), self.tr("Please specify target point layer"))
83-
elif self.inField1.currentText() == "":
84-
QMessageBox.information(self, self.tr("Create Point Distance Matrix"), self.tr("Please specify input unique ID field"))
85-
elif self.inField2.currentText() == "":
86-
QMessageBox.information(self, self.tr("Create Point Distance Matrix"), self.tr("Please specify target unique ID field"))
87-
else:
88-
point1 = self.inPoint1.currentText()
89-
point2 = self.inPoint2.currentText()
90-
field1 = self.inField1.currentText()
91-
field2 = self.inField2.currentText()
92-
outPath = self.outFile.text()
93-
if self.rdoLinear.isChecked(): matType = "Linear"
94-
elif self.rdoStandard.isChecked(): matType = "Standard"
95-
else: matType = "Summary"
96-
if self.chkNearest.isChecked(): nearest = self.spnNearest.value()
97-
else: nearest = nearest = 0
98-
if outPath.contains("\\"):
99-
outName = outPath.right((outPath.length() - outPath.lastIndexOf("\\")) - 1)
100-
else:
101-
outName = outPath.right((outPath.length() - outPath.lastIndexOf("/")) - 1)
102-
if outName.endsWith(".csv"):
103-
outName = outName.left(outName.length() - 4)
104-
self.outFile.clear()
105-
self.compute(point1, point2, field1, field2, outPath, matType, nearest, self.progressBar)
106-
self.progressBar.setValue(100)
107-
addToTOC = QMessageBox.information(self, "Create Point Distance Matrix", self.tr("Created output matrix:\n") + outPath)
108-
self.progressBar.setValue(0)
109-
110-
def saveFile(self):
111-
self.outFile.clear()
112-
fileDialog = QFileDialog()
113-
outName = fileDialog.getSaveFileName(self, "Output Distance Matrix",".", "Delimited txt file (*.csv)")
114-
fileCheck = QFile(outName)
115-
filePath = QFileInfo(outName).absoluteFilePath()
116-
if filePath.right(4) != ".csv": filePath = filePath + ".csv"
117-
if not outName.isEmpty():
118-
self.outFile.insert(filePath)
119-
120-
def compute(self, line1, line2, field1, field2, outPath, matType, nearest, progressBar):
121-
layer1 = self.getVectorLayerByName(line1)
122-
layer2 = self.getVectorLayerByName(line2)
123-
provider1 = layer1.dataProvider()
124-
provider2 = layer2.dataProvider()
125-
allAttrs = provider1.attributeIndexes()
126-
provider1.select(allAttrs)
127-
allAttrs = provider2.attributeIndexes()
128-
provider2.select(allAttrs)
129-
sindex = QgsSpatialIndex()
130-
inFeat = QgsFeature()
131-
while provider2.nextFeature(inFeat):
132-
sindex.insertFeature(inFeat)
133-
provider2.rewind()
134-
if nearest < 1: nearest = layer2.featureCount()
135-
else: nearest = nearest + 1
136-
index1 = provider1.fieldNameIndex(field1)
137-
index2 = provider2.fieldNameIndex(field2)
138-
sRs = provider1.crs()
139-
distArea = QgsDistanceArea()
140-
#use srs of the first layer (users should ensure that they are both in the same projection)
141-
#distArea.setSourceSRS(sRs)
142-
143-
f = open(unicode(outPath), "wb")
144-
writer = csv.writer(f)
145-
if matType <> "Standard":
146-
if matType == "Linear":
147-
writer.writerow(["InputID", "TargetID", "Distance"])
148-
else:
149-
writer.writerow(["InputID", "MEAN", "STDDEV", "MIN", "MAX"])
150-
self.linearMatrix(writer, provider1, provider2, index1, index2, nearest, distArea, matType, sindex, progressBar)
151-
else:
152-
self.regularMatrix(writer, provider1, provider2, index1, index2, nearest, distArea, sindex, progressBar)
153-
f.close()
154-
155-
def regularMatrix(self, writer, provider1, provider2, index1, index2, nearest, distArea, sindex, progressBar):
156-
inFeat = QgsFeature()
157-
outFeat = QgsFeature()
158-
inGeom = QgsGeometry()
159-
outGeom = QgsGeometry()
160-
first = True
161-
start = 15.00
162-
add = 85.00 / provider1.featureCount()
163-
while provider1.nextFeature(inFeat):
164-
inGeom = inFeat.geometry()
165-
inID = inFeat.attributeMap()[index1].toString()
166-
if first:
167-
featList = sindex.nearestNeighbor(inGeom.asPoint(), nearest)
168-
first = False
169-
data = ["ID"]
170-
for i in featList:
171-
provider2.featureAtId(int(i), outFeat, True, [index2])
172-
data.append(unicode(outFeat.attributeMap()[index2].toString()))
173-
writer.writerow(data)
174-
data = [unicode(inID)]
175-
for j in featList:
176-
provider2.featureAtId(int(j), outFeat, True)
177-
outGeom = outFeat.geometry()
178-
dist = distArea.measureLine(inGeom.asPoint(), outGeom.asPoint())
179-
data.append(float(dist))
180-
writer.writerow(data)
181-
start = start + add
182-
progressBar.setValue(start)
183-
del writer
184-
185-
def linearMatrix(self, writer, provider1, provider2, index1, index2, nearest, distArea, matType, sindex, progressBar):
186-
inFeat = QgsFeature()
187-
outFeat = QgsFeature()
188-
inGeom = QgsGeometry()
189-
outGeom = QgsGeometry()
190-
start = 15.00
191-
add = 85.00 / provider1.featureCount()
192-
while provider1.nextFeature(inFeat):
193-
inGeom = inFeat.geometry()
194-
inID = inFeat.attributeMap()[index1].toString()
195-
featList = sindex.nearestNeighbor(inGeom.asPoint(), nearest)
196-
distList = []
197-
vari = 0.00
198-
for i in featList:
199-
provider2.featureAtId(int(i), outFeat, True, [index2])
200-
outID = outFeat.attributeMap()[index2].toString()
201-
outGeom = outFeat.geometry()
202-
dist = distArea.measureLine(inGeom.asPoint(), outGeom.asPoint())
203-
if dist > 0:
204-
if matType == "Linear": writer.writerow([unicode(inID), unicode(outID), float(dist)])
205-
else: distList.append(float(dist))
206-
if matType == "Summary":
207-
mean = sum(distList) / len(distList)
208-
for i in distList:
209-
vari = vari + ((i - mean)*(i - mean))
210-
vari = sqrt(vari / len(distList))
211-
writer.writerow([unicode(inID), float(mean), float(vari), float(min(distList)), float(max(distList))])
212-
start = start + add
213-
progressBar.setValue(start)
214-
del writer
215-
216-
def getVectorLayerByName(self, myName):
217-
mc = self.iface.mapCanvas()
218-
nLayers = mc.layerCount()
219-
for l in range(nLayers):
220-
layer = mc.layer(l)
221-
if layer.name() == unicode(myName):
222-
vlayer = QgsVectorLayer(unicode(layer.source()), unicode(myName), unicode(layer.dataProvider().name()))
223-
if vlayer.isValid():
224-
return vlayer
225-
else:
226-
QMessageBox.information(self, self.tr("Locate Line Intersections"), self.tr("Vector layer is not valid"))
227-
228-
def getFieldList(self, vlayer):
229-
fProvider = vlayer.dataProvider()
230-
feat = QgsFeature()
231-
allAttrs = fProvider.attributeIndexes()
232-
fProvider.select(allAttrs)
233-
myFields = fProvider.fields()
234-
return myFields
79+
def __init__(self, iface):
80+
QDialog.__init__(self)
81+
self.iface = iface
82+
# Set up the user interface from Designer.
83+
self.setupUi(self)
84+
QObject.connect(self.btnFile, SIGNAL("clicked()"), self.saveFile)
85+
QObject.connect(self.inPoint1, SIGNAL("currentIndexChanged(QString)"), self.update1)
86+
QObject.connect(self.inPoint2, SIGNAL("currentIndexChanged(QString)"), self.update2)
87+
# populate layer list
88+
self.setWindowTitle(self.tr("Distance matrix"))
89+
self.progressBar.setValue(0)
90+
mapCanvas = self.iface.mapCanvas()
91+
for i in range(mapCanvas.layerCount()):
92+
layer = mapCanvas.layer(i)
93+
if layer.type() == layer.VectorLayer:
94+
if layer.geometryType() == QGis.Point:
95+
self.inPoint1.addItem(layer.name())
96+
self.inPoint2.addItem(layer.name())
97+
98+
def update1(self, inputLayer):
99+
changedLayer = self.getVectorLayerByName(unicode(inputLayer))
100+
changedField = self.getFieldList(changedLayer)
101+
for i in changedField:
102+
if changedField[i].type() == QVariant.Int or changedField[i].type() == QVariant.String:
103+
self.inField1.addItem(unicode(changedField[i].name()))
104+
105+
def update2(self, inputLayer):
106+
changedLayer = self.getVectorLayerByName(unicode(inputLayer))
107+
changedField = self.getFieldList(changedLayer)
108+
for i in changedField:
109+
if changedField[i].type() == QVariant.Int or changedField[i].type() == QVariant.String:
110+
self.inField2.addItem(unicode(changedField[i].name()))
111+
112+
def accept(self):
113+
if self.inPoint1.currentText() == "":
114+
QMessageBox.information(self, self.tr("Create Point Distance Matrix"), self.tr("Please specify input point layer"))
115+
elif self.outFile.text() == "":
116+
QMessageBox.information(self, self.tr("Create Point Distance Matrix"), self.tr("Please specify output file"))
117+
elif self.inPoint2.currentText() == "":
118+
QMessageBox.information(self, self.tr("Create Point Distance Matrix"), self.tr("Please specify target point layer"))
119+
elif self.inField1.currentText() == "":
120+
QMessageBox.information(self, self.tr("Create Point Distance Matrix"), self.tr("Please specify input unique ID field"))
121+
elif self.inField2.currentText() == "":
122+
QMessageBox.information(self, self.tr("Create Point Distance Matrix"), self.tr("Please specify target unique ID field"))
123+
else:
124+
point1 = self.inPoint1.currentText()
125+
point2 = self.inPoint2.currentText()
126+
field1 = self.inField1.currentText()
127+
field2 = self.inField2.currentText()
128+
outPath = self.outFile.text()
129+
if self.rdoLinear.isChecked(): matType = "Linear"
130+
elif self.rdoStandard.isChecked(): matType = "Standard"
131+
else: matType = "Summary"
132+
if self.chkNearest.isChecked(): nearest = self.spnNearest.value()
133+
else: nearest = nearest = 0
134+
if outPath.contains("\\"):
135+
outName = outPath.right((outPath.length() - outPath.lastIndexOf("\\")) - 1)
136+
else:
137+
outName = outPath.right((outPath.length() - outPath.lastIndexOf("/")) - 1)
138+
if outName.endsWith(".csv"):
139+
outName = outName.left(outName.length() - 4)
140+
self.outFile.clear()
141+
self.compute(point1, point2, field1, field2, outPath, matType, nearest, self.progressBar)
142+
self.progressBar.setValue(100)
143+
addToTOC = QMessageBox.information(self, "Create Point Distance Matrix", self.tr("Created output matrix:\n") + outPath)
144+
self.progressBar.setValue(0)
145+
146+
def saveFile(self):
147+
self.outFile.clear()
148+
fileDialog = QFileDialog()
149+
outName = fileDialog.getSaveFileName(self, "Output Distance Matrix",".", "Delimited txt file (*.csv)")
150+
fileCheck = QFile(outName)
151+
filePath = QFileInfo(outName).absoluteFilePath()
152+
if filePath.right(4) != ".csv": filePath = filePath + ".csv"
153+
if not outName.isEmpty():
154+
self.outFile.insert(filePath)
155+
156+
def compute(self, line1, line2, field1, field2, outPath, matType, nearest, progressBar):
157+
layer1 = self.getVectorLayerByName(line1)
158+
layer2 = self.getVectorLayerByName(line2)
159+
provider1 = layer1.dataProvider()
160+
provider2 = layer2.dataProvider()
161+
allAttrs = provider1.attributeIndexes()
162+
provider1.select(allAttrs)
163+
allAttrs = provider2.attributeIndexes()
164+
provider2.select(allAttrs)
165+
sindex = QgsSpatialIndex()
166+
inFeat = QgsFeature()
167+
while provider2.nextFeature(inFeat):
168+
sindex.insertFeature(inFeat)
169+
provider2.rewind()
170+
if nearest < 1: nearest = layer2.featureCount()
171+
else: nearest = nearest + 1
172+
index1 = provider1.fieldNameIndex(field1)
173+
index2 = provider2.fieldNameIndex(field2)
174+
sRs = provider1.crs()
175+
distArea = QgsDistanceArea()
176+
#use srs of the first layer (users should ensure that they are both in the same projection)
177+
#distArea.setSourceSRS(sRs)
178+
179+
f = open(unicode(outPath), "wb")
180+
writer = UnicodeWriter(f)
181+
if matType <> "Standard":
182+
if matType == "Linear":
183+
writer.writerow(["InputID", "TargetID", "Distance"])
184+
else:
185+
writer.writerow(["InputID", "MEAN", "STDDEV", "MIN", "MAX"])
186+
self.linearMatrix(writer, provider1, provider2, index1, index2, nearest, distArea, matType, sindex, progressBar)
187+
else:
188+
self.regularMatrix(writer, provider1, provider2, index1, index2, nearest, distArea, sindex, progressBar)
189+
f.close()
190+
191+
def regularMatrix(self, writer, provider1, provider2, index1, index2, nearest, distArea, sindex, progressBar):
192+
inFeat = QgsFeature()
193+
outFeat = QgsFeature()
194+
inGeom = QgsGeometry()
195+
outGeom = QgsGeometry()
196+
first = True
197+
start = 15.00
198+
add = 85.00 / provider1.featureCount()
199+
while provider1.nextFeature(inFeat):
200+
inGeom = inFeat.geometry()
201+
inID = inFeat.attributeMap()[index1].toString()
202+
if first:
203+
featList = sindex.nearestNeighbor(inGeom.asPoint(), nearest)
204+
first = False
205+
data = ["ID"]
206+
for i in featList:
207+
provider2.featureAtId(int(i), outFeat, True, [index2])
208+
data.append(unicode(outFeat.attributeMap()[index2].toString()))
209+
writer.writerow(data)
210+
data = [unicode(inID)]
211+
for j in featList:
212+
provider2.featureAtId(int(j), outFeat, True)
213+
outGeom = outFeat.geometry()
214+
dist = distArea.measureLine(inGeom.asPoint(), outGeom.asPoint())
215+
data.append(str(float(dist)))
216+
writer.writerow(data)
217+
start = start + add
218+
progressBar.setValue(start)
219+
del writer
220+
221+
def linearMatrix(self, writer, provider1, provider2, index1, index2, nearest, distArea, matType, sindex, progressBar):
222+
inFeat = QgsFeature()
223+
outFeat = QgsFeature()
224+
inGeom = QgsGeometry()
225+
outGeom = QgsGeometry()
226+
start = 15.00
227+
add = 85.00 / provider1.featureCount()
228+
while provider1.nextFeature(inFeat):
229+
inGeom = inFeat.geometry()
230+
inID = inFeat.attributeMap()[index1].toString()
231+
featList = sindex.nearestNeighbor(inGeom.asPoint(), nearest)
232+
distList = []
233+
vari = 0.00
234+
for i in featList:
235+
provider2.featureAtId(int(i), outFeat, True, [index2])
236+
outID = outFeat.attributeMap()[index2].toString()
237+
outGeom = outFeat.geometry()
238+
dist = distArea.measureLine(inGeom.asPoint(), outGeom.asPoint())
239+
if dist > 0:
240+
if matType == "Linear": writer.writerow([unicode(inID), unicode(outID), float(dist)])
241+
else: distList.append(float(dist))
242+
if matType == "Summary":
243+
mean = sum(distList) / len(distList)
244+
for i in distList:
245+
vari = vari + ((i - mean)*(i - mean))
246+
vari = sqrt(vari / len(distList))
247+
writer.writerow([unicode(inID), float(mean), float(vari), float(min(distList)), float(max(distList))])
248+
start = start + add
249+
progressBar.setValue(start)
250+
del writer
251+
252+
def getVectorLayerByName(self, myName):
253+
mc = self.iface.mapCanvas()
254+
nLayers = mc.layerCount()
255+
for l in range(nLayers):
256+
layer = mc.layer(l)
257+
if layer.name() == unicode(myName):
258+
vlayer = QgsVectorLayer(unicode(layer.source()), unicode(myName), unicode(layer.dataProvider().name()))
259+
if vlayer.isValid():
260+
return vlayer
261+
else:
262+
QMessageBox.information(self, self.tr("Locate Line Intersections"), self.tr("Vector layer is not valid"))
263+
264+
def getFieldList(self, vlayer):
265+
fProvider = vlayer.dataProvider()
266+
feat = QgsFeature()
267+
allAttrs = fProvider.attributeIndexes()
268+
fProvider.select(allAttrs)
269+
myFields = fProvider.fields()
270+
return myFields

0 commit comments

Comments
 (0)
Please sign in to comment.