--- /dev/null
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+* *
+* 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. *
+* *
+***************************************************************************
+"""
+
+from PyQt5.QtCore import (QCoreApplication, QVariant)
+from qgis.core import (QgsProcessing,
+ QgsFeatureSink,
+ QgsFeature,
+ QgsProcessingException,
+ QgsProcessingAlgorithm,
+ QgsProcessingParameterFeatureSource,
+ QgsProcessingParameterFeatureSink,
+ QgsProcessingParameterVectorLayer,
+ QgsProcessingParameterDistance,
+ QgsVectorDataProvider,
+ QgsFields,
+ QgsField,
+ QgsUnitTypes)
+import processing
+import re
+from operator import itemgetter
+
+class SpanCoordinatesAlgorithm2(QgsProcessingAlgorithm):
+ """
+ This is an example algorithm that takes a vector layer and
+ creates a new identical one.
+
+ It is meant to be used as an example of how to create your own
+ algorithms and explain methods and variables used to do it. An
+ algorithm like this will be available in all elements, and there
+ is not need for additional work.
+
+ All Processing algorithms should extend the QgsProcessingAlgorithm
+ class.
+ """
+
+ # Constants used to refer to parameters and outputs. They will be
+ # used when calling the algorithm from another algorithm, or when
+ # calling from the QGIS console.
+
+ PIPE = 'PIPE'
+ SPAN = 'SPAN'
+ TOLERANCE = 'TOLERANCE'
+ MINIMUM_LENGTH = 'MINIMUM_LENGTH'
+ OUTPUT = 'OUTPUT'
+
+ # Constants for feature field names
+ FATHER_ID = 'FatherId'
+ LINESTRING = 'LineString'
+
+ # RegExp for DN values in
+ DN_PATTERN = re.compile(r"DN(\d+)(-.*)?")
+
+ def tr(self, string):
+ """
+ Returns a translatable string with the self.tr() function.
+ """
+ return QCoreApplication.translate('Processing', string)
+
+ def createInstance(self):
+ return SpanCoordinatesAlgorithm2()
+
+ def name(self):
+ """
+ Returns the algorithm name, used for identifying the algorithm. This
+ string should be fixed for the algorithm, and must not be localised.
+ The name should be unique within each provider. Names should contain
+ lowercase alphanumeric characters only and no spaces or other
+ formatting characters.
+ """
+ return 'calculateDistrictCSV2'
+
+ def displayName(self):
+ """
+ Returns the translated algorithm name, which should be used for any
+ user-visible display of the algorithm name.
+ """
+ return self.tr('Calculate CSV for Apros District (No coordinates)')
+
+ def group(self):
+ """
+ Returns the name of the group this algorithm belongs to. This string
+ should be localised.
+ """
+ return self.tr('District network scripts')
+
+ def groupId(self):
+ """
+ Returns the unique ID of the group this algorithm belongs to. This
+ string should be fixed for the algorithm, and must not be localised.
+ The group id should be unique within each provider. Group id should
+ contain lowercase alphanumeric characters only and no spaces or other
+ formatting characters.
+ """
+ return 'districtscripts'
+
+ def shortHelpString(self):
+ """
+ Returns a localised short helper string for the algorithm. This string
+ should provide a basic description about what the algorithm does and the
+ parameters and outputs associated with it..
+ """
+ return self.tr("Calculate a string of coordinate points for a point span")
+
+ def initAlgorithm(self, config=None):
+ """
+ Here we define the inputs and output of the algorithm, along
+ with some other properties.
+ """
+
+ # We add the input vector features source. It can have any kind of
+ # geometry.
+ self.addParameter(
+ QgsProcessingParameterVectorLayer(
+ self.PIPE,
+ self.tr('Pipeline layer'),
+ [QgsProcessing.TypeVectorLine]
+ )
+ )
+
+ self.addParameter(
+ QgsProcessingParameterVectorLayer(
+ self.SPAN,
+ self.tr('Point span layer'),
+ [QgsProcessing.TypeVectorLine]
+ )
+ )
+
+ tol = QgsProcessingParameterDistance(
+ self.TOLERANCE,
+ self.tr('Location tolerance'),
+ 0.001,
+ minValue = 0.0
+ )
+ tol.setDefaultUnit(QgsUnitTypes.DistanceMeters)
+ self.addParameter( tol )
+
+ dist = QgsProcessingParameterDistance(
+ self.MINIMUM_LENGTH,
+ self.tr('Minimum span length'),
+ 0.25,
+ minValue = 0.0
+ )
+ dist.setDefaultUnit(QgsUnitTypes.DistanceMeters)
+ self.addParameter( dist )
+
+ self.addParameter(
+ QgsProcessingParameterFeatureSink(
+ self.OUTPUT,
+ self.tr('Output layer'),
+ QgsProcessing.TypeVectorLine
+ )
+ )
+
+ def processAlgorithm(self, parameters, context, feedback):
+ """
+ Here is where the processing itself takes place.
+ """
+
+ # Retrieve the feature source and sink. The 'dest_id' variable is used
+ # to uniquely identify the feature sink, and must be included in the
+ # dictionary returned by the processAlgorithm function.
+ pipe = self.parameterAsLayer(
+ parameters,
+ self.PIPE,
+ context
+ )
+
+ span = self.parameterAsLayer(
+ parameters,
+ self.SPAN,
+ context
+ )
+
+ eps = self.parameterAsDouble(
+ parameters,
+ self.TOLERANCE,
+ context
+ )
+
+ minLength = self.parameterAsDouble(
+ parameters,
+ self.MINIMUM_LENGTH,
+ context
+ )
+
+ feedback.pushInfo('Tolerance: {} m\nMinimum span length: {} m'.format(eps, minLength))
+
+ sourceFields = span.fields()
+ sourceNames = sourceFields.names()
+ outputFields = QgsFields(sourceFields)
+ if not ('x1' in sourceNames): outputFields.append(QgsField('x1', QVariant.Double))
+ if not ('y1' in sourceNames): outputFields.append(QgsField('y1', QVariant.Double))
+ if not ('z1' in sourceNames): outputFields.append(QgsField('z1', QVariant.Double))
+ if not ('x2' in sourceNames): outputFields.append(QgsField('x2', QVariant.Double))
+ if not ('y2' in sourceNames): outputFields.append(QgsField('y2', QVariant.Double))
+ if not ('z2' in sourceNames): outputFields.append(QgsField('z2', QVariant.Double))
+ if not ('Length' in sourceNames): outputFields.append(QgsField('Length', QVariant.Double))
+ if not ('LineString' in sourceNames): outputFields.append(QgsField('LineString', QVariant.String))
+ if not ('DimensionDN' in sourceNames): outputFields.append(QgsField('DimensionDN', QVariant.Int))
+ if not ('PipeStruct' in sourceNames): outputFields.append(QgsField('PipeStruct', QVariant.String))
+
+ (output, outputId) = self.parameterAsSink(
+ parameters,
+ self.OUTPUT,
+ context,
+ outputFields
+ )
+
+ # If source was not found, throw an exception to indicate that the algorithm
+ # encountered a fatal error. The exception text can be any string, but in this
+ # case we use the pre-built invalidSourceError method to return a standard
+ # helper text for when a source cannot be evaluated
+ if pipe is None:
+ raise QgsProcessingException(self.invalidSourceError(parameters, self.PIPE))
+ if span is None:
+ raise QgsProcessingException(self.invalidSourceError(parameters, self.SPAN))
+
+ # Compute the number of steps to display within the progress bar and
+ # get features from source
+ total = 100.0 / pipe.featureCount() if pipe.featureCount() else 0
+
+ # Dictionary from span feature ids to lengths
+ lengths = dict()
+
+ # Dictionary from span feature ids to lists of lists of QgsPoint objects
+ strings = dict()
+
+ # Dictionary for the PipeStruct field values
+ types = dict()
+
+ spanFeatures = span.getFeatures()
+ pipeFeatures = pipe.getFeatures()
+
+ for counter, feature in enumerate(pipeFeatures):
+ if feedback.isCanceled(): break
+
+ geometry = feature.geometry()
+ if geometry == None: continue
+
+ fatherID = feature[self.FATHER_ID]
+
+ # Length
+ myLength = None
+ if feature.fields().lookupField('Length') != -1:
+ myLength = feature['Length']
+ if myLength == None:
+ myLength = geometry.length()
+
+ oldLength = lengths.get(fatherID, 0.0)
+ lengths[fatherID] = oldLength + myLength
+
+ # Segment points
+ pointList = strings.get(fatherID, [])
+ # feedback.pushInfo('Point list: {}'.format(pointList))
+ mylist = []
+
+ vertices = geometry.vertices()
+ while vertices.hasNext():
+ mylist.append(vertices.next())
+
+ # feedback.pushInfo('Feature {}, Father {}, Points: {}'.format(feature['Id'], fatherID, ";".join(map(lambda x: '{} {}'.format(x.x(), x.y()), mylist))))
+
+ pointList.append(mylist)
+ strings[fatherID] = pointList
+
+ # Store the value of PipeStruct
+ t = feature['PipeStruct']
+ tt = types.get(fatherID, {})
+ types[fatherID] = tt
+ c = tt.get(t, 0)
+ c += myLength
+ tt[t] = c
+
+ # Update the progress bar
+ feedback.setProgress(int(counter * total))
+
+ if feedback.isCanceled():
+ return
+
+ feedback.pushInfo('Done')
+
+ #span.startEditing()
+
+ feedback.pushInfo('Started editing')
+ feedback.pushInfo(str(spanFeatures))
+
+ for feature in spanFeatures:
+ if feedback.isCanceled(): break
+
+ f = feature.fields()
+
+ #feedback.pushInfo(str(feature))
+ id = feature['Id']
+ #feedback.pushInfo(str(id))
+
+ # Length
+ myLength = None
+ if f.lookupField('Length') != -1:
+ myLength = feature['Length']
+ if myLength == None:
+ myLength = feature.geometry().length()
+
+ # Ignore short stumps
+ if myLength <= minLength:
+ continue
+
+ # Vertices
+ mypoints = list(feature.geometry().vertices())
+ mylist = strings.get(id, None)
+ if mylist == None:
+ feedback.pushInfo('No points for feature {}'.format(id))
+ mylist = [mypoints]
+
+ #feedback.pushInfo('Points: {}'.format("|".join(map(lambda x: ";".join(('{} {}'.format(p.x(), p.y()) for p in x)), mylist))))
+
+ head = feature.geometry().vertices().next()
+ resultList = [head]
+
+ #feedback.pushInfo(str(resultList))
+
+ i = next((i for i, x in enumerate(mylist) if head.distance(x[0]) <= eps), None)
+ if i == None:
+ mylist = list(map(lambda x: list(reversed(x)), mylist))
+ i = next((i for i, x in enumerate(mylist) if head.distance(x[0]) <= eps), None)
+ if i == None:
+ feedback.pushInfo('Warning: No matching start vertex for feature {}'.format(id))
+ mylist = [mypoints]
+ i = 0
+
+ vertices = mylist.pop(i)
+
+ while i != None:
+ tail = vertices[-1]
+ resultList.extend(vertices[1:])
+ if tail.distance(mypoints[-1]) <= eps:
+ break
+
+ i = next((i for i, x in enumerate(mylist) if tail.distance(x[0]) <= eps), None)
+ if i != None:
+ vertices = mylist.pop(i)
+ else:
+ i = next((i for i, x in enumerate(mylist) if tail.distance(x[-1]) <= eps), None)
+ if i != None:
+ vertices = list(reversed(mylist.pop(i)))
+
+ # feedback.pushInfo(str(resultList))
+
+ # Convert to string
+ result = ";".join(('{} {}'.format(p.x(), p.y()) for p in resultList))
+
+ # feedback.pushInfo('Feature {}: {}'.format(id, result))
+
+ outputFeature = QgsFeature()
+ outputFeature.setFields(outputFields)
+ for i, x in enumerate(feature.attributes()):
+ fieldName = sourceFields[i].name()
+ outputFeature[fieldName] = x
+
+ vts = list(feature.geometry().vertices())
+ p1 = vts[0]
+ p2 = vts[-1]
+
+ outputFeature['x1'] = feature['x1'] if f.lookupField('x1') != -1 else p1.x()
+ outputFeature['y1'] = feature['y1'] if f.lookupField('y1') != -1 else p1.y()
+ outputFeature['z1'] = feature['z1'] if f.lookupField('z1') != -1 else p1.z()
+ outputFeature['x2'] = feature['x2'] if f.lookupField('x2') != -1 else p2.x()
+ outputFeature['y2'] = feature['y2'] if f.lookupField('y2') != -1 else p2.y()
+ outputFeature['z2'] = feature['z2'] if f.lookupField('z2') != -1 else p2.z()
+ outputFeature['Length'] = feature['Length'] if f.lookupField('Length') != -1 else feature.geometry().length()
+ outputFeature['LineString'] = result
+
+ # Handle pipe type codes
+ mytypes = list(types.get(id, {}).items())
+ if len(mytypes) == 0:
+ feedback.pushInfo('No type codes for feature {}'.format(id))
+ else:
+ if len(mytypes) > 1:
+ mytypes.sort(key = itemgetter(1))
+ feedback.pushInfo('No unique type code for feature {}: {}'.format(id, mytypes))
+ outputFeature['PipeStruct'] = mytypes[-1][0]
+
+ if f.lookupField('Label') != -1:
+ label = feature['Label']
+ m = self.DN_PATTERN.fullmatch(label)
+ if m:
+ outputFeature['DimensionDN'] = int(m.group(1))
+
+ output.addFeature(outputFeature)
+
+ feedback.pushInfo('Loop done')
+
+ #if feedback.isCanceled():
+ # span.rollBack()
+ #else:
+ # span.commitChanges()
+
+ feedback.pushInfo('Changes committed')
+
+ # Return the results of the algorithm. In this case our only result is
+ # the feature sink which contains the processed features, but some
+ # algorithms may return multiple feature sinks, calculated numeric
+ # statistics, etc. These should all be included in the returned
+ # dictionary, with keys matching the feature corresponding parameter
+ # or output names.
+ return {self.OUTPUT: outputId}
--- /dev/null
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+* *
+* 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. *
+* *
+***************************************************************************
+"""
+
+from PyQt5.QtCore import (QCoreApplication, QVariant)
+from qgis.core import (QgsProcessing,
+ QgsFeatureSink,
+ QgsFeature,
+ QgsProcessingException,
+ QgsProcessingAlgorithm,
+ QgsProcessingParameterFeatureSource,
+ QgsProcessingParameterFeatureSink,
+ QgsProcessingParameterVectorLayer,
+ QgsProcessingParameterDistance,
+ QgsVectorDataProvider,
+ QgsFields,
+ QgsField,
+ QgsUnitTypes)
+import processing
+import re
+from operator import itemgetter
+
+class SpanCoordinatesAlgorithm3(QgsProcessingAlgorithm):
+ """
+ This is an example algorithm that takes a vector layer and
+ creates a new identical one.
+
+ It is meant to be used as an example of how to create your own
+ algorithms and explain methods and variables used to do it. An
+ algorithm like this will be available in all elements, and there
+ is not need for additional work.
+
+ All Processing algorithms should extend the QgsProcessingAlgorithm
+ class.
+ """
+
+ # Constants used to refer to parameters and outputs. They will be
+ # used when calling the algorithm from another algorithm, or when
+ # calling from the QGIS console.
+
+ PIPE = 'PIPE'
+ SPAN = 'SPAN'
+ TOLERANCE = 'TOLERANCE'
+ MINIMUM_LENGTH = 'MINIMUM_LENGTH'
+ OUTPUT = 'OUTPUT'
+
+ # Constants for feature field names
+ FATHER_ID = 'FatherId'
+ LINESTRING = 'LineString'
+
+ # RegExp for DN values in
+ DN_PATTERN = re.compile(r"DN(\d+)(-.*)?")
+
+ def tr(self, string):
+ """
+ Returns a translatable string with the self.tr() function.
+ """
+ return QCoreApplication.translate('Processing', string)
+
+ def createInstance(self):
+ return SpanCoordinatesAlgorithm3()
+
+ def name(self):
+ """
+ Returns the algorithm name, used for identifying the algorithm. This
+ string should be fixed for the algorithm, and must not be localised.
+ The name should be unique within each provider. Names should contain
+ lowercase alphanumeric characters only and no spaces or other
+ formatting characters.
+ """
+ return 'calculateDistrictCSV3'
+
+ def displayName(self):
+ """
+ Returns the translated algorithm name, which should be used for any
+ user-visible display of the algorithm name.
+ """
+ return self.tr('Calculate CSV for Apros District (TechTypeData)')
+
+ def group(self):
+ """
+ Returns the name of the group this algorithm belongs to. This string
+ should be localised.
+ """
+ return self.tr('District network scripts')
+
+ def groupId(self):
+ """
+ Returns the unique ID of the group this algorithm belongs to. This
+ string should be fixed for the algorithm, and must not be localised.
+ The group id should be unique within each provider. Group id should
+ contain lowercase alphanumeric characters only and no spaces or other
+ formatting characters.
+ """
+ return 'districtscripts'
+
+ def shortHelpString(self):
+ """
+ Returns a localised short helper string for the algorithm. This string
+ should provide a basic description about what the algorithm does and the
+ parameters and outputs associated with it..
+ """
+ return self.tr("Calculate a string of coordinate points for a point span")
+
+ def initAlgorithm(self, config=None):
+ """
+ Here we define the inputs and output of the algorithm, along
+ with some other properties.
+ """
+
+ # We add the input vector features source. It can have any kind of
+ # geometry.
+ self.addParameter(
+ QgsProcessingParameterVectorLayer(
+ self.PIPE,
+ self.tr('Pipeline layer'),
+ [QgsProcessing.TypeVectorLine]
+ )
+ )
+
+ self.addParameter(
+ QgsProcessingParameterVectorLayer(
+ self.SPAN,
+ self.tr('Point span layer'),
+ [QgsProcessing.TypeVectorLine]
+ )
+ )
+
+ tol = QgsProcessingParameterDistance(
+ self.TOLERANCE,
+ self.tr('Location tolerance'),
+ 0.001,
+ minValue = 0.0
+ )
+ tol.setDefaultUnit(QgsUnitTypes.DistanceMeters)
+ self.addParameter( tol )
+
+ dist = QgsProcessingParameterDistance(
+ self.MINIMUM_LENGTH,
+ self.tr('Minimum span length'),
+ 0.25,
+ minValue = 0.0
+ )
+ dist.setDefaultUnit(QgsUnitTypes.DistanceMeters)
+ self.addParameter( dist )
+
+ self.addParameter(
+ QgsProcessingParameterFeatureSink(
+ self.OUTPUT,
+ self.tr('Output layer'),
+ QgsProcessing.TypeVectorLine
+ )
+ )
+
+ def processAlgorithm(self, parameters, context, feedback):
+ """
+ Here is where the processing itself takes place.
+ """
+
+ # Retrieve the feature source and sink. The 'dest_id' variable is used
+ # to uniquely identify the feature sink, and must be included in the
+ # dictionary returned by the processAlgorithm function.
+ pipe = self.parameterAsLayer(
+ parameters,
+ self.PIPE,
+ context
+ )
+
+ span = self.parameterAsLayer(
+ parameters,
+ self.SPAN,
+ context
+ )
+
+ eps = self.parameterAsDouble(
+ parameters,
+ self.TOLERANCE,
+ context
+ )
+
+ minLength = self.parameterAsDouble(
+ parameters,
+ self.MINIMUM_LENGTH,
+ context
+ )
+
+ feedback.pushInfo('Tolerance: {} m\nMinimum span length: {} m'.format(eps, minLength))
+
+ sourceFields = span.fields()
+ sourceNames = sourceFields.names()
+ outputFields = QgsFields(sourceFields)
+ if not ('x1' in sourceNames): outputFields.append(QgsField('x1', QVariant.Double))
+ if not ('y1' in sourceNames): outputFields.append(QgsField('y1', QVariant.Double))
+ if not ('z1' in sourceNames): outputFields.append(QgsField('z1', QVariant.Double))
+ if not ('x2' in sourceNames): outputFields.append(QgsField('x2', QVariant.Double))
+ if not ('y2' in sourceNames): outputFields.append(QgsField('y2', QVariant.Double))
+ if not ('z2' in sourceNames): outputFields.append(QgsField('z2', QVariant.Double))
+ if not ('Length' in sourceNames): outputFields.append(QgsField('Length', QVariant.Double))
+ if not ('LineString' in sourceNames): outputFields.append(QgsField('LineString', QVariant.String))
+ if not ('TechTypeId' in sourceNames): outputFields.append(QgsField('TechTypeId', QVariant.String))
+ if not ('PipeStruct' in sourceNames): outputFields.append(QgsField('PipeStruct', QVariant.String))
+
+ (output, outputId) = self.parameterAsSink(
+ parameters,
+ self.OUTPUT,
+ context,
+ outputFields
+ )
+
+ # If source was not found, throw an exception to indicate that the algorithm
+ # encountered a fatal error. The exception text can be any string, but in this
+ # case we use the pre-built invalidSourceError method to return a standard
+ # helper text for when a source cannot be evaluated
+ if pipe is None:
+ raise QgsProcessingException(self.invalidSourceError(parameters, self.PIPE))
+ if span is None:
+ raise QgsProcessingException(self.invalidSourceError(parameters, self.SPAN))
+
+ # Compute the number of steps to display within the progress bar and
+ # get features from source
+ total = 100.0 / pipe.featureCount() if pipe.featureCount() else 0
+
+ # Dictionary from span feature ids to lengths
+ lengths = dict()
+
+ # Dictionary from span feature ids to lists of lists of QgsPoint objects
+ strings = dict()
+
+ # Dictionary for the PipeStruct field values
+ types = dict()
+
+ spanFeatures = span.getFeatures()
+ pipeFeatures = pipe.getFeatures()
+
+ for counter, feature in enumerate(pipeFeatures):
+ if feedback.isCanceled(): break
+
+ geometry = feature.geometry()
+ if geometry == None: continue
+
+ fatherID = feature[self.FATHER_ID]
+
+ # Length
+ myLength = None
+ if feature.fields().lookupField('Length') != -1:
+ myLength = feature['Length']
+ if myLength == None:
+ myLength = geometry.length()
+
+ oldLength = lengths.get(fatherID, 0.0)
+ lengths[fatherID] = oldLength + myLength
+
+ # Segment points
+ pointList = strings.get(fatherID, [])
+ # feedback.pushInfo('Point list: {}'.format(pointList))
+ mylist = []
+
+ vertices = geometry.vertices()
+ while vertices.hasNext():
+ mylist.append(vertices.next())
+
+ # feedback.pushInfo('Feature {}, Father {}, Points: {}'.format(feature['Id'], fatherID, ";".join(map(lambda x: '{} {}'.format(x.x(), x.y()), mylist))))
+
+ pointList.append(mylist)
+ strings[fatherID] = pointList
+
+ # Store the value of PipeStruct
+ t = feature['PipeStruct']
+ tt = types.get(fatherID, {})
+ types[fatherID] = tt
+ c = tt.get(t, 0)
+ c += myLength
+ tt[t] = c
+
+ # Update the progress bar
+ feedback.setProgress(int(counter * total))
+
+ if feedback.isCanceled():
+ return
+
+ feedback.pushInfo('Done')
+
+ #span.startEditing()
+
+ feedback.pushInfo('Started editing')
+ feedback.pushInfo(str(spanFeatures))
+
+ for feature in spanFeatures:
+ if feedback.isCanceled(): break
+
+ f = feature.fields()
+
+ #feedback.pushInfo(str(feature))
+ id = feature['Id']
+ #feedback.pushInfo(str(id))
+
+ # Length
+ myLength = None
+ if f.lookupField('Length') != -1:
+ myLength = feature['Length']
+ if myLength == None:
+ myLength = feature.geometry().length()
+
+ # Ignore short stumps
+ if myLength <= minLength:
+ continue
+
+ # Vertices
+ mypoints = list(feature.geometry().vertices())
+ mylist = strings.get(id, None)
+ if mylist == None:
+ feedback.pushInfo('No points for feature {}'.format(id))
+ mylist = [mypoints]
+
+ #feedback.pushInfo('Points: {}'.format("|".join(map(lambda x: ";".join(('{} {}'.format(p.x(), p.y()) for p in x)), mylist))))
+
+ head = feature.geometry().vertices().next()
+ resultList = [head]
+
+ #feedback.pushInfo(str(resultList))
+
+ i = next((i for i, x in enumerate(mylist) if head.distance(x[0]) <= eps), None)
+ if i == None:
+ mylist = list(map(lambda x: list(reversed(x)), mylist))
+ i = next((i for i, x in enumerate(mylist) if head.distance(x[0]) <= eps), None)
+ if i == None:
+ feedback.pushInfo('Warning: No matching start vertex for feature {}'.format(id))
+ mylist = [mypoints]
+ i = 0
+
+ vertices = mylist.pop(i)
+
+ while i != None:
+ tail = vertices[-1]
+ resultList.extend(vertices[1:])
+ if tail.distance(mypoints[-1]) <= eps:
+ break
+
+ i = next((i for i, x in enumerate(mylist) if tail.distance(x[0]) <= eps), None)
+ if i != None:
+ vertices = mylist.pop(i)
+ else:
+ i = next((i for i, x in enumerate(mylist) if tail.distance(x[-1]) <= eps), None)
+ if i != None:
+ vertices = list(reversed(mylist.pop(i)))
+
+ # feedback.pushInfo(str(resultList))
+
+ # Convert to string
+ result = ";".join(('{} {}'.format(p.x(), p.y()) for p in resultList))
+
+ # feedback.pushInfo('Feature {}: {}'.format(id, result))
+
+ outputFeature = QgsFeature()
+ outputFeature.setFields(outputFields)
+ for i, x in enumerate(feature.attributes()):
+ fieldName = sourceFields[i].name()
+ outputFeature[fieldName] = x
+
+ vts = list(feature.geometry().vertices())
+ p1 = vts[0]
+ p2 = vts[-1]
+
+ outputFeature['x1'] = feature['x1'] if f.lookupField('x1') != -1 else p1.x()
+ outputFeature['y1'] = feature['y1'] if f.lookupField('y1') != -1 else p1.y()
+ outputFeature['z1'] = feature['z1'] if f.lookupField('z1') != -1 else p1.z()
+ outputFeature['x2'] = feature['x2'] if f.lookupField('x2') != -1 else p2.x()
+ outputFeature['y2'] = feature['y2'] if f.lookupField('y2') != -1 else p2.y()
+ outputFeature['z2'] = feature['z2'] if f.lookupField('z2') != -1 else p2.z()
+ outputFeature['Length'] = feature['Length'] if f.lookupField('Length') != -1 else feature.geometry().length()
+ outputFeature['LineString'] = result
+
+ # Handle pipe type codes
+ mytypes = list(types.get(id, {}).items())
+ if len(mytypes) == 0:
+ feedback.pushInfo('No type codes for feature {}'.format(id))
+ else:
+ if len(mytypes) > 1:
+ mytypes.sort(key = itemgetter(1))
+ feedback.pushInfo('No unique type code for feature {}: {}'.format(id, mytypes))
+ outputFeature['PipeStruct'] = mytypes[-1][0]
+
+ if f.lookupField('TechTypeId') != -1:
+ outputFeature['TechTypeId'] = feature['TechTypeId']
+
+ output.addFeature(outputFeature)
+
+ feedback.pushInfo('Loop done')
+
+ #if feedback.isCanceled():
+ # span.rollBack()
+ #else:
+ # span.commitChanges()
+
+ feedback.pushInfo('Changes committed')
+
+ # Return the results of the algorithm. In this case our only result is
+ # the feature sink which contains the processed features, but some
+ # algorithms may return multiple feature sinks, calculated numeric
+ # statistics, etc. These should all be included in the returned
+ # dictionary, with keys matching the feature corresponding parameter
+ # or output names.
+ return {self.OUTPUT: outputId}