]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.feature/rootFiles/QGIS scripts/generateCSV.py
80c596b2c31733ec7eda00c734469d2eebca3dd1
[simantics/district.git] / org.simantics.district.feature / rootFiles / QGIS scripts / generateCSV.py
1 # -*- coding: utf-8 -*-
2
3 """
4 ***************************************************************************
5 *                                                                         *
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.                                   *
10 *                                                                         *
11 ***************************************************************************
12 """
13
14 from PyQt5.QtCore import (QCoreApplication, QVariant)
15 from qgis.core import (QgsProcessing,
16                        QgsFeatureSink,
17                        QgsFeature,
18                        QgsProcessingException,
19                        QgsProcessingAlgorithm,
20                        QgsProcessingParameterFeatureSource,
21                        QgsProcessingParameterFeatureSink,
22                        QgsProcessingParameterVectorLayer,
23                        QgsProcessingParameterDistance,
24                        QgsVectorDataProvider,
25                        QgsFields,
26                        QgsField)
27 import processing
28
29
30 class SpanCoordinatesAlgorithm(QgsProcessingAlgorithm):
31     """
32     This is an example algorithm that takes a vector layer and
33     creates a new identical one.
34
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.
39
40     All Processing algorithms should extend the QgsProcessingAlgorithm
41     class.
42     """
43
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.
47
48     PIPE = 'PIPE'
49     SPAN = 'SPAN'
50     TOLERANCE = 'TOLERANCE'
51     OUTPUT = 'OUTPUT'
52     
53     # Constants for feature field names
54     FATHER_ID = 'FatherId'
55     LINESTRING = 'LineString'
56
57     def tr(self, string):
58         """
59         Returns a translatable string with the self.tr() function.
60         """
61         return QCoreApplication.translate('Processing', string)
62
63     def createInstance(self):
64         return SpanCoordinatesAlgorithm()
65
66     def name(self):
67         """
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.
73         """
74         return 'calculateDistrictCSV'
75
76     def displayName(self):
77         """
78         Returns the translated algorithm name, which should be used for any
79         user-visible display of the algorithm name.
80         """
81         return self.tr('Calculate CSV for Apros District')
82
83     def group(self):
84         """
85         Returns the name of the group this algorithm belongs to. This string
86         should be localised.
87         """
88         return self.tr('District network scripts')
89
90     def groupId(self):
91         """
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.
97         """
98         return 'districtscripts'
99
100     def shortHelpString(self):
101         """
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..
105         """
106         return self.tr("Calculate a string of coordinate points for a point span")
107
108     def initAlgorithm(self, config=None):
109         """
110         Here we define the inputs and output of the algorithm, along
111         with some other properties.
112         """
113
114         # We add the input vector features source. It can have any kind of
115         # geometry.
116         self.addParameter(
117             QgsProcessingParameterVectorLayer(
118                 self.PIPE,
119                 self.tr('Pipeline layer'),
120                 [QgsProcessing.TypeVectorLine]
121             )
122         )
123
124         self.addParameter(
125             QgsProcessingParameterVectorLayer(
126                 self.SPAN,
127                 self.tr('Point span layer'),
128                 [QgsProcessing.TypeVectorLine]
129             )
130         )
131         
132         self.addParameter(
133             QgsProcessingParameterDistance(
134                 self.TOLERANCE,
135                 self.tr('Location tolerance'),
136                 0.0,
137                 minValue = 0.0
138             )
139         )
140         
141         self.addParameter(
142             QgsProcessingParameterFeatureSink(
143                 self.OUTPUT,
144                 self.tr('Output layer'),
145                 QgsProcessing.TypeVectorLine
146             )
147         )
148
149     def processAlgorithm(self, parameters, context, feedback):
150         """
151         Here is where the processing itself takes place.
152         """
153
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(
158             parameters,
159             self.PIPE,
160             context
161         )
162
163         span = self.parameterAsLayer(
164             parameters,
165             self.SPAN,
166             context
167         )
168         
169         eps = self.parameterAsDouble(
170             parameters,
171             self.TOLERANCE,
172             context
173         )
174         
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))
186         
187         (output, outputId) = self.parameterAsSink(
188             parameters,
189             self.OUTPUT,
190             context,
191             outputFields
192         )
193
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
198         if pipe is None:
199             raise QgsProcessingException(self.invalidSourceError(parameters, self.PIPE))
200         if span is None:
201             raise QgsProcessingException(self.invalidSourceError(parameters, self.SPAN))
202
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
206         
207         # Dictionary from span feature ids to lengths
208         lengths = dict()
209         
210         # Dictionary from span feature ids to lists of lists of QgsPoint objects
211         strings = dict()
212         
213         spanFeatures = span.getFeatures()
214         pipeFeatures = pipe.getFeatures()
215         
216         for counter, feature in enumerate(pipeFeatures):
217             if feedback.isCanceled(): break
218                 
219             geometry = feature.geometry()
220             if geometry == None: continue
221             
222             fatherID = feature[self.FATHER_ID]
223             
224             # Length
225             length = geometry.length()
226             
227             oldLength = lengths.get(fatherID, 0.0)
228             lengths[fatherID] = oldLength + length
229             
230             # Segment points
231             pointList = strings.get(fatherID, [])
232             # feedback.pushInfo('Point list: {}'.format(pointList))
233             mylist = []
234             
235             vertices = geometry.vertices()
236             while vertices.hasNext():
237                 mylist.append(vertices.next())
238                 
239             # feedback.pushInfo('Feature {}, Father {}, Points: {}'.format(feature['Id'], fatherID, ";".join(map(lambda x: '{} {}'.format(x.x(), x.y()), mylist))))
240             
241             pointList.append(mylist)
242             strings[fatherID] = pointList
243             
244             # Update the progress bar
245             feedback.setProgress(int(counter * total))
246
247         if feedback.isCanceled():
248             return
249
250         feedback.pushInfo('Done')
251         
252         #span.startEditing()
253         
254         feedback.pushInfo('Started editing')
255         feedback.pushInfo(str(spanFeatures))
256         
257         for feature in spanFeatures:
258             if feedback.isCanceled(): break
259
260             #feedback.pushInfo(str(feature))
261             id = feature['Id']
262             #feedback.pushInfo(str(id))
263
264             # Length
265             length = lengths.get(id, None)
266             
267             # Vertices
268             mylist = strings.get(id, None)
269             if mylist == None:
270                 feedback.pushInfo('No points for feature {}'.format(id))
271                 continue
272             
273             #feedback.pushInfo('Points: {}'.format("|".join(map(lambda x: ";".join(('{} {}'.format(p.x(), p.y()) for p in x)), mylist))))
274             
275             mypoints = list(feature.geometry().vertices())
276             head = feature.geometry().vertices().next()
277             resultList = [head]
278             
279             #feedback.pushInfo(str(resultList))
280             
281             i = next((i for i, x in enumerate(mylist) if head.distance(x[0]) <= eps), None)
282             if i == 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)
285                 if i == None:
286                     feedback.pushInfo('No matching start vertex for feature {}'.format(id))
287                     continue
288             
289             while i != None:
290                 vertices = mylist.pop(i)
291                 tail = vertices[-1]
292                 resultList.extend(vertices[1:])
293                 i = next((i for i, x in enumerate(mylist) if tail.distance(x[0]) <= eps), None)
294
295             # feedback.pushInfo(str(resultList))
296
297             # Convert to string
298             result = ";".join(('{} {}'.format(p.x(), p.y()) for p in resultList))
299
300             feedback.pushInfo('Feature {}: {}'.format(id, result))
301             
302             outputFeature = QgsFeature()
303             outputFeature.setFields(outputFields)
304             for i, x in enumerate(feature.attributes()):
305                 fieldName = sourceFields[i].name()
306                 outputFeature[fieldName] = x
307             
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
316             
317             output.addFeature(outputFeature)
318             
319         feedback.pushInfo('Loop done')
320         
321         #if feedback.isCanceled():
322         #    span.rollBack()
323         #else:
324         #    span.commitChanges()
325
326         feedback.pushInfo('Changes committed')
327         
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
333         # or output names.
334         return {self.OUTPUT: outputId}