1 # -*- coding: utf-8 -*-
4 ***************************************************************************
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
11 ***************************************************************************
14 from PyQt5.QtCore import (QCoreApplication, QVariant)
15 from qgis.core import (QgsProcessing,
18 QgsProcessingException,
19 QgsProcessingAlgorithm,
20 QgsProcessingParameterFeatureSource,
21 QgsProcessingParameterFeatureSink,
22 QgsProcessingParameterVectorLayer,
23 QgsProcessingParameterDistance,
24 QgsVectorDataProvider,
30 class SpanCoordinatesAlgorithm(QgsProcessingAlgorithm):
32 This is an example algorithm that takes a vector layer and
33 creates a new identical one.
35 It is meant to be used as an example of how to create your own
36 algorithms and explain methods and variables used to do it. An
37 algorithm like this will be available in all elements, and there
38 is not need for additional work.
40 All Processing algorithms should extend the QgsProcessingAlgorithm
44 # Constants used to refer to parameters and outputs. They will be
45 # used when calling the algorithm from another algorithm, or when
46 # calling from the QGIS console.
50 TOLERANCE = 'TOLERANCE'
53 # Constants for feature field names
54 FATHER_ID = 'FatherId'
55 LINESTRING = 'LineString'
59 Returns a translatable string with the self.tr() function.
61 return QCoreApplication.translate('Processing', string)
63 def createInstance(self):
64 return SpanCoordinatesAlgorithm()
68 Returns the algorithm name, used for identifying the algorithm. This
69 string should be fixed for the algorithm, and must not be localised.
70 The name should be unique within each provider. Names should contain
71 lowercase alphanumeric characters only and no spaces or other
72 formatting characters.
74 return 'calculateDistrictCSV'
76 def displayName(self):
78 Returns the translated algorithm name, which should be used for any
79 user-visible display of the algorithm name.
81 return self.tr('Calculate CSV for Apros District')
85 Returns the name of the group this algorithm belongs to. This string
88 return self.tr('District network scripts')
92 Returns the unique ID of the group this algorithm belongs to. This
93 string should be fixed for the algorithm, and must not be localised.
94 The group id should be unique within each provider. Group id should
95 contain lowercase alphanumeric characters only and no spaces or other
96 formatting characters.
98 return 'districtscripts'
100 def shortHelpString(self):
102 Returns a localised short helper string for the algorithm. This string
103 should provide a basic description about what the algorithm does and the
104 parameters and outputs associated with it..
106 return self.tr("Calculate a string of coordinate points for a point span")
108 def initAlgorithm(self, config=None):
110 Here we define the inputs and output of the algorithm, along
111 with some other properties.
114 # We add the input vector features source. It can have any kind of
117 QgsProcessingParameterVectorLayer(
119 self.tr('Pipeline layer'),
120 [QgsProcessing.TypeVectorLine]
125 QgsProcessingParameterVectorLayer(
127 self.tr('Point span layer'),
128 [QgsProcessing.TypeVectorLine]
133 QgsProcessingParameterDistance(
135 self.tr('Location tolerance'),
142 QgsProcessingParameterFeatureSink(
144 self.tr('Output layer'),
145 QgsProcessing.TypeVectorLine
149 def processAlgorithm(self, parameters, context, feedback):
151 Here is where the processing itself takes place.
154 # Retrieve the feature source and sink. The 'dest_id' variable is used
155 # to uniquely identify the feature sink, and must be included in the
156 # dictionary returned by the processAlgorithm function.
157 pipe = self.parameterAsLayer(
163 span = self.parameterAsLayer(
169 eps = self.parameterAsDouble(
175 sourceFields = span.fields()
176 sourceNames = sourceFields.names()
177 outputFields = QgsFields(sourceFields)
178 if not ('x1' in sourceNames): outputFields.append(QgsField('x1', QVariant.Double))
179 if not ('y1' in sourceNames): outputFields.append(QgsField('y1', QVariant.Double))
180 if not ('z1' in sourceNames): outputFields.append(QgsField('z1', QVariant.Double))
181 if not ('x2' in sourceNames): outputFields.append(QgsField('x2', QVariant.Double))
182 if not ('y2' in sourceNames): outputFields.append(QgsField('y2', QVariant.Double))
183 if not ('z2' in sourceNames): outputFields.append(QgsField('z2', QVariant.Double))
184 if not ('Length' in sourceNames): outputFields.append(QgsField('Length', QVariant.Double))
185 if not ('LineString' in sourceNames): outputFields.append(QgsField('LineString', QVariant.String))
187 (output, outputId) = self.parameterAsSink(
194 # If source was not found, throw an exception to indicate that the algorithm
195 # encountered a fatal error. The exception text can be any string, but in this
196 # case we use the pre-built invalidSourceError method to return a standard
197 # helper text for when a source cannot be evaluated
199 raise QgsProcessingException(self.invalidSourceError(parameters, self.PIPE))
201 raise QgsProcessingException(self.invalidSourceError(parameters, self.SPAN))
203 # Compute the number of steps to display within the progress bar and
204 # get features from source
205 total = 100.0 / pipe.featureCount() if pipe.featureCount() else 0
207 # Dictionary from span feature ids to lengths
210 # Dictionary from span feature ids to lists of lists of QgsPoint objects
213 spanFeatures = span.getFeatures()
214 pipeFeatures = pipe.getFeatures()
216 for counter, feature in enumerate(pipeFeatures):
217 if feedback.isCanceled(): break
219 geometry = feature.geometry()
220 if geometry == None: continue
222 fatherID = feature[self.FATHER_ID]
225 length = geometry.length()
227 oldLength = lengths.get(fatherID, 0.0)
228 lengths[fatherID] = oldLength + length
231 pointList = strings.get(fatherID, [])
232 # feedback.pushInfo('Point list: {}'.format(pointList))
235 vertices = geometry.vertices()
236 while vertices.hasNext():
237 mylist.append(vertices.next())
239 # feedback.pushInfo('Feature {}, Father {}, Points: {}'.format(feature['Id'], fatherID, ";".join(map(lambda x: '{} {}'.format(x.x(), x.y()), mylist))))
241 pointList.append(mylist)
242 strings[fatherID] = pointList
244 # Update the progress bar
245 feedback.setProgress(int(counter * total))
247 if feedback.isCanceled():
250 feedback.pushInfo('Done')
254 feedback.pushInfo('Started editing')
255 feedback.pushInfo(str(spanFeatures))
257 for feature in spanFeatures:
258 if feedback.isCanceled(): break
260 #feedback.pushInfo(str(feature))
262 #feedback.pushInfo(str(id))
265 length = lengths.get(id, None)
268 mylist = strings.get(id, None)
270 feedback.pushInfo('No points for feature {}'.format(id))
273 #feedback.pushInfo('Points: {}'.format("|".join(map(lambda x: ";".join(('{} {}'.format(p.x(), p.y()) for p in x)), mylist))))
275 mypoints = list(feature.geometry().vertices())
276 head = feature.geometry().vertices().next()
279 #feedback.pushInfo(str(resultList))
281 i = next((i for i, x in enumerate(mylist) if head.distance(x[0]) <= eps), None)
283 mylist = list(map(lambda x: list(reversed(x)), mylist))
284 i = next((i for i, x in enumerate(mylist) if head.distance(x[0]) <= eps), None)
286 feedback.pushInfo('No matching start vertex for feature {}'.format(id))
290 vertices = mylist.pop(i)
292 resultList.extend(vertices[1:])
293 i = next((i for i, x in enumerate(mylist) if tail.distance(x[0]) <= eps), None)
295 # feedback.pushInfo(str(resultList))
298 result = ";".join(('{} {}'.format(p.x(), p.y()) for p in resultList))
300 feedback.pushInfo('Feature {}: {}'.format(id, result))
302 outputFeature = QgsFeature()
303 outputFeature.setFields(outputFields)
304 for i, x in enumerate(feature.attributes()):
305 fieldName = sourceFields[i].name()
306 outputFeature[fieldName] = x
308 outputFeature['x1'] = feature['x1']
309 outputFeature['y1'] = feature['y1']
310 outputFeature['z1'] = feature['z1']
311 outputFeature['x2'] = feature['x2']
312 outputFeature['y2'] = feature['y2']
313 outputFeature['z2'] = feature['z2']
314 outputFeature['Length'] = feature['length'] # length
315 outputFeature['LineString'] = result
317 output.addFeature(outputFeature)
319 feedback.pushInfo('Loop done')
321 #if feedback.isCanceled():
324 # span.commitChanges()
326 feedback.pushInfo('Changes committed')
328 # Return the results of the algorithm. In this case our only result is
329 # the feature sink which contains the processed features, but some
330 # algorithms may return multiple feature sinks, calculated numeric
331 # statistics, etc. These should all be included in the returned
332 # dictionary, with keys matching the feature corresponding parameter
334 return {self.OUTPUT: outputId}