]> gerrit.simantics Code Review - simantics/district.git/commitdiff
QGIS Python script for generating pipelines CVS files.
authorReino Ruusu <reino.ruusu@semantum.fi>
Thu, 21 Mar 2019 12:36:48 +0000 (14:36 +0200)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Sat, 31 Aug 2019 20:05:57 +0000 (23:05 +0300)
gitlab #40

Change-Id: I4c6527eddbb5d61b4cac81f5bed2cfd49863c83a

org.simantics.district.feature/build.properties
org.simantics.district.feature/rootFiles/QGIS scripts/generateCSV.py [new file with mode: 0644]

index 82ab19c62d182db3688b1af6e6f465a74265ac92..191f89077607f97c5d3a485e075b434e74bbaa87 100644 (file)
@@ -1 +1,2 @@
-bin.includes = feature.xml\r
+bin.includes = feature.xml
+root=rootFiles
diff --git a/org.simantics.district.feature/rootFiles/QGIS scripts/generateCSV.py b/org.simantics.district.feature/rootFiles/QGIS scripts/generateCSV.py
new file mode 100644 (file)
index 0000000..80c596b
--- /dev/null
@@ -0,0 +1,334 @@
+# -*- 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)
+import processing
+
+
+class SpanCoordinatesAlgorithm(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'
+    OUTPUT = 'OUTPUT'
+    
+    # Constants for feature field names
+    FATHER_ID = 'FatherId'
+    LINESTRING = 'LineString'
+
+    def tr(self, string):
+        """
+        Returns a translatable string with the self.tr() function.
+        """
+        return QCoreApplication.translate('Processing', string)
+
+    def createInstance(self):
+        return SpanCoordinatesAlgorithm()
+
+    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 'calculateDistrictCSV'
+
+    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')
+
+    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]
+            )
+        )
+        
+        self.addParameter(
+            QgsProcessingParameterDistance(
+                self.TOLERANCE,
+                self.tr('Location tolerance'),
+                0.0,
+                minValue = 0.0
+            )
+        )
+        
+        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
+        )
+        
+        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))
+        
+        (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()
+        
+        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
+            length = geometry.length()
+            
+            oldLength = lengths.get(fatherID, 0.0)
+            lengths[fatherID] = oldLength + length
+            
+            # 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
+            
+            # 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
+
+            #feedback.pushInfo(str(feature))
+            id = feature['Id']
+            #feedback.pushInfo(str(id))
+
+            # Length
+            length = lengths.get(id, None)
+            
+            # Vertices
+            mylist = strings.get(id, None)
+            if mylist == None:
+                feedback.pushInfo('No points for feature {}'.format(id))
+                continue
+            
+            #feedback.pushInfo('Points: {}'.format("|".join(map(lambda x: ";".join(('{} {}'.format(p.x(), p.y()) for p in x)), mylist))))
+            
+            mypoints = list(feature.geometry().vertices())
+            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('No matching start vertex for feature {}'.format(id))
+                    continue
+            
+            while i != None:
+                vertices = mylist.pop(i)
+                tail = vertices[-1]
+                resultList.extend(vertices[1:])
+                i = next((i for i, x in enumerate(mylist) if tail.distance(x[0]) <= eps), None)
+
+            # 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
+            
+            outputFeature['x1'] = feature['x1']
+            outputFeature['y1'] = feature['y1']
+            outputFeature['z1'] = feature['z1']
+            outputFeature['x2'] = feature['x2']
+            outputFeature['y2'] = feature['y2']
+            outputFeature['z2'] = feature['z2']
+            outputFeature['Length'] = feature['length'] # length
+            outputFeature['LineString'] = result
+            
+            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}