-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathMultiDistanceBuffer_engine.py
250 lines (231 loc) · 10.7 KB
/
MultiDistanceBuffer_engine.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# -*- coding: utf-8 -*-
"""
/***************************************************************************
MultiDistanceBuffer_engine
-------------------
begin : 2014-09-04
git sha : $Format:%H$
copyright : (C) 2015-2018 by Håvard Tveite
email : [email protected]
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
"""
# import datetime # Testing... ???
# import time # Testing ???
import math # for beregning av segments to approximate
from qgis.core import QgsVectorLayer, QgsFeature
from qgis.core import QgsField, QgsGeometry
# from qgis.analysis import QgsGeometryAnalyzer
from qgis.PyQt import QtCore
from qgis.PyQt.QtCore import QCoreApplication, QVariant
class Worker(QtCore.QObject):
'''The worker that does the heavy lifting.
/* Leaves temporary files that should be deleted by the caller.
* Removes all attributes of the input layer and adds a distance
* attribute.
*
*/
'''
# Define the signals used to communicate back to the application
progress = QtCore.pyqtSignal(float) # For reporting progress
status = QtCore.pyqtSignal(str) # For reporting status
error = QtCore.pyqtSignal(str) # For reporting errors
# Signal for sending back the result:
finished = QtCore.pyqtSignal(bool, object)
def __init__(self, inputvectorlayer, buffersizes,
outputlayername, selectedonly,
segments, deviation):
"""Initialise.
Arguments:
inputvectorlayer -- (QgsVectorLayer) The base vector
layer for the buffer.
buffersizes -- array of floats, sorted asc.
outputlayername -- Name of the output vector layer.
selectedonly -- (boolean) Should only selected
features be buffered. NOT USED!
segments -- segments to approximate (apply if > 0).
deviation -- maximum deviation (apply if > 0.0 and
not segments > 0).
"""
QtCore.QObject.__init__(self) # Essential!
# Creating instance variables from the parameters
self.inpvl = inputvectorlayer
self.buffersizes = buffersizes
self.outputlayername = outputlayername
# self.selectedonly = selectedonly
# Creating instance variables for the progress bar ++
# Number of elements that have been processed - updated by
# calculate_progress
self.processed = 0
# Current percentage of progress - updated by
# calculate_progress
self.percentage = 0
# Number of buffer sizes - used by calculate_progress for
# the standard method
self.worktodo = len(self.buffersizes)
# The number of elements that is needed to increment the
# progressbar - set early in run()
self.increment = self.worktodo // 1000
# Flag set by kill(), checked in the loop
self.abort = False
# Distance attribute name
self.distAttrName = 'distance'
# Inner distance attribute name
self.innerAttrName = 'inner'
# Options
self.segments = segments
self.deviation = deviation
# end of __init__
# Should @pyqtSlot be used here?
def run(self):
try:
layercopy = self.inpvl
self.inpvl = None # Remove the reference to the layer
if layercopy is None:
self.finished.emit(False, None)
return
pr = layercopy.dataProvider()
# Remove all the existing attributes:
thefields = pr.fields()
if layercopy.attributeList():
while len(layercopy.attributeList()) > 0:
pr.deleteAttributes([0])
layercopy.updateFields()
# Add the distance attributes
pr.addAttributes([QgsField(self.distAttrName, QVariant.Double)])
pr.addAttributes([QgsField(self.innerAttrName, QVariant.Double)])
layercopy.updateFields() # Commit the attribute changes
# Create the memory layer for the results (have to specify a
# CRS in order to avoid the select CRS dialogue)
memresult = QgsVectorLayer('Polygon?crs=EPSG:4326',
self.outputlayername, "memory")
# Set the real CRS
memresult.setCrs(layercopy.crs())
# Add attributes to the memory layer
for distfield in layercopy.dataProvider().fields().toList():
memresult.dataProvider().addAttributes([distfield])
memresult.updateFields()
buffergeomvector = [] # Not used by the "standard" method
# Use feature increment for the non-"standard" methods
if (self.segments > 0 or self.deviation > 0.0):
self.worktodo = (layercopy.featureCount() *
len(self.buffersizes))
# The number of elements that is needed to increment the
# progressbar - set early in run()
self.increment = self.worktodo // 1000
# Do the buffering (order: smallest to largest distances):
j = 0
prevdist = None
for dist in self.buffersizes:
if self.abort is True:
break
self.status.emit(self.tr('Doing buffer distance ') +
str(dist) + '... '
# +str(datetime.datetime.now().strftime('%H:%M:%S.%f'))
)
# Determine which buffer variant to use
if (self.segments > 0):
segments = self.segments
# self.status.emit("Segments")
else:
tolerance = self.deviation
# Calculate the number of segments per quarter circle
segments = 5
if dist != 0.0:
segments = int(math.pi / (4.0 * math.acos(1.0 -
(tolerance / float(abs(dist)))))) + 1
segments = max(segments, 1)
multigeom = QgsGeometry()
# Go through the features and buffer and combine the
# feature geometries
i = 0
for feat in layercopy.getFeatures():
bgeom = feat.geometry().buffer(dist, segments)
if i == 0:
multigeom = bgeom
else:
multigeom = multigeom.combine(bgeom)
i = i + 1
self.calculate_progress()
if self.abort is True:
break
buffergeomvector.append(multigeom)
# Compute the donut and add it to the result dataset
newgeom = None
if j == 0: # Just add the innermost buffer
newgeom = buffergeomvector[j]
else:
# Get the donut by subtracting the inner ring
# from this ring
outergeom = buffergeomvector[j]
innergeom = buffergeomvector[j - 1]
newgeom = outergeom.symDifference(innergeom)
newfeature = QgsFeature()
newfeature.setGeometry(newgeom)
newfeature.setAttributes([dist, prevdist])
memresult.dataProvider().addFeatures([newfeature])
j = j + 1
prevdist = dist
# self.status.emit(self.tr('Finished with buffer ')
# + str(datetime.datetime.now().strftime('%H:%M:%S.%f')))
# Update the layer extents (after adding features)
memresult.updateExtents()
memresult.reload()
# Remove references
layercopy = None
for buffgeom in buffergeomvector:
buffgeom = None
buffergeomvector = None
except:
# Remove references
layercopy = None
import traceback
self.error.emit(traceback.format_exc())
self.finished.emit(False, None)
for buffgeom in buffergeomvector:
buffgeom = None
buffergeomvector = None
else:
if self.abort is True:
self.finished.emit(False, None)
else:
if memresult is not None:
self.finished.emit(True, memresult)
memresult = None
else:
self.finished.emit(False, None)
# end of run
def calculate_progress(self):
'''Update progress and emit a signal with the percentage'''
self.processed = self.processed + 1
# update the progress bar at certain increments
if (self.increment == 0 or
self.processed % self.increment == 0):
perc_new = (self.processed * 100) / self.worktodo
if perc_new > self.percentage:
self.percentage = perc_new
self.progress.emit(self.percentage)
# end of calculate_progress
def kill(self):
'''Kill the thread by setting the abort flag'''
self.abort = True
self.status.emit("Worker told to abort")
# end of kill
def tr(self, message):
'''Get the translation for a string using Qt translation API.
We implement this ourselves since we do not inherit QObject.
:param message: String for translation.
:type message: str, QString
:returns: Translated version of message.
:rtype: QString
'''
# noinspection PyTypeChecker, PyArgumentList, PyCallByClass
return QCoreApplication.translate('MultiDistanceBufferEngine', message)
# end of tr