示例#1
0
    def testGetGeometry(self):
        idx = QgsSpatialIndex()
        idx2 = QgsSpatialIndex(QgsSpatialIndex.FlagStoreFeatureGeometries)
        fid = 0
        for y in range(5):
            for x in range(10, 15):
                ft = QgsFeature()
                ft.setId(fid)
                ft.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
                idx.addFeature(ft)
                idx2.addFeature(ft)
                fid += 1

        # not storing geometries, a keyerror should be raised
        with self.assertRaises(KeyError):
            idx.geometry(-100)
        with self.assertRaises(KeyError):
            idx.geometry(1)
        with self.assertRaises(KeyError):
            idx.geometry(2)
        with self.assertRaises(KeyError):
            idx.geometry(1000)

        self.assertEqual(idx2.geometry(1).asWkt(1), 'Point (11 0)')
        self.assertEqual(idx2.geometry(2).asWkt(1), 'Point (12 0)')
        with self.assertRaises(KeyError):
            idx2.geometry(-100)
        with self.assertRaises(KeyError):
            idx2.geometry(1000)
示例#2
0
    def processAlgorithm(self, parameters, context, feedback):
        pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER,
                                            context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE,
                                             context)
        crs = self.parameterAsCrs(parameters, self.TARGET_CRS, context)
        bbox = self.parameterAsExtent(parameters, self.EXTENT, context, crs)

        extent = QgsGeometry().fromRect(bbox)

        fields = QgsFields()
        fields.append(QgsField('id', QVariant.Int, '', 10, 0))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, fields,
                                               QgsWkbTypes.Point, crs)
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        nPoints = 0
        nIterations = 0
        maxIterations = pointCount * 200
        total = 100.0 / pointCount if pointCount else 1

        index = QgsSpatialIndex()
        points = dict()

        random.seed()

        while nIterations < maxIterations and nPoints < pointCount:
            if feedback.isCanceled():
                break

            rx = bbox.xMinimum() + bbox.width() * random.random()
            ry = bbox.yMinimum() + bbox.height() * random.random()

            p = QgsPointXY(rx, ry)
            geom = QgsGeometry.fromPointXY(p)
            if geom.within(extent) and \
                    vector.checkMinDistance(p, index, minDistance, points):
                f = QgsFeature(nPoints)
                f.initAttributes(1)
                f.setFields(fields)
                f.setAttribute('id', nPoints)
                f.setGeometry(geom)
                sink.addFeature(f, QgsFeatureSink.FastInsert)
                index.addFeature(f)
                points[nPoints] = p
                nPoints += 1
                feedback.setProgress(int(nPoints * total))
            nIterations += 1

        if nPoints < pointCount:
            feedback.pushInfo(
                self.tr(
                    'Could not generate requested number of random points. '
                    'Maximum number of attempts exceeded.'))

        return {self.OUTPUT: dest_id}
示例#3
0
    def testIndex(self):
        idx = QgsSpatialIndex()
        fid = 0
        for y in range(5, 15, 5):
            for x in range(5, 25, 5):
                ft = QgsFeature()
                ft.setId(fid)
                ft.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
                idx.addFeature(ft)
                fid += 1

        # intersection test
        rect = QgsRectangle(7.0, 3.0, 17.0, 13.0)
        fids = idx.intersects(rect)
        myExpectedValue = 4
        myValue = len(fids)
        myMessage = 'Expected: %s Got: %s' % (myExpectedValue, myValue)
        self.assertEqual(myValue, myExpectedValue, myMessage)
        fids.sort()
        myMessage = ('Expected: %s\nGot: %s\n' % ([1, 2, 5, 6], fids))
        assert fids == [1, 2, 5, 6], myMessage

        # nearest neighbor test
        fids = idx.nearestNeighbor(QgsPointXY(8.75, 6.25), 3)
        myExpectedValue = 0
        myValue = len(fids)
        myMessage = 'Expected: %s Got: %s' % (myExpectedValue, myValue)

        fids.sort()
        myMessage = ('Expected: %s\nGot: %s\n' % ([0, 1, 5], fids))
        assert fids == [0, 1, 5], myMessage
def create_spatial_index(layer):
    # Select all features along with their attributes
    all_features = {feature.id(): feature for (feature) in layer.getFeatures()}
    # Create spatial index
    spatial_index = QgsSpatialIndex()
    for f in all_features.values():
        spatial_index.addFeature(f)
    return spatial_index
示例#5
0
    def compute_graph(features,
                      feedback,
                      create_id_graph=False,
                      min_distance=0):
        """ compute topology from a layer/field """
        s = Graph(sort_graph=False)
        id_graph = None
        if create_id_graph:
            id_graph = Graph(sort_graph=True)

        # skip features without geometry
        features_with_geometry = {
            f_id: f
            for (f_id, f) in features.items() if f.hasGeometry()
        }

        total = 70.0 / len(
            features_with_geometry) if features_with_geometry else 1
        index = QgsSpatialIndex()

        i = 0
        for feature_id, f in features_with_geometry.items():
            if feedback.isCanceled():
                break

            g = f.geometry()
            if min_distance > 0:
                g = g.buffer(min_distance, 5)

            engine = QgsGeometry.createGeometryEngine(g.constGet())
            engine.prepareGeometry()

            feature_bounds = g.boundingBox()
            # grow bounds a little so we get touching features
            feature_bounds.grow(feature_bounds.width() * 0.01)
            intersections = index.intersects(feature_bounds)
            for l2 in intersections:
                f2 = features_with_geometry[l2]
                if engine.intersects(f2.geometry().constGet()):
                    s.add_edge(f.id(), f2.id())
                    s.add_edge(f2.id(), f.id())
                    if id_graph:
                        id_graph.add_edge(f.id(), f2.id())

            index.addFeature(f)
            i += 1
            feedback.setProgress(int(i * total))

        for feature_id, f in features_with_geometry.items():
            if feedback.isCanceled():
                break

            if feature_id not in s.node_edge:
                s.add_edge(feature_id, None)

        return s, id_graph
示例#6
0
 def buildSpatialIndexAndIdDict(self, inputLyr):
     """
     creates a spatial index for the centroid layer
     """
     spatialIdx = QgsSpatialIndex()
     idDict = {}
     for feat in inputLyr.getFeatures():
         spatialIdx.addFeature(feat)
         idDict[feat.id()] = feat
     return spatialIdx, idDict
示例#7
0
    def processAlgorithm(self, parameters, context, feedback):
        pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context)
        crs = self.parameterAsCrs(parameters, self.TARGET_CRS, context)
        bbox = self.parameterAsExtent(parameters, self.EXTENT, context, crs)

        extent = QgsGeometry().fromRect(bbox)

        fields = QgsFields()
        fields.append(QgsField('id', QVariant.Int, '', 10, 0))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.Point, crs)
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        nPoints = 0
        nIterations = 0
        maxIterations = pointCount * 200
        total = 100.0 / pointCount if pointCount else 1

        index = QgsSpatialIndex()
        points = dict()

        random.seed()

        while nIterations < maxIterations and nPoints < pointCount:
            if feedback.isCanceled():
                break

            rx = bbox.xMinimum() + bbox.width() * random.random()
            ry = bbox.yMinimum() + bbox.height() * random.random()

            p = QgsPointXY(rx, ry)
            geom = QgsGeometry.fromPointXY(p)
            if geom.within(extent) and \
                    vector.checkMinDistance(p, index, minDistance, points):
                f = QgsFeature(nPoints)
                f.initAttributes(1)
                f.setFields(fields)
                f.setAttribute('id', nPoints)
                f.setGeometry(geom)
                sink.addFeature(f, QgsFeatureSink.FastInsert)
                index.addFeature(f)
                points[nPoints] = p
                nPoints += 1
                feedback.setProgress(int(nPoints * total))
            nIterations += 1

        if nPoints < pointCount:
            feedback.pushInfo(self.tr('Could not generate requested number of random points. '
                                      'Maximum number of attempts exceeded.'))

        return {self.OUTPUT: dest_id}
def createIndex(layer):
    provider = layer.dataProvider()
    caps = provider.capabilities()
    if caps & QgsVectorDataProvider.CreateSpatialIndex:
        feat = QgsFeature()
        index = QgsSpatialIndex()
        fit = provider.getFeatures()
        while fit.nextFeature(feat):
            index.addFeature(feat)
        return index
    else:
        return None
示例#9
0
def get_nearest_segmentId(layer, QgsPointXY, set_progress_funk):
    spIndex = QgsSpatialIndex()
    all_f = layer.featureCount()
    for feature in layer.getFeatures():
        progress = 47 + (int(feature.id()) * 10) / all_f
        set_progress_funk(progress)
        spIndex.addFeature(feature)
    nearestIds = spIndex.nearestNeighbor(QgsPointXY, 1)
    nearest_feature = layer.getFeatures(QgsFeatureRequest().setFilterFid(
        nearestIds[0]))
    ftr = QgsFeature()
    nearest_feature.nextFeature(ftr)
    return ftr["fid"]
示例#10
0
    def compute_graph(features, feedback, create_id_graph=False, min_distance=0):
        """ compute topology from a layer/field """
        s = Graph(sort_graph=False)
        id_graph = None
        if create_id_graph:
            id_graph = Graph(sort_graph=True)

        # skip features without geometry
        features_with_geometry = {f_id: f for (f_id, f) in features.items() if f.hasGeometry()}

        total = 70.0 / len(features_with_geometry) if features_with_geometry else 1
        index = QgsSpatialIndex()

        i = 0
        for feature_id, f in features_with_geometry.items():
            if feedback.isCanceled():
                break

            g = f.geometry()
            if min_distance > 0:
                g = g.buffer(min_distance, 5)

            engine = QgsGeometry.createGeometryEngine(g.constGet())
            engine.prepareGeometry()

            feature_bounds = g.boundingBox()
            # grow bounds a little so we get touching features
            feature_bounds.grow(feature_bounds.width() * 0.01)
            intersections = index.intersects(feature_bounds)
            for l2 in intersections:
                f2 = features_with_geometry[l2]
                if engine.intersects(f2.geometry().constGet()):
                    s.add_edge(f.id(), f2.id())
                    s.add_edge(f2.id(), f.id())
                    if id_graph:
                        id_graph.add_edge(f.id(), f2.id())

            index.addFeature(f)
            i += 1
            feedback.setProgress(int(i * total))

        for feature_id, f in features_with_geometry.items():
            if feedback.isCanceled():
                break

            if feature_id not in s.node_edge:
                s.add_edge(feature_id, None)

        return s, id_graph
示例#11
0
 def _assignFuel(self, points):
     index = QgsSpatialIndex()
     for feat in self._fuel_layer.getFeatures():
         index.addFeature(feat)
     intersecting_fuels = dict()
     intersecting_points = list()
     for pt in points:
         pt.fuelModel = '1'
         geom = pt.feature.geometry()
         intersects = index.intersects(geom.boundingBox())
         if len(intersects) > 0:
             request = QgsFeatureRequest()
             request.setFilterFids([intersects[0]])
             fuels = [f for f in self._fuel_layer.getFeatures(request)]
             for fuel in fuels:
                 if not fuel.id() in intersecting_fuels:
                     intersecting_fuels[fuel.id()] = fuel
             intersecting_points.append(pt)
     if len(intersecting_fuels) > 0:
         for pt in intersecting_points:
             for k, feat in intersecting_fuels.items():
                 if feat.geometry().contains(pt.feature.geometry()):
                     pt.fuelModel = feat.attributes()[1]
                     break
示例#12
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE,
                                             context)

        expression = QgsExpression(
            self.parameterAsString(parameters, self.EXPRESSION, context))
        if expression.hasParserError():
            raise QgsProcessingException(expression.parserErrorString())

        expressionContext = self.createExpressionContext(
            parameters, context, source)
        expression.prepare(expressionContext)

        fields = QgsFields()
        fields.append(QgsField('id', QVariant.Int, '', 10, 0))

        (sink,
         dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                         fields, QgsWkbTypes.Point,
                                         source.sourceCrs(),
                                         QgsFeatureSink.RegeneratePrimaryKey)
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        da = QgsDistanceArea()
        da.setSourceCrs(source.sourceCrs(), context.transformContext())
        da.setEllipsoid(context.project().ellipsoid())

        total = 100.0 / source.featureCount() if source.featureCount() else 0
        current_progress = 0
        for current, f in enumerate(source.getFeatures()):
            if feedback.isCanceled():
                break

            if not f.hasGeometry():
                continue

            current_progress = total * current
            feedback.setProgress(current_progress)

            expressionContext.setFeature(f)
            value = expression.evaluate(expressionContext)
            if expression.hasEvalError():
                feedback.pushInfo(
                    self.tr('Evaluation error for feature ID {}: {}').format(
                        f.id(), expression.evalErrorString()))
                continue

            fGeom = f.geometry()
            engine = QgsGeometry.createGeometryEngine(fGeom.constGet())
            engine.prepareGeometry()

            bbox = fGeom.boundingBox()
            if strategy == 0:
                pointCount = int(value)
            else:
                pointCount = int(round(value * da.measureArea(fGeom)))

            if pointCount == 0:
                feedback.pushInfo(
                    "Skip feature {} as number of points for it is 0.".format(
                        f.id()))
                continue

            index = QgsSpatialIndex()
            points = dict()

            nPoints = 0
            nIterations = 0
            maxIterations = pointCount * 200
            feature_total = total / pointCount if pointCount else 1

            random.seed()

            while nIterations < maxIterations and nPoints < pointCount:
                if feedback.isCanceled():
                    break

                rx = bbox.xMinimum() + bbox.width() * random.random()
                ry = bbox.yMinimum() + bbox.height() * random.random()

                p = QgsPointXY(rx, ry)
                geom = QgsGeometry.fromPointXY(p)
                if engine.contains(geom.constGet()) and \
                        vector.checkMinDistance(p, index, minDistance, points):
                    f = QgsFeature(nPoints)
                    f.initAttributes(1)
                    f.setFields(fields)
                    f.setAttribute('id', nPoints)
                    f.setGeometry(geom)
                    sink.addFeature(f, QgsFeatureSink.FastInsert)
                    index.addFeature(f)
                    points[nPoints] = p
                    nPoints += 1
                    feedback.setProgress(current_progress +
                                         int(nPoints * feature_total))
                nIterations += 1

            if nPoints < pointCount:
                feedback.pushInfo(
                    self.tr('Could not generate requested number of random '
                            'points. Maximum number of attempts exceeded.'))

        feedback.setProgress(100)

        return {self.OUTPUT: dest_id}
示例#13
0
from qgis.core import QgsFeatureRequest

import csv
from qgis.utils import iface

layer = iface.activeLayer()
iter = layer.getFeatures()
grid = [f for f in iter]
groups = dict()
file = open('/home3/jaume/farmacies-2020-03-14.csv')
farmacies = csv.reader(file, delimiter=',')
i = 0

index = QgsSpatialIndex()
for feat in grid:
    index.addFeature(feat)
    groups[feat.id()] = list()

for farmacia in farmacies:
    if i > 0:
        punt = QgsGeometry.fromPointXY(
            QgsPointXY(float(farmacia[16]), float(farmacia[17])))
        intersects = index.intersects(punt.boundingBox())
        if len(intersects) > 0:
            request = QgsFeatureRequest()
            request.setFilterFids(intersects)
            feats = [f for f in layer.getFeatures(request)]
            found = False
            for feat in feats:
                if punt.intersects(feat.geometry()):
                    groups[feat.id()].append(farmacia)
示例#14
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context)
        MaxTriesPerPoint = self.parameterAsDouble(parameters, self.MAXTRIESPERPOINT, context)
        fields = QgsFields()
        fields.append(QgsField('id', QVariant.Int, '', 10, 0))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.Point, source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey)
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
      
        totNPoints = 0  # The total number of points generated
        featureCount = source.featureCount()
        total = 100.0 / (pointCount * featureCount) if pointCount else 1
        random.seed()

        index = QgsSpatialIndex()
        points = dict()

        da = QgsDistanceArea()
        da.setSourceCrs(source.sourceCrs(), context.transformContext())
        da.setEllipsoid(context.project().ellipsoid())

        maxIterations = pointCount * MaxTriesPerPoint
        for f in source.getFeatures():
            lineGeoms = []
            lineCount = 0
            fGeom = f.geometry()
            feedback.pushInfo('fGeom: ' + str(fGeom))
            totLineLength = da.measureLength(fGeom)
            feedback.pushInfo('fGeom totLineLength: ' + str(totLineLength))
            # Explode multi part
            if fGeom.isMultipart():
                for aLine in fGeom.asMultiPolyline():
                    lineGeoms.append(aLine)
                #lines = fGeom.asMultiPolyline()
                # pick random line
                #lineId = random.randint(0, len(lines) - 1)
                #vertices = lines[lineId]
            else:
                lineGeoms.append(fGeom.asPolyline())
                #vertices = fGeom.asPolyline()
            feedback.pushInfo('lineGeoms: ' + str(lineGeoms))


            # Generate points on the line geometry / geometries
            nPoints = 0
            nIterations = 0
            while nIterations < maxIterations and nPoints < pointCount:
                if feedback.isCanceled():
                    break
                #feedback.pushInfo('nIterations: ' + str(nIterations))
                # Get the random "position" for this point
                randomLength = random.random() * totLineLength
                feedback.pushInfo('randomLength: ' + str(randomLength))
                currLength = 0
                prefLength = 0
                # Go through the parts
                for l in lineGeoms:
                    if feedback.isCanceled():
                        break
                    currGeom = QgsGeometry.fromPolylineXY(l)
                    #lineLength = da.measureLength(QgsGeometry.fromPolylineXY(l))
                    lineLength = da.measureLength(currGeom)
                    prevLength = currLength
                    currLength += lineLength
                    feedback.pushInfo('l lineLength: ' + str(lineLength) + ' currLength: ' + str(currLength))
                    vertices = l
                    # Skip if this is not the "selected" part
                    if currLength < randomLength:
                        continue
                    #randomLength -= currLength
                    #vertices = QgsGeometry.fromPolylineXY(l)
                    feedback.pushInfo('l/vertices: ' + str(vertices))

                    #randomLength = random.random() * lineLength
                    #distanceToVertex(vid)

                    # find the segment for the new point
                    # and calculate the offset (remainDistance) on that segment
                    remainDist = randomLength - prevLength
                    feedback.pushInfo('remainDist1: ' + str(remainDist))
                    if len(vertices) == 2:
                        vid = 0
                        #remainDist = randomLength - currLength
                    else:
                        vid = 0
                        #while (fGeom.distanceToVertex(vid)) < randomLength:
                        #while (currGeom.distanceToVertex(vid)) < randomLength:
                        currDist = currGeom.distanceToVertex(vid)
                        prevDist = currDist
                        while currDist < remainDist and vid < len(vertices):
                            vid += 1
                            prevDist = currDist
                            currDist = currGeom.distanceToVertex(vid)
                            feedback.pushInfo('currdist: ' + str(currDist) + ' vid: ' + str(vid))
                        if vid == len(vertices):
                            feedback.pushInfo('**** vid = len(vertices)! ****')
                        vid -= 1
                        feedback.pushInfo('currdist2: ' + str(currDist) + ' vid: ' + str(vid))
                        remainDist = remainDist - prevDist
                    feedback.pushInfo('remainDist2: ' + str(remainDist))
                    if remainDist <= 0:
                        continue
                    startPoint = vertices[vid]
                    endPoint = vertices[vid + 1]
                    length = da.measureLine(startPoint, endPoint)
                    # if remainDist > minDistance:
                    d = remainDist / (length - remainDist)
                    rx = (startPoint.x() + d * endPoint.x()) / (1 + d)
                    ry = (startPoint.y() + d * endPoint.y()) / (1 + d)

                    # generate random point
                    p = QgsPointXY(rx, ry)
                    geom = QgsGeometry.fromPointXY(p)
                    if vector.checkMinDistance(p, index, minDistance, points):
                        f = QgsFeature(totNPoints)
                        f.initAttributes(1)
                        f.setFields(fields)
                        f.setAttribute('id', totNPoints)
                        f.setGeometry(geom)
                        sink.addFeature(f, QgsFeatureSink.FastInsert)
                        index.addFeature(f)
                        points[nPoints] = p
                        nPoints += 1
                        totNPoints += 1
                        feedback.setProgress(int(totNPoints * total))
                    break
                nIterations += 1

            if nPoints < pointCount:
                #feedback.pushInfo(self.tr('Could not generate requested number of random points. '
                feedback.reportError(self.tr('Could not generate requested number of random points. '
                                             'Maximum number of attempts exceeded.'), False)

        return {self.OUTPUT: dest_id, self.OUTPUT_POINTS: nPoints}
示例#15
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context)

        fields = QgsFields()
        fields.append(QgsField('id', QVariant.Int, '', 10, 0))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.Point, source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        nPoints = 0
        nIterations = 0
        maxIterations = pointCount * 200
        featureCount = source.featureCount()
        total = 100.0 / pointCount if pointCount else 1

        index = QgsSpatialIndex()
        points = dict()

        da = QgsDistanceArea()
        da.setSourceCrs(source.sourceCrs(), context.transformContext())
        da.setEllipsoid(context.project().ellipsoid())

        request = QgsFeatureRequest()

        random.seed()

        while nIterations < maxIterations and nPoints < pointCount:
            if feedback.isCanceled():
                break

            # pick random feature
            fid = random.randint(0, featureCount - 1)
            f = next(source.getFeatures(request.setFilterFid(fid).setSubsetOfAttributes([])))
            fGeom = f.geometry()

            if fGeom.isMultipart():
                lines = fGeom.asMultiPolyline()
                # pick random line
                lineId = random.randint(0, len(lines) - 1)
                vertices = lines[lineId]
            else:
                vertices = fGeom.asPolyline()

            # pick random segment
            if len(vertices) == 2:
                vid = 0
            else:
                vid = random.randint(0, len(vertices) - 2)
            startPoint = vertices[vid]
            endPoint = vertices[vid + 1]
            length = da.measureLine(startPoint, endPoint)
            dist = length * random.random()

            if dist > minDistance:
                d = dist / (length - dist)
                rx = (startPoint.x() + d * endPoint.x()) / (1 + d)
                ry = (startPoint.y() + d * endPoint.y()) / (1 + d)

                # generate random point
                p = QgsPointXY(rx, ry)
                geom = QgsGeometry.fromPointXY(p)
                if vector.checkMinDistance(p, index, minDistance, points):
                    f = QgsFeature(nPoints)
                    f.initAttributes(1)
                    f.setFields(fields)
                    f.setAttribute('id', nPoints)
                    f.setGeometry(geom)
                    sink.addFeature(f, QgsFeatureSink.FastInsert)
                    index.addFeature(f)
                    points[nPoints] = p
                    nPoints += 1
                    feedback.setProgress(int(nPoints * total))
            nIterations += 1

        if nPoints < pointCount:
            feedback.pushInfo(self.tr('Could not generate requested number of random points. '
                                      'Maximum number of attempts exceeded.'))

        return {self.OUTPUT: dest_id}
示例#16
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context)

        expression = QgsExpression(self.parameterAsString(parameters, self.EXPRESSION, context))
        if expression.hasParserError():
            raise QgsProcessingException(expression.parserErrorString())

        expressionContext = self.createExpressionContext(parameters, context, source)
        expression.prepare(expressionContext)

        fields = QgsFields()
        fields.append(QgsField('id', QVariant.Int, '', 10, 0))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.Point, source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey)
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        da = QgsDistanceArea()
        da.setSourceCrs(source.sourceCrs(), context.transformContext())
        da.setEllipsoid(context.project().ellipsoid())

        total = 100.0 / source.featureCount() if source.featureCount() else 0
        current_progress = 0
        for current, f in enumerate(source.getFeatures()):
            if feedback.isCanceled():
                break

            if not f.hasGeometry():
                continue

            current_progress = total * current
            feedback.setProgress(current_progress)

            expressionContext.setFeature(f)
            value = expression.evaluate(expressionContext)
            if expression.hasEvalError():
                feedback.pushInfo(
                    self.tr('Evaluation error for feature ID {}: {}').format(f.id(), expression.evalErrorString()))
                continue

            fGeom = f.geometry()
            engine = QgsGeometry.createGeometryEngine(fGeom.constGet())
            engine.prepareGeometry()

            bbox = fGeom.boundingBox()
            if strategy == 0:
                pointCount = int(value)
            else:
                pointCount = int(round(value * da.measureArea(fGeom)))

            if pointCount == 0:
                feedback.pushInfo("Skip feature {} as number of points for it is 0.".format(f.id()))
                continue

            index = QgsSpatialIndex()
            points = dict()

            nPoints = 0
            nIterations = 0
            maxIterations = pointCount * 200
            feature_total = total / pointCount if pointCount else 1

            random.seed()

            while nIterations < maxIterations and nPoints < pointCount:
                if feedback.isCanceled():
                    break

                rx = bbox.xMinimum() + bbox.width() * random.random()
                ry = bbox.yMinimum() + bbox.height() * random.random()

                p = QgsPointXY(rx, ry)
                geom = QgsGeometry.fromPointXY(p)
                if engine.contains(geom.constGet()) and \
                        vector.checkMinDistance(p, index, minDistance, points):
                    f = QgsFeature(nPoints)
                    f.initAttributes(1)
                    f.setFields(fields)
                    f.setAttribute('id', nPoints)
                    f.setGeometry(geom)
                    sink.addFeature(f, QgsFeatureSink.FastInsert)
                    index.addFeature(f)
                    points[nPoints] = p
                    nPoints += 1
                    feedback.setProgress(current_progress + int(nPoints * feature_total))
                nIterations += 1

            if nPoints < pointCount:
                feedback.pushInfo(self.tr('Could not generate requested number of random '
                                          'points. Maximum number of attempts exceeded.'))

        feedback.setProgress(100)

        return {self.OUTPUT: dest_id}
示例#17
0
class sGraph(QObject):
    finished = pyqtSignal(object)
    error = pyqtSignal(Exception, str)
    progress = pyqtSignal(float)
    warning = pyqtSignal(str)
    killed = pyqtSignal(bool)

    def __init__(self, edges={}, nodes={}):
        QObject.__init__(self)
        self.sEdges = edges
        self.sNodes = nodes  # can be empty
        self.total_progress = 0
        self.step = 0

        if len(self.sEdges) == 0:
            self.edge_id = 0
            self.sNodesCoords = {}
            self.node_id = 0
        else:
            self.edge_id = max(self.sEdges.keys())
            self.node_id = max(self.sNodes.keys())
            self.sNodesCoords = {snode.getCoords(): snode.id for snode in list(self.sNodes.values())}

        self.edgeSpIndex = QgsSpatialIndex()
        self.ndSpIndex = QgsSpatialIndex()
        res = [self.edgeSpIndex.addFeature(sedge.feature) for sedge in list(self.sEdges.values())]
        del res

        self.errors = []
        # breakages, orphans, merges, snaps, duplicate, points, mlparts
        self.unlinks = []
        self.points = []
        self.multiparts = []

    # graph from feat iter
    # updates the id
    def load_edges(self, feat_iter, angle_threshold):

        for f in feat_iter:

            if self.killed is True:
                break

            # add edge
            geometry = f.geometry().simplify(angle_threshold)
            geometry_pl = geometry.asPolyline()
            startpoint = geometry_pl[0]
            endpoint = geometry_pl[-1]
            start = self.load_point(startpoint)
            end = self.load_point(endpoint)
            snodes = [start, end]
            self.edge_id += 1
            self.update_topology(snodes[0], snodes[1], self.edge_id)

            f.setId(self.edge_id)
            f.setGeometry(geometry)
            sedge = sEdge(self.edge_id, f, snodes)
            self.sEdges[self.edge_id] = sedge

        return

    # pseudo graph from feat iter (only clean features - ids are fixed)
    def load_edges_w_o_topology(self, clean_feat_iter):

        for f in clean_feat_iter:

            if self.killed is True:
                break

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            # add edge
            sedge = sEdge(f.id(), f, [])
            self.sEdges[f.id()] = sedge
            self.edgeSpIndex.addFeature(f)

        self.edge_id = f.id()
        return

    # find existing or generate new node
    def load_point(self, point):
        try:
            node_id = self.sNodesCoords[(point[0], point[1])]
        except KeyError:
            self.node_id += 1
            node_id = self.node_id
            feature = QgsFeature()
            feature.setId(node_id)
            feature.setAttributes([node_id])
            feature.setGeometry(QgsGeometry.fromPointXY(point))
            self.sNodesCoords[(point[0], point[1])] = node_id
            snode = sNode(node_id, feature, [], [])
            self.sNodes[self.node_id] = snode
        return node_id

    # store topology
    def update_topology(self, node1, node2, edge):
        self.sNodes[node1].topology.append(node2)
        self.sNodes[node1].adj_edges.append(edge)
        self.sNodes[node2].topology.append(node1)
        self.sNodes[node2].adj_edges.append(edge)
        return

    # delete point
    def delete_node(self, node_id):
        del self.sNodes[node_id]
        return True

    def remove_edge(self, nodes, e):
        self.sNodes[nodes[0]].adj_edges.remove(e)
        self.sNodes[nodes[0]].topology.remove(nodes[1])
        self.sNodes[nodes[1]].adj_edges.remove(e)  # if self loop - removed twice
        self.sNodes[nodes[1]].topology.remove(nodes[0])  # if self loop - removed twice
        del self.sEdges[e]
        # spIndex self.edgeSpIndex.deleteFeature(self.sEdges[e].feature)
        return

    # create graph (broken_features_iter)
    # can be applied to edges w-o topology for speed purposes
    def break_features_iter(self, getUnlinks, angle_threshold, fix_unlinks=False):

        for sedge in list(self.sEdges.values()):

            if self.killed is True:
                break

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            f = sedge.feature
            f_geom = f.geometry()
            pl = f_geom.asPolyline()
            lines = [line for line in self.edgeSpIndex.intersects(f_geom.boundingBox()) if line != f.id()]

            # self intersections
            # include first and last
            self_intersections = uf.getSelfIntersections(pl)

            # common vertices
            intersections = list(itertools.chain.from_iterable(
                [set(pl[1:-1]).intersection(set(self.sEdges[line].feature.geometry().asPolyline())) for line in lines]))
            intersections += self_intersections
            intersections = (set(intersections))

            if len(intersections) > 0:
                # broken features iterator
                # errors
                for pnt in intersections:
                    err_f = QgsFeature(error_feat)
                    err_f.setGeometry(QgsGeometry.fromPointXY(pnt))
                    err_f.setAttributes(['broken'])
                    self.errors.append(err_f)
                vertices_indices = uf.find_vertex_indices(pl, intersections)
                for start, end in zip(vertices_indices[:-1], vertices_indices[1:]):
                    broken_feat = QgsFeature(f)
                    broken_geom = QgsGeometry.fromPolylineXY(pl[start:end + 1]).simplify(angle_threshold)
                    broken_feat.setGeometry(broken_geom)
                    yield broken_feat
            else:
                simpl_geom = f.geometry().simplify(angle_threshold)
                f.setGeometry(simpl_geom)
                yield f

    def fix_unlinks(self):

        self.edgeSpIndex = QgsSpatialIndex()
        self.step = self.step / 2.0

        for e in list(self.sEdges.values()):
            if self.killed is True:
                break

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            self.edgeSpIndex.addFeature(e.feature)

        for sedge in list(self.sEdges.values()):

            if self.killed is True:
                break

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            f = sedge.feature
            f_geom = f.geometry()
            pl = f_geom.asPolyline()
            lines = [line for line in self.edgeSpIndex.intersects(f_geom.boundingBox()) if line != f.id()]
            lines = [line for line in lines if f_geom.crosses(self.sEdges[line].feature.geometry())]
            for line in lines:
                crossing_points = f_geom.intersection(self.sEdges[line].feature.geometry())
                if crossing_points.type() == QgsWkbTypes.PointGeometry:
                    if not crossing_points.isMultipart():
                        if crossing_points.asPoint() in pl[1:-1]:
                            edge_geometry = self.sEdges[sedge.id].feature.geometry()
                            edge_geometry.moveVertex(crossing_points.asPoint().x() + 1,
                                                     crossing_points.asPoint().y() + 1,
                                                     pl.index(crossing_points.asPoint()))
                            self.sEdges[sedge.id].feature.setGeometry(edge_geometry)
                    else:
                        for p in crossing_points.asMultiPoint():
                            if p in pl[1:-1]:
                                edge_geometry = self.sEdges[sedge.id].feature.geometry()
                                edge_geometry.moveVertex(p.x() + 1,
                                                         p.y() + 1,
                                                         pl.index(p))
                                self.sEdges[sedge.id].feature.setGeometry(edge_geometry)
            # TODO: exclude vertices - might be in one of the lines

        return

    def con_comp_iter(self, group_dictionary):
        components_passed = set([])
        for id in list(group_dictionary.keys()):

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            if {id}.isdisjoint(components_passed):
                group = [[id]]
                candidates = ['dummy', 'dummy']
                while len(candidates) > 0:
                    flat_group = group[:-1] + group[-1]
                    candidates = [set(group_dictionary[last_visited_node]).difference(set(flat_group)) for
                                  last_visited_node in group[-1]]
                    candidates = list(set(itertools.chain.from_iterable(candidates)))
                    group = flat_group + [candidates]
                    components_passed.update(set(candidates))
                yield group[:-1]

    # group points based on proximity - spatial index is not updated
    def snap_endpoints(self, snap_threshold):
        QgsMessageLog.logMessage('starting snapping', level=Qgis.Critical)
        res = [self.ndSpIndex.addFeature(snode.feature) for snode in list(self.sNodes.values())]
        filtered_nodes = {}
        # exclude nodes where connectivity = 2 - they will be merged
        self.step = self.step / float(2)
        for node in [n for n in list(self.sNodes.values()) if n.adj_edges != 2]:
            if self.killed is True:
                break

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            # find nodes within x distance
            node_geom = node.feature.geometry()
            nodes = [nd for nd in self.ndSpIndex.intersects(node_geom.buffer(snap_threshold, 10).boundingBox()) if
                     nd != node.id and node_geom.distance(self.sNodes[nd].feature.geometry()) <= snap_threshold]
            if len(nodes) > 0:
                filtered_nodes[node.id] = nodes

        QgsMessageLog.logMessage('continuing snapping', level=Qgis.Critical)
        self.step = (len(filtered_nodes) * self.step) / float(len(self.sNodes))
        for group in self.con_comp_iter(filtered_nodes):

            if self.killed is True:
                break

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            # find con_edges
            con_edges = set(itertools.chain.from_iterable([self.sNodes[node].adj_edges for node in group]))

            # collapse nodes to node
            merged_node_id, centroid_point = self.collapse_to_node(group)

            # update connected edges and their topology
            for edge in con_edges:
                sedge = self.sEdges[edge]
                start, end = sedge.nodes
                # if existing self loop
                if start == end:  # and will be in group
                    if sedge.feature.geometry().length() <= snap_threshold:  # short self-loop
                        self.remove_edge((start, end), edge)
                    else:
                        self.sEdges[edge].replace_start(self.node_id, centroid_point)
                        self.update_topology(merged_node_id, merged_node_id, edge)
                        self.sNodes[end].topology.remove(start)
                        self.sEdges[edge].replace_end(self.node_id, centroid_point)
                        self.sNodes[start].topology.remove(end)
                    # self.sNodes[start].topology.remove(end)
                # if becoming self loop (if one intermediate vertex - turns back on itself)
                elif start in group and end in group:
                    if (len(sedge.feature.geometry().asPolyline()) <= 3
                            or sedge.feature.geometry().length() <= snap_threshold):
                        self.remove_edge((start, end), edge)
                    else:
                        self.sEdges[edge].replace_start(self.node_id, centroid_point)
                        self.sEdges[edge].replace_end(self.node_id, centroid_point)
                        self.update_topology(merged_node_id, merged_node_id, edge)
                        self.sNodes[end].topology.remove(start)
                        self.sNodes[start].topology.remove(end)
                # if only start
                elif start in group:
                    self.sEdges[edge].replace_start(self.node_id, centroid_point)
                    self.sNodes[merged_node_id].topology.append(end)
                    self.sNodes[merged_node_id].adj_edges.append(edge)
                    self.sNodes[end].topology.append(merged_node_id)
                    self.sNodes[end].topology.remove(start)
                # if only end
                elif end in group:
                    self.sEdges[edge].replace_end(self.node_id, centroid_point)
                    self.sNodes[merged_node_id].topology.append(start)
                    self.sNodes[merged_node_id].adj_edges.append(edge)
                    self.sNodes[start].topology.append(merged_node_id)
                    self.sNodes[start].topology.remove(end)

            # errors
            for node in group:
                err_f = QgsFeature(error_feat)
                err_f.setGeometry(self.sNodes[node].feature.geometry())
                err_f.setAttributes(['snapped'])
                self.errors.append(err_f)

            # delete old nodes
            res = [self.delete_node(item) for item in group]

        return

    def collapse_to_node(self, group):

        # create new node, coords
        self.node_id += 1
        feat = QgsFeature()
        centroid = (
            QgsGeometry.fromMultiPointXY([self.sNodes[nd].feature.geometry().asPoint() for nd in group])).centroid()
        feat.setGeometry(centroid)
        feat.setAttributes([self.node_id])
        feat.setId(self.node_id)
        snode = sNode(self.node_id, feat, [], [])
        self.sNodes[self.node_id] = snode
        self.ndSpIndex.addFeature(feat)

        return self.node_id, centroid.asPoint()

    # TODO add agg_cost
    def route_nodes(self, group, step):
        count = 1
        group = [group]
        while count <= step:
            last_visited = group[-1]
            group = group[:-1] + group[-1]
            con_nodes = set(itertools.chain.from_iterable(
                [self.sNodes[last_node].topology for last_node in last_visited])).difference(group)
            group += [con_nodes]
            count += 1
            for nd in con_nodes:
                yield count - 1, nd

    def route_edges(self, group, step):
        count = 1
        group = [group]
        while count <= step:
            last_visited = group[-1]
            group = group[:-1] + group[-1]
            con_edges = set(
                itertools.chain.from_iterable([self.sNodes[last_node].topology for last_node in last_visited]))
            con_nodes = [con_node for con_node in con_nodes if con_node not in group]
            group += [con_nodes]
            count += 1
            # TODO: return circles
            for dg in con_edges:
                yield count - 1, nd, dg

    # TODO: snap_geometries (not endpoints)
    # TODO: extend

    def clean_dupl(self, group_edges, snap_threshold, parallel=False):

        self.total_progress += self.step
        self.progress.emit(self.total_progress)

        # keep line with minimum length
        # TODO: add distance centroids
        lengths = [self.sEdges[e].feature.geometry().length() for e in group_edges]
        sorted_edges = [x for _, x in sorted(zip(lengths, group_edges))]
        min_len = min(lengths)

        # if parallel is False:
        prl_dist_threshold = 0
        # else:
        #    dist_threshold = snap_threshold
        for e in sorted_edges[1:]:
            # delete line
            if abs(self.sEdges[e].feature.geometry().length() - min_len) <= prl_dist_threshold:
                for p in set([self.sNodes[n].feature.geometry() for n in self.sEdges[e].nodes]):
                    err_f = QgsFeature(error_feat)
                    err_f.setGeometry(p)
                    err_f.setAttributes(['duplicate'])
                    self.errors.append(err_f)
                self.remove_edge(self.sEdges[e].nodes, e)
        return

    def clean_multipart(self, e):

        self.total_progress += self.step
        self.progress.emit(self.total_progress)

        # only used in the last cleaning iteration - only updates self.sEdges and spIndex (allowed to be used once in the end)
        # nodes are not added to the new edges

        multi_poly = e.feature.geometry().asMultiPolyline()

        for singlepart in multi_poly:
            # create new edge and update spIndex
            single_geom = QgsGeometry.fromPolylineXY(singlepart)
            single_feature = QgsFeature(e.feature)
            single_feature.setGeometry(single_geom)
            self.edge_id += 1
            single_feature.setId(self.edge_id)
            self.sEdges[self.edge_id] = sEdge(self.edge_id, single_feature, [])
            self.edgeSpIndex.addFeature(single_feature)

            if len(multi_poly) >= 1:
                # add points as multipart errors if there was actually more than one line
                for p in single_geom.asPolyline():
                    err_f = QgsFeature(error_feat)
                    err_f.setGeometry(QgsGeometry.fromPointXY(p))
                    err_f.setAttributes(['multipart'])
                    self.errors.append(err_f)

        # delete old feature - spIndex

        self.edgeSpIndex.deleteFeature(self.sEdges[e.id].feature)
        del self.sEdges[e.id]

        return

    def clean_orphan(self, e):

        self.total_progress += self.step
        self.progress.emit(self.total_progress)

        nds = e.nodes
        snds = self.sNodes[nds[0]], self.sNodes[nds[1]]
        # connectivity of both endpoints 1
        # if parallel - A:[B,B]
        # if selfloop to line - A: [A,A, C]
        # if selfloop
        # if selfloop and parallel
        if len(set(snds[0].topology)) == len(set(snds[1].topology)) == 1 and len(set(snds[0].adj_edges)) == 1:
            del self.sEdges[e.id]
            for nd in set(nds):
                err_f = QgsFeature(error_feat)
                err_f.setGeometry(self.sNodes[nd].feature.geometry())
                err_f.setAttributes(['orphan'])
                self.errors.append(err_f)
                del self.sNodes[nd]
        return True

    # find duplicate geometries
    # find orphans

    def clean(self, duplicates, orphans, snap_threshold, closed_polylines, multiparts=False):
        # clean duplicates - delete longest from group using snap threshold
        step_original = float(self.step)
        if duplicates:
            input = [(e.id, frozenset(e.nodes)) for e in list(self.sEdges.values())]
            groups = defaultdict(list)
            for v, k in input: groups[k].append(v)

            dupl_candidates = dict([nodes_edges for nodes_edges in list(groups.items()) if len(nodes_edges[1]) > 1])

            self.step = (len(dupl_candidates) * self.step) / float(len(self.sEdges))
            for (nodes, group_edges) in list(dupl_candidates.items()):

                if self.killed is True:
                    break

                self.total_progress += self.step
                self.progress.emit(self.total_progress)
                self.clean_dupl(group_edges, snap_threshold, False)

        self.step = step_original
        # clean orphans
        if orphans:
            for e in list(self.sEdges.values()):

                if self.killed is True:
                    break

                self.total_progress += self.step
                self.progress.emit(self.total_progress)

                self.clean_orphan(e)

        # clean orphan closed polylines
        elif closed_polylines:

            for e in list(self.sEdges.values()):

                if self.killed is True:
                    break

                self.total_progress += self.step
                self.progress.emit(self.total_progress)

                if len(set(e.nodes)) == 1:
                    self.clean_orphan(e)

        # break multiparts
        if multiparts:
            for e in list(self.sEdges.values()):

                if self.killed is True:
                    break

                self.total_progress += self.step
                self.progress.emit(self.total_progress)

                if e.feature.geometry().type() == QgsWkbTypes.LineGeometry and \
                        e.feature.geometry().isMultipart():
                    self.clean_multipart(e)

        return

    # merge

    def merge_b_intersections(self, angle_threshold):

        # special cases: merge parallels (becomes orphan)
        # do not merge two parallel self loops

        edges_passed = set([])

        for e in self.edge_edges_iter():
            if {e}.isdisjoint(edges_passed):
                edges_passed.update({e})
                group_nodes, group_edges = self.route_polylines(e)
                if group_edges:
                    edges_passed.update({group_edges[-1]})
                    self.merge_edges(group_nodes, group_edges, angle_threshold)
        return

    def merge_collinear(self, collinear_threshold, angle_threshold=0):

        filtered_nodes = dict([id_nd for id_nd in list(graph.sNodes.items()) if
                               len(id_nd[1].topology) == 2 and len(id_nd[1].adj_edges) == 2])
        filtered_nodes = dict([id_nd1 for id_nd1 in list(filtered_nodes.items()) if
                               uf.angle_3_points(graph.sNodes[id_nd1[1].topology[0]].feature.geometry().asPoint(),
                                                 id_nd1[1].feature.geometry().asPoint(), graph.sNodes[
                                                     id_nd1[1].topology[
                                                         1]].feature.geometry().asPoint()) <= collinear_threshold])
        filtered_nodes = {id: nd.adj_edges for id, nd in list(filtered_nodes.items())}
        filtered_edges = {}
        for k, v in list(filtered_nodes.items()):
            try:
                filtered_edges[v[0]].append(v[1])
            except KeyError:
                filtered_edges[v[0]] = [v[1]]
            try:
                filtered_edges[v[1]].append(v[0])
            except KeyError:
                filtered_edges[v[1]] = [v[0]]

        self.step = (len(filtered_edges) * self.step) / float(len(self.sEdges))

        for group in self.collinear_comp_iter(filtered_edges):
            nodes = [self.sEdges[e].nodes for e in group]
            for idx, pair in enumerate(nodes[:-1]):
                if pair[0] in nodes[idx + 1]:
                    nodes[idx] = pair[::-1]
            if nodes[-1][1] in nodes[-2]:
                nodes[-1] = nodes[-1][::-1]
            nodes = [n[0] for n in nodes] + [nodes[-1][-1]]
            self.merge_edges(nodes, group, angle_threshold)
        return

    def collinear_comp_iter(self, group_dictionary):
        components_passed = set([])
        for id, top in list(group_dictionary.items()):

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            if {id}.isdisjoint(components_passed) and len(top) != 2:
                group = [[id]]
                candidates = ['dummy', 'dummy']
                while len(candidates) > 0:
                    flat_group = group[:-1] + group[-1]
                    candidates = [set(group_dictionary[last_visited_node]).difference(set(flat_group)) for
                                  last_visited_node in group[-1]]
                    candidates = list(set(itertools.chain.from_iterable(candidates)))
                    group = flat_group + [candidates]
                    components_passed.update(set(candidates))
                yield group[:-1]

    def edge_edges_iter(self):
        # what if two parallel edges at the edge - should become self loop
        for nd_id, nd in list(self.sNodes.items()):

            if self.killed is True:
                break

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            con_edges = nd.adj_edges
            if len(nd.topology) != 2 and len(con_edges) != 2:  # not set to include parallels and self loops
                for e in con_edges:
                    yield e

    def route_polylines(self, startedge):
        # if edge has been passed
        startnode, endnode = self.sEdges[startedge].nodes
        if len(self.sNodes[endnode].topology) != 2:  # not set to account for self loops
            startnode, endnode = endnode, startnode
        group_nodes = [startnode, endnode]
        group_edges = [startedge]
        while len(set(self.sNodes[group_nodes[-1]].adj_edges)) == 2:
            last_visited = group_nodes[-1]
            if last_visited in self.sNodes[last_visited].topology:  # to account for self loops
                break
            con_edge = set(self.sNodes[last_visited].adj_edges).difference(set(group_edges)).pop()
            con_node = [n for n in self.sEdges[con_edge].nodes if n != last_visited][0]  # to account for self loops
            group_nodes.append(con_node)
            group_edges.append(con_edge)
        if len(group_nodes) > 2:
            return group_nodes, group_edges
        else:
            return None, None

    def generate_unlinks(self):  # for osm or other

        # spIndex # TODO change OTF - insert/delete feature
        self.edgeSpIndex = QgsSpatialIndex()

        self.step = self.step / float(4)
        for e in list(self.sEdges.values()):
            if self.killed is True:
                break

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            self.edgeSpIndex.addFeature(e.feature)

        unlinks_id = 0
        self.step = float(3.0) * self.step
        for id, e in list(self.sEdges.items()):

            if self.killed is True:
                break

            self.total_progress += self.step
            self.progress.emit(self.total_progress)

            f_geom = e.feature.geometry()
            # to avoid duplicate unlinks - id > line
            lines = [line for line in self.edgeSpIndex.intersects(f_geom.boundingBox()) if
                     f_geom.crosses(self.sEdges[line].feature.geometry()) and id > line]

            for line in lines:
                crossing_points = f_geom.intersection(self.sEdges[line].feature.geometry())
                # in some cases the startpoint or endpoint is returned - exclude
                if crossing_points.type() == QgsWkbTypes.PointGeometry:
                    if not crossing_points.isMultipart():
                        un_f = QgsFeature(unlink_feat)
                        un_f.setGeometry(crossing_points)
                        un_f.setId(unlinks_id)
                        un_f.setAttributes([unlinks_id])
                        unlinks_id += 1
                        self.unlinks.append(un_f)
                    else:
                        for p in crossing_points.asMultiPoint():
                            if p not in f_geom.asPolyline():
                                un_f = QgsFeature(unlink_feat)
                                un_f.setGeometry(QgsGeometry.fromPointXY(p))
                                un_f.setId(unlinks_id)
                                un_f.setAttributes([unlinks_id])
                                unlinks_id += 1
                                self.unlinks.append(un_f)
        return

    # TODO: features added - pass through clean_iterator (can be ml line)
    def merge_edges(self, group_nodes, group_edges, angle_threshold):

        geoms = [self.sEdges[e].feature.geometry() for e in group_edges]
        lengths = [g.length() for g in geoms]
        max_len = max(lengths)

        # merge edges
        self.edge_id += 1
        feat = QgsFeature()
        # attributes from longest
        longest_feat = self.sEdges[group_edges[lengths.index(max_len)]].feature
        feat.setAttributes(longest_feat.attributes())
        merged_geom = uf.merge_geoms(geoms, angle_threshold)
        if merged_geom.type() == QgsWkbTypes.LineGeometry:
            if not merged_geom.isMultipart():
                p0 = merged_geom.asPolyline()[0]
                p1 = merged_geom.asPolyline()[-1]
            else:
                p0 = merged_geom.asMultiPolyline()[0][0]
                p1 = merged_geom.asMultiPolyline()[-1][-1]

        # special case - if self loop breaks at intersection of other line & then merged back on old self loop point
        # TODO: include in merged_geoms functions to make indepedent
        selfloop_point = self.sNodes[group_nodes[0]].feature.geometry().asPoint()
        if p0 == p1 and p0 != selfloop_point:
            merged_points = geoms[0].asPolyline()
            geom1 = self.sEdges[group_edges[0]].feature.geometry().asPolyline()
            if not geom1[0] == selfloop_point:
                merged_points = merged_points[::-1]
            for geom in geoms[1:]:
                points = geom.asPolyline()
                if not points[0] == merged_points[-1]:
                    merged_points += (points[::-1])[1:]
                else:
                    merged_points += points[1:]
            merged_geom = QgsGeometry.fromPolylineXY(merged_points)
            if merged_geom.wkbType() != QgsWkbTypes.LineString:
                print('ml', merged_geom.wkbType())

        feat.setGeometry(merged_geom)
        feat.setId(self.edge_id)

        if p0 == self.sNodes[group_nodes[0]].feature.geometry().asPoint():
            merged_edge = sEdge(self.edge_id, feat, [group_nodes[0], group_nodes[-1]])
        else:
            merged_edge = sEdge(self.edge_id, feat, [group_nodes[-1], group_nodes[0]])
        self.sEdges[self.edge_id] = merged_edge

        # update ends
        self.sNodes[group_nodes[0]].topology.remove(group_nodes[1])
        self.update_topology(group_nodes[0], group_nodes[-1], self.edge_id)
        # if group_nodes == [group_nodes[0], group_nodes[1], group_nodes[0]]:
        self.sNodes[group_nodes[-1]].topology.remove(group_nodes[-2])
        self.sNodes[group_nodes[0]].adj_edges.remove(group_edges[0])
        self.sNodes[group_nodes[-1]].adj_edges.remove(group_edges[-1])

        # middle nodes del
        for nd in group_nodes[1:-1]:
            err_f = QgsFeature(error_feat)
            err_f.setGeometry(self.sNodes[nd].feature.geometry())
            err_f.setAttributes(['merged'])
            self.errors.append(err_f)
            del self.sNodes[nd]

        # del edges
        for e in group_edges:
            del self.sEdges[e]

        return

    def simplify_circles(self):
        roundabouts = NULL
        short = NULL
        res = [self.collapse_to_node(group) for group in con_components(roundabouts + short)]
        return

    def simplify_parallel_lines(self):
        dual_car = NULL
        res = [self.collapse_to_medial_axis(group) for group in con_components(dual_car)]
        pass

    def collapse_to_medial_axis(self):
        pass

    def simplify_angle(self, max_angle_threshold):
        pass

    def catchment_iterator(self, origin_point, closest_edge, cost_limit, origin_name):
        # find closest line
        edge_geom = self.sEdges[closest_edge].feature.geometry()
        nodes = set(self.sEdges[closest_edge].nodes)

        # endpoints
        branches = []
        shortest_line = origin_point.shortestLine(edge_geom)
        point_on_line = shortest_line.intersection(edge_geom)
        fraction = edge_geom.lineLocatePoint(point_on_line)
        fractions = [fraction, 1 - fraction]
        degree = 0
        for node, fraction in zip(nodes, fractions):
            branches.append((None, node, closest_edge, self.sNodes[node].feature.geometry().distance(point_on_line),))

        for k in list(self.sEdges.keys()):
            self.sEdges[k].visited[origin_name] = None

        self.sEdges[closest_edge].visited[origin_name] = True

        while len(branches) > 0:
            branches = [nbr for (org, dest, edge, agg_cost) in branches if agg_cost < cost_limit and dest != [] for nbr
                        in self.get_next_edges(dest, agg_cost, origin_name)]

            # fraction = 1 - ((agg_cost - cost_limit) / float(cost_limit))
            # degree += 1

    def get_next_edges(self, old_dest, agg_cost, origin_name):
        new_origin = old_dest[0]
        new_branches = []
        for edg in set(self.sNodes[new_origin].adj_edges):
            sedge = self.sEdges[edg]
            if sedge.visited[origin_name] is None:
                sedge.visited[origin_name] = new_origin
                new_agg_cost = agg_cost + sedge.len
                sedge.agg_cost[origin_name] = new_agg_cost
                self.sEdges[edg] = sedge
                new_dest = [n for n in sedge.nodes if n != new_origin]
                new_branches.append((new_origin, new_dest, edg, new_agg_cost))
        return new_branches

    def kill(self):
        self.killed = True
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               source.fields(), source.wkbType(), source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]))

        total = 100.0 / source.featureCount() if source.featureCount() else 0
        geoms = dict()
        null_geom_features = set()
        index = QgsSpatialIndex()
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            if not f.hasGeometry():
                null_geom_features.add(f.id())
                continue

            geoms[f.id()] = f.geometry()
            index.addFeature(f)

            feedback.setProgress(int(0.10 * current * total)) # takes about 10% of time

        # start by assuming everything is unique, and chop away at this list
        unique_features = dict(geoms)

        current = 0
        removed = 0
        for feature_id, geometry in geoms.items():
            if feedback.isCanceled():
                break

            if feature_id not in unique_features:
                # feature was already marked as a duplicate
                continue

            candidates = index.intersects(geometry.boundingBox())
            candidates.remove(feature_id)

            for candidate_id in candidates:
                if candidate_id not in unique_features:
                    # candidate already marked as a duplicate (not sure if this is possible,
                    # since it would mean the current feature would also have to be a duplicate!
                    # but let's be safe!)
                    continue

                if geometry.isGeosEqual(geoms[candidate_id]):
                    # candidate is a duplicate of feature
                    del unique_features[candidate_id]
                    removed += 1

            current += 1
            feedback.setProgress(int(0.80 * current * total) + 10)  # takes about 80% of time

        # now, fetch all the feature attributes for the unique features only
        # be super-smart and don't re-fetch geometries
        distinct_geoms = set(unique_features.keys())
        output_feature_ids = distinct_geoms.union(null_geom_features)
        total = 100.0 / len(output_feature_ids) if output_feature_ids else 1

        request = QgsFeatureRequest().setFilterFids(list(output_feature_ids)).setFlags(QgsFeatureRequest.NoGeometry)
        for current, f in enumerate(source.getFeatures(request)):
            if feedback.isCanceled():
                break

            # use already fetched geometry
            if f.id() not in null_geom_features:
                f.setGeometry(unique_features[f.id()])
            sink.addFeature(f, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(0.10 * current * total) + 90) # takes about 10% of time

        feedback.pushInfo(self.tr('{} duplicate features removed'.format(removed)))
        return {self.OUTPUT: dest_id,
                self.DUPLICATE_COUNT: removed,
                self.RETAINED_COUNT: len(output_feature_ids)}
示例#19
0
class RasterHandler(QObject):
    """Raster layer handler."""

    raster_changed = pyqtSignal(object)

    def __init__(self, layer, uc=None, debug=False):
        super(RasterHandler, self).__init__()
        self.layer = layer
        self.uc = uc
        self.logger = get_logger() if debug else None
        self.provider = layer.dataProvider()
        self.bands_nr = self.layer.bandCount()
        self.bands_range = range(1, self.bands_nr + 1)
        self.active_bands = [1]
        self.project = QgsProject.instance()
        self.crs_transform = None if self.project.crs() == self.layer.crs() else \
            QgsCoordinateTransform(self.project.crs(), self.layer.crs(), self.project)
        self.data_types = None
        self.nodata_values = None
        self.pixel_size_x = self.layer.rasterUnitsPerPixelX()
        self.pixel_size_y = self.layer.rasterUnitsPerPixelY()
        self.raster_cols = self.layer.width()
        self.raster_rows = self.layer.height()
        self.layer_extent = self.provider.extent()
        self.min_x = self.layer_extent.xMinimum()
        self.min_y = self.layer_extent.yMinimum()
        self.max_x = self.layer_extent.xMaximum()
        self.max_y = self.layer_extent.yMaximum()
        self.origin_x = self.min_x
        self.origin_y = self.max_y
        self.first_pixel_x = self.min_x + self.pixel_size_x / 2.  # x coord of upper left pixel center
        self.first_pixel_y = self.max_y - self.pixel_size_y / 2.  # y
        self.cell_centers = None  # dict of coordinates of currently selected cells centers {(row, col): (x, y)}
        self.cell_exp_val = None  # dict of evaluated expressions for cells centers {(row, col): value}
        self.cell_pts_layer = None  # point memory layer with selected cells centers
        self.selecting_geoms = None  # dictionary of selecting geometries {id: geometry}
        self.spatial_index = None  # spatial index of selecting geometries extents
        self.block_row_min = None  # range of indices of the raster block to modify
        self.block_row_max = None
        self.block_col_min = None
        self.block_col_max = None
        self.selected_cells = None  # list of selected cells as tuples of global indices (row, cell)
        self.selected_cells_feats = None  # {(row, cell): feature}
        self.total_geometry = None
        self.all_touched_cells = None
        self.exp_field_idx = None
        self.get_data_types()
        self.get_nodata_values()

    def get_data_types(self):
        self.data_types = []
        for nr in self.bands_range:
            self.data_types.append(self.provider.dataType(nr))

    def write_supported(self):
        msg = ""
        supported = True
        for nr in self.bands_range:
            if self.provider.dataType(
                    nr) == 0 or self.provider.dataType(nr) > 7:
                msg = f"{dtypes[self.provider.dataType(nr)]['name']} (band {nr})"
                supported = False
        return supported, msg

    def get_nodata_values(self):
        self.nodata_values = []
        for nr in self.bands_range:
            if self.provider.sourceHasNoDataValue(nr):
                self.nodata_values.append(self.provider.sourceNoDataValue(nr))
                self.provider.setUseSourceNoDataValue(nr, True)
            # no nodata defined in the raster source
            else:
                # check if user defined any nodata values
                if self.provider.userNoDataValues(nr):
                    # get min nodata value from the first user nodata range
                    nd_ranges = self.provider.userNoDataValues(nr)
                    self.nodata_values.append(nd_ranges[0].min())
                else:
                    # leave nodata undefined
                    self.nodata_values.append(None)

    def select(self, geometries, all_touched_cells=True, transform=True):
        """
        For the geometries list, find selected cells.
        If all_touched_cells is True, all cells touching a geometry will be selected.
        Otherwise, a geometry must intersect a cell center to select it.
        """
        if self.logger:
            self.logger.debug(
                f"Selecting cells for geometries: {[g.asWkt() for g in geometries]}"
            )
        if not geometries:
            self.uc.bar_warn("Select some raster cells!")
            return
        self.selecting_geoms = dict()
        self.selected_cells = []
        self.spatial_index = QgsSpatialIndex()
        self.total_geometry = QgsGeometry()
        dxy = 0.001
        geoms = []
        for nr, geom in enumerate(geometries):
            if not geom.isGeosValid():
                continue
            sgeom = QgsGeometry(geom)
            if self.crs_transform and transform:
                try:
                    res = sgeom.transform(self.crs_transform)
                    if not res == QgsGeometry.Success:
                        raise QgsCsException(repr(res))
                except QgsCsException as err:
                    msg = "Raster transformation failed! Check the raster projection settings."
                    if self.uc:
                        self.uc.bar_warn(msg, dur=5)
                    msg += repr(err)
                    if self.logger:
                        self.logger.warning(msg)
                    return

            self.selecting_geoms[nr] = sgeom
            self.spatial_index.addFeature(nr, sgeom.boundingBox())
            geoms.append(sgeom)
        self.total_geometry = QgsGeometry.unaryUnion(geoms)
        if self.logger:
            self.logger.debug(
                f"Total selecting geometry bbox: {self.total_geometry.boundingBox()}"
            )
        self.block_row_min, self.block_row_max, self.block_col_min, self.block_col_max = \
            self.extent_to_cell_indices(self.total_geometry.boundingBox())

        half_pix_x = self.pixel_size_x / 2.
        half_pix_y = self.pixel_size_y / 2.
        self.cell_centers = dict()
        for row in range(self.block_row_min, self.block_row_max + 1):
            for col in range(self.block_col_min, self.block_col_max + 1):
                pt_x = self.first_pixel_x + col * self.pixel_size_x
                pt_y = self.first_pixel_y - row * self.pixel_size_y
                if all_touched_cells:
                    bbox = QgsRectangle(pt_x - half_pix_x, pt_y - half_pix_y,
                                        pt_x + half_pix_x, pt_y + half_pix_y)
                else:
                    bbox = QgsRectangle(pt_x, pt_y, pt_x + dxy, pt_y + dxy)
                sel_inter = self.spatial_index.intersects(bbox)
                for sel_geom_id in sel_inter:
                    g = self.selecting_geoms[sel_geom_id]
                    if g.intersects(bbox):
                        self.selected_cells.append((row, col))
                        self.cell_centers[(row, col)] = (pt_x, pt_y)
        if self.logger:
            self.logger.debug(
                f"Nr of cells selected: {len(self.selected_cells)}")

    def create_cell_pts_layer(self):
        """For current block extent, create memory point layer with a feature in each selected cell."""
        crs_str = self.layer.crs().authid().lower()
        fields_def = "field=row:int&field=col:int"
        self.cell_pts_layer = QgsVectorLayer(
            f"Point?crs={crs_str}&{fields_def}", "Temp raster cell points",
            "memory")
        fields = self.cell_pts_layer.dataProvider().fields()
        feats = []
        for row_col, xy in self.cell_centers.items():
            row, col = row_col
            x, y = xy
            feat = QgsFeature(fields)
            feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
            feat["row"] = row
            feat["col"] = col
            feats.append(feat)
        self.cell_pts_layer.dataProvider().addFeatures(feats)
        self.selected_cells_feats = dict()  # {(row, cell): feat}
        for feat in self.cell_pts_layer.getFeatures():
            self.selected_cells_feats[(feat["row"], feat["col"])] = feat.id()

    def write_block(self, const_values=None, low_pass_filter=False):
        """
        Construct raster block for each band, apply the values and write to file.
        If const_values are given (a list of const values for each band) they are used for each selected cell.
        In other case the memory layer with values calculated for each cell selected will be used.
        Alternatively, selected cells values can be filtered using low-pass 3x3 filter.
        """
        if self.logger:
            vals = f"const values ({const_values})" if const_values else "expression values."
            self.logger.debug(f"Writing blocks with {vals}")
        if not self.provider.isEditable():
            res = self.provider.setEditable(True)
            if not res:
                if self.uc:
                    self.uc.show_warn('QGIS can\'t modify this type of raster')
                return None
        if self.logger:
            self.logger.debug("Calculating block origin coordinates...")
        b_orig_x, b_orig_y = self.index_to_point(self.block_row_min,
                                                 self.block_col_min)
        cols = self.block_col_max - self.block_col_min + 1
        rows = self.block_row_max - self.block_row_min + 1
        b_end_x = b_orig_x + cols * self.pixel_size_x
        b_end_y = b_orig_y - rows * self.pixel_size_y
        block_bbox = QgsRectangle(b_orig_x, b_end_y, b_end_x, b_orig_y)
        if self.logger:
            self.logger.debug(f"Block bbox: {block_bbox.toString()}")
            self.logger.debug(
                f"Nr of cells in the block: rows={rows}, cols={cols}")
        old_blocks = []
        new_blocks = []
        cell_values = dict()
        if const_values is None and not low_pass_filter:
            for feat in self.cell_pts_layer.getFeatures():
                cell_values[feat.id()] = feat.attribute(self.exp_field_idx)
        for band_nr in self.active_bands:
            block = self.provider.block(band_nr, block_bbox, cols, rows)
            new_blocks.append(block)
            block_data = block.data().data()
            old_block = QgsRasterBlock(self.data_types[band_nr - 1], cols,
                                       rows)
            old_block.setData(block_data)
            for abs_row, abs_col in self.selected_cells:
                row = abs_row - self.block_row_min
                col = abs_col - self.block_col_min
                if const_values:
                    idx = band_nr - 1 if len(self.active_bands) > 1 else 0
                    new_val = const_values[idx]
                elif low_pass_filter:
                    # the filter is applied for cells inside the block only
                    if block.height() < 3 or block.width() < 3:
                        # the selected block is too small for filtering -> keep the old value
                        new_val = None
                    else:
                        new_val = low_pass_filtered(
                            old_block, row, col,
                            self.nodata_values[band_nr - 1])
                else:
                    # set the expression value
                    feat_id = self.selected_cells_feats[(abs_row, abs_col)]
                    if cell_values[feat_id] is not None:
                        new_val = None if math.isnan(cell_values[feat_id]) or \
                                      cell_values[feat_id] is None else cell_values[feat_id]
                    else:
                        new_val = None
                new_val = old_block.value(row,
                                          col) if new_val is None else new_val
                set_res = block.setValue(row, col, new_val)
                if self.logger:
                    self.logger.debug(
                        f"Setting block value for band {band_nr}, row {row}, col: {col}: {set_res}"
                    )
            old_blocks.append(old_block)
            band_res = self.provider.writeBlock(block, band_nr,
                                                self.block_col_min,
                                                self.block_row_min)
            if self.logger:
                self.logger.debug(
                    f"Writing block for band {band_nr}: {band_res}")
        self.provider.setEditable(False)
        change = RasterChange(self.active_bands, self.block_row_min,
                              self.block_col_min, old_blocks, new_blocks)
        self.raster_changed.emit(change)
        return True

    def write_block_undo(self, data):
        """Write blocks from the undo / redo stack."""
        if self.logger:
            self.logger.debug(f"Writing blocks from undo")
        if not self.provider.isEditable():
            res = self.provider.setEditable(True)
        bands, row_min, col_min, blocks = data
        for band_nr in bands:
            idx = band_nr - 1 if len(bands) > 1 else 0
            block = blocks[idx]
            band_res = self.provider.writeBlock(block, band_nr, col_min,
                                                row_min)
            if self.logger:
                self.logger.debug(
                    f"Writing undo/redo block for band {band_nr}: {band_res}")
        self.provider.setEditable(False)

    def extent_to_cell_indices(self, extent):
        """Return x and y raster cell indices ranges for the extent."""
        col_min, row_max = self.point_to_index(
            (extent.xMinimum(), extent.yMinimum()))
        col_max, row_min = self.point_to_index(
            (extent.xMaximum(), extent.yMaximum()))
        if self.logger:
            self.logger.debug(
                f"Cell ranges for extent {extent.toString(precision=3)} = row_min: {row_min}, "
                +
                f"row_max: {row_max}, col_min: {col_min}, col_max: {col_max}")
        return row_min, row_max, col_min, col_max

    def index_to_point(self, row, col, upper_left=True):
        """Return cell upper left corner or cell center coordinates."""
        x0 = self.origin_x if upper_left else self.first_pixel_x
        y0 = self.origin_y if upper_left else self.first_pixel_y
        x, y = x0 + col * self.pixel_size_x, y0 - row * self.pixel_size_y
        if self.logger:
            self.logger.debug(f"Coords for ({row}, {col}) = ({x}, {y}) (x, y)")
        return x, y

    def point_to_index(self, coords):
        """
        Return raster cell indices for the coordinates.
        If it falls outside of the layer extent, then the first or last index is returned.
        """
        if self.origin_x <= coords[0] <= self.max_x:
            x_offset = coords[0] - self.origin_x
            col = math.floor(x_offset / self.pixel_size_x)
        elif coords[0] < self.origin_x:
            col = 0
        else:
            col = self.raster_cols - 1

        if self.min_y <= coords[1] <= self.origin_y:
            y_offset = self.origin_y - coords[1]
            row = math.floor(y_offset / self.pixel_size_y)
        elif coords[1] > self.origin_y:
            row = 0
        else:
            row = self.raster_rows - 1

        return col, row
示例#20
0
    def processAlgorithm(
            self,  # pylint: disable=missing-function-docstring,too-many-statements,too-many-branches,too-many-locals
            parameters,
            context,
            feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)

        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, source.fields(),
                                               QgsWkbTypes.LineString,
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        roundabout_expression_string = self.parameterAsExpression(
            parameters, self.EXPRESSION, context)

        # step 1 - find all roundabouts
        exp = QgsExpression(roundabout_expression_string)
        expression_context = self.createExpressionContext(
            parameters, context, source)
        exp.prepare(expression_context)

        roundabouts = []
        not_roundabouts = {}
        not_roundabout_index = QgsSpatialIndex()

        total = 10.0 / source.featureCount() if source.featureCount() else 0
        features = source.getFeatures()

        _id = 1
        for current, feature in enumerate(features):
            if feedback.isCanceled():
                break

            def add_feature(f, _id, geom, is_roundabout):
                output_feature = QgsFeature(f)
                output_feature.setGeometry(geom)
                output_feature.setId(_id)
                if is_roundabout:
                    roundabouts.append(output_feature)
                else:
                    not_roundabouts[output_feature.id()] = output_feature
                    not_roundabout_index.addFeature(output_feature)

            expression_context.setFeature(feature)

            is_roundabout = exp.evaluate(expression_context)
            if not feature.geometry().wkbType() == QgsWkbTypes.LineString:
                geom = feature.geometry()
                for p in geom.parts():
                    add_feature(feature, _id, QgsGeometry(p.clone()),
                                is_roundabout)
                    _id += 1
            else:
                add_feature(feature, _id, feature.geometry(), is_roundabout)
                _id += 1

            # Update the progress bar
            feedback.setProgress(int(current * total))

        feedback.pushInfo(
            self.tr('Found {} roundabout parts'.format(len(roundabouts))))
        feedback.pushInfo(
            self.tr('Found {} not roundabouts'.format(len(not_roundabouts))))

        if feedback.isCanceled():
            return {self.OUTPUT: dest_id}

        all_roundabouts = QgsGeometry.unaryUnion(
            [r.geometry() for r in roundabouts])
        feedback.setProgress(20)
        all_roundabouts = all_roundabouts.mergeLines()
        feedback.setProgress(25)

        total = 70.0 / all_roundabouts.constGet().numGeometries(
        ) if all_roundabouts.isMultipart() else 1

        for current, roundabout in enumerate(all_roundabouts.parts()):
            touching = not_roundabout_index.intersects(
                roundabout.boundingBox())
            if not touching:
                continue

            if feedback.isCanceled():
                break

            roundabout_engine = QgsGeometry.createGeometryEngine(roundabout)
            roundabout_engine.prepareGeometry()
            roundabout_geom = QgsGeometry(roundabout.clone())
            roundabout_centroid = roundabout_geom.centroid()

            other_points = []

            # find all touching roads, and move the touching part to the centroid
            for t in touching:
                touching_geom = not_roundabouts[t].geometry()
                touching_road = touching_geom.constGet().clone()
                if not roundabout_engine.touches(touching_road):
                    # print('not touching!!')
                    continue

                # work out if start or end of line touched the roundabout
                nearest = roundabout_geom.nearestPoint(touching_geom)
                _, v = touching_geom.closestVertexWithContext(
                    nearest.asPoint())

                if v == 0:
                    # started at roundabout
                    other_points.append((touching_road.endPoint(), True, t))
                else:
                    # ended at roundabout
                    other_points.append((touching_road.startPoint(), False, t))

            if not other_points:
                continue

            # see if any incoming segments originate at the same place ("V" patterns)
            averaged = set()
            for point1, started_at_roundabout1, id1 in other_points:
                if id1 in averaged:
                    continue

                if feedback.isCanceled():
                    break

                parts_to_average = [id1]
                for point2, _, id2 in other_points:
                    if id2 == id1:
                        continue

                    if point2 != point1:
                        # todo tolerance?
                        continue

                    parts_to_average.append(id2)

                if len(parts_to_average) == 1:
                    # not a <O pattern, just a round coming straight to the roundabout
                    line = not_roundabouts[id1].geometry().constGet().clone()
                    if started_at_roundabout1:
                        # extend start of line to roundabout centroid
                        line.moveVertex(QgsVertexId(0, 0, 0),
                                        roundabout_centroid.constGet())
                    else:
                        # extend end of line to roundabout centroid
                        line.moveVertex(
                            QgsVertexId(0, 0,
                                        line.numPoints() - 1),
                            roundabout_centroid.constGet())

                    not_roundabout_index.deleteFeature(
                        not_roundabouts[parts_to_average[0]])
                    not_roundabouts[parts_to_average[0]].setGeometry(
                        QgsGeometry(line))
                    not_roundabout_index.addFeature(
                        not_roundabouts[parts_to_average[0]])

                elif len(parts_to_average) == 2:
                    # <O pattern
                    src_part, other_part = parts_to_average  # pylint: disable=unbalanced-tuple-unpacking
                    averaged.add(src_part)
                    averaged.add(other_part)

                    averaged_line = GeometryUtils.average_linestrings(
                        not_roundabouts[src_part].geometry().constGet(),
                        not_roundabouts[other_part].geometry().constGet())

                    if started_at_roundabout1:
                        # extend start of line to roundabout centroid
                        averaged_line.moveVertex(
                            QgsVertexId(0, 0, 0),
                            roundabout_centroid.constGet())
                    else:
                        # extend end of line to roundabout centroid
                        averaged_line.moveVertex(
                            QgsVertexId(0, 0,
                                        averaged_line.numPoints() - 1),
                            roundabout_centroid.constGet())

                    not_roundabout_index.deleteFeature(
                        not_roundabouts[src_part])
                    not_roundabouts[src_part].setGeometry(
                        QgsGeometry(averaged_line))
                    not_roundabout_index.addFeature(not_roundabouts[src_part])

                    not_roundabout_index.deleteFeature(
                        not_roundabouts[other_part])
                    del not_roundabouts[other_part]

            feedback.setProgress(25 + int(current * total))

        total = 5.0 / len(not_roundabouts)
        current = 0
        for _, f in not_roundabouts.items():
            if feedback.isCanceled():
                break

            sink.addFeature(f, QgsFeatureSink.FastInsert)
            current += 1
            feedback.setProgress(95 + int(current * total))

        return {self.OUTPUT: dest_id}
示例#21
0
    def processAlgorithm(
            self,  # pylint: disable=missing-function-docstring,too-many-statements,too-many-branches,too-many-locals
            parameters,
            context,
            feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)

        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, source.fields(),
                                               source.wkbType(),
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        threshold = self.parameterAsDouble(parameters, self.THRESHOLD, context)
        fields = self.parameterAsFields(parameters, self.FIELDS, context)
        field_indices = [source.fields().lookupField(f) for f in fields]
        index = QgsSpatialIndex()
        roads = {}

        total = 10.0 / source.featureCount() if source.featureCount() else 0
        features = source.getFeatures()

        for current, feature in enumerate(features):
            if feedback.isCanceled():
                break

            if feature.geometry().isMultipart():
                if feature.geometry().constGet().numGeometries() > 1:
                    raise QgsProcessingException(
                        self.tr('Only single-part geometries are supported'))
                part1 = feature.geometry().constGet().geometryN(0).clone()
                feature.setGeometry(part1)

            index.addFeature(feature)
            roads[feature.id()] = feature

            feedback.setProgress(int(current * total))

        collapsed = {}
        processed = set()

        total = 85.0 / len(roads)
        current = 0
        for _id, f in roads.items():
            if feedback.isCanceled():
                break

            current += 1
            feedback.setProgress(10 + current * total)

            if _id in processed:
                continue

            box = f.geometry().boundingBox()
            box.grow(threshold)

            similar_candidates = index.intersects(box)
            if not similar_candidates:
                collapsed[_id] = f
                processed.add(_id)
                continue

            candidate = f.geometry()
            candidate_attrs = [f.attributes()[i] for i in field_indices]

            parts = []

            for t in similar_candidates:
                if t == _id:
                    continue

                other = roads[t]
                other_attrs = [other.attributes()[i] for i in field_indices]
                if other_attrs != candidate_attrs:
                    continue

                dist = candidate.hausdorffDistance(other.geometry())
                if dist < threshold:
                    parts.append(t)

            if len(parts) == 0:
                collapsed[_id] = f
                continue

            # todo fix this
            if len(parts) > 1:
                continue
            assert len(parts) == 1, len(parts)

            other = roads[parts[0]].geometry()
            averaged = QgsGeometry(
                GeometryUtils.average_linestrings(candidate.constGet(),
                                                  other.constGet()))

            # reconnect touching lines
            bbox = candidate.boundingBox()
            bbox.combineExtentWith(other.boundingBox())
            touching_candidates = index.intersects(bbox)

            for touching_candidate in touching_candidates:
                if touching_candidate in (_id, parts[0]):
                    continue

                # print(touching_candidate)

                touching_candidate_geom = roads[touching_candidate].geometry()
                # either the start or end of touching_candidate_geom touches candidate
                start = QgsGeometry(
                    touching_candidate_geom.constGet().startPoint())
                end = QgsGeometry(
                    touching_candidate_geom.constGet().endPoint())

                moved_start = False
                moved_end = False
                for cc in [candidate, other]:
                    #  if start.touches(cc):
                    start_line = start.shortestLine(cc)
                    if start_line.length() < 0.00000001:
                        # start touches, move to touch averaged line
                        averaged_line = start.shortestLine(averaged)
                        new_start = averaged_line.constGet().endPoint()
                        touching_candidate_geom.get().moveVertex(
                            QgsVertexId(0, 0, 0), new_start)
                        # print('moved start')
                        moved_start = True
                        continue
                    end_line = end.shortestLine(cc)
                    if end_line.length() < 0.00000001:
                        # endtouches, move to touch averaged line
                        averaged_line = end.shortestLine(averaged)
                        new_end = averaged_line.constGet().endPoint()
                        touching_candidate_geom.get().moveVertex(
                            QgsVertexId(
                                0, 0,
                                touching_candidate_geom.constGet().numPoints()
                                - 1), new_end)
                        # print('moved end')
                        moved_end = True
                        # break

                index.deleteFeature(roads[touching_candidate])
                if moved_start and moved_end:
                    if touching_candidate in collapsed:
                        del collapsed[touching_candidate]
                    processed.add(touching_candidate)
                else:
                    roads[touching_candidate].setGeometry(
                        touching_candidate_geom)
                    index.addFeature(roads[touching_candidate])
                    if touching_candidate in collapsed:
                        collapsed[touching_candidate].setGeometry(
                            touching_candidate_geom)

            index.deleteFeature(f)
            index.deleteFeature(roads[parts[0]])

            ff = QgsFeature(roads[parts[0]])
            ff.setGeometry(averaged)
            index.addFeature(ff)
            roads[ff.id()] = ff

            ff = QgsFeature(f)
            ff.setGeometry(averaged)
            index.addFeature(ff)
            roads[_id] = ff

            collapsed[_id] = ff
            processed.add(_id)
            processed.add(parts[0])

        total = 5.0 / len(processed)
        current = 0
        for _, f in collapsed.items():
            if feedback.isCanceled():
                break

            sink.addFeature(f, QgsFeatureSink.FastInsert)
            current += 1
            feedback.setProgress(95 + int(current * total))

        return {self.OUTPUT: dest_id}
示例#22
0
    def processAlgorithm(
            self,  # pylint: disable=missing-function-docstring,too-many-statements,too-many-branches,too-many-locals
            parameters,
            context,
            feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)

        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, source.fields(),
                                               source.wkbType(),
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        threshold = self.parameterAsDouble(parameters, self.THRESHOLD, context)
        fields = self.parameterAsFields(parameters, self.FIELDS, context)
        field_indices = [source.fields().lookupField(f) for f in fields]
        index = QgsSpatialIndex()
        roads = {}

        total = 10.0 / source.featureCount() if source.featureCount() else 0
        features = source.getFeatures()

        for current, feature in enumerate(features):
            if feedback.isCanceled():
                break

            index.addFeature(feature)
            roads[feature.id()] = feature

            feedback.setProgress(int(current * total))

        total = 90.0 / len(roads)
        current = 0
        removed = 0
        for _id, f in roads.items():
            if feedback.isCanceled():
                break

            current += 1

            if f.geometry().length() >= threshold:
                sink.addFeature(f, QgsFeatureSink.FastInsert)
                feedback.setProgress(10 + int(current * total))
                continue

            # we mark identify a cross road because either side is touched by at least two other features
            # with matching identifier attributes

            candidate_attrs = [f.attributes()[i] for i in field_indices]

            touching_candidates = index.intersects(f.geometry().boundingBox())
            if not f.geometry().isMultipart():
                candidate = f.geometry().constGet().clone()
            else:
                if f.geometry().constGet().numGeometries() > 1:
                    raise QgsProcessingException(
                        self.tr('Only single-part geometries are supported'))
                candidate = f.geometry().constGet().geometryN(0).clone()

            candidate_start = candidate.startPoint()
            candidate_end = candidate.endPoint()
            start_engine = QgsGeometry.createGeometryEngine(candidate_start)
            end_engine = QgsGeometry.createGeometryEngine(candidate_end)
            touching_start_count = 0
            touching_end_count = 0
            for t in touching_candidates:
                if t == _id:
                    continue

                other = roads[t]

                other_attrs = [other.attributes()[i] for i in field_indices]
                if other_attrs != candidate_attrs:
                    continue

                if other.geometry().length() < threshold:
                    continue

                if start_engine.intersects(roads[t].geometry().constGet()):
                    touching_start_count += 1
                if end_engine.intersects(roads[t].geometry().constGet()):
                    touching_end_count += 1

                if touching_start_count >= 2 and touching_end_count >= 2:
                    break

            feedback.setProgress(10 + int(current * total))

            if touching_start_count >= 2 and touching_end_count >= 2:
                # kill it
                removed += 1
            else:
                sink.addFeature(f, QgsFeatureSink.FastInsert)

        feedback.pushInfo(self.tr('Removed {} cross roads'.format(removed)))

        return {self.OUTPUT: dest_id}
示例#23
0
    def processAlgorithm(
            self,  # pylint: disable=missing-function-docstring,too-many-statements,too-many-branches,too-many-locals
            parameters,
            context,
            feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)

        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, source.fields(),
                                               source.wkbType(),
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        threshold = self.parameterAsDouble(parameters, self.THRESHOLD, context)

        index = QgsSpatialIndex()
        roads = {}

        total = 10.0 / source.featureCount() if source.featureCount() else 0
        features = source.getFeatures()

        for current, feature in enumerate(features):
            if feedback.isCanceled():
                break

            index.addFeature(feature)
            roads[feature.id()] = feature

            feedback.setProgress(int(current * total))

        total = 90.0 / len(roads)
        removed = 0
        current = 0
        for _id, f in roads.items():
            if feedback.isCanceled():
                break

            current += 1
            if f.geometry().length() >= threshold:
                sink.addFeature(f, QgsFeatureSink.FastInsert)
                feedback.setProgress(10 + int(current * total))
                continue

            touching_candidates = index.intersects(f.geometry().boundingBox())
            if len(touching_candidates) == 1:
                # small street, touching nothing but itself -- kill it!
                removed += 1
                feedback.setProgress(10 + int(current * total))
                continue

            if not f.geometry().isMultipart():
                candidate = f.geometry().constGet().clone()
            else:
                if f.geometry().constGet().numGeometries() > 1:
                    raise QgsProcessingException(
                        self.tr('Only single-part geometries are supported'))
                candidate = f.geometry().constGet().geometryN(0).clone()

            candidate_start = candidate.startPoint()
            candidate_end = candidate.endPoint()
            start_engine = QgsGeometry.createGeometryEngine(candidate_start)
            end_engine = QgsGeometry.createGeometryEngine(candidate_end)
            touching_start = False
            touching_end = False
            for t in touching_candidates:
                if t == _id:
                    continue

                if start_engine.intersects(roads[t].geometry().constGet()):
                    touching_start = True
                if end_engine.intersects(roads[t].geometry().constGet()):
                    touching_end = True

                if touching_start and touching_end:
                    break

            feedback.setProgress(10 + int(current * total))
            if touching_start and touching_end:
                # keep it, it joins two roads
                sink.addFeature(f, QgsFeatureSink.FastInsert)
                continue

            removed += 1

        feedback.pushInfo(self.tr('Removed {} cul-de-sacs'.format(removed)))

        return {self.OUTPUT: dest_id}
示例#24
0
class ContourTool(object):
    def updateReference(self, referenceLayer):
        """
        Updates the reference layer and updates the spatial index
        """
        self.first_value = None
        self.reference = referenceLayer
        self.populateIndex()

    def populateIndex(self):
        """
        Populates the spatial index
        """
        #spatial index
        self.index = QgsSpatialIndex()
        for feat in self.reference.getFeatures():
            self.index.addFeature(feat)

    def getCandidates(self, bbox):
        """
        Gets candidates using the spatial index to speedup the process
        """
        #features that might satisfy the query
        ids = self.index.intersects(bbox)
        candidates = []
        for id in ids:
            candidates.append(
                next(
                    self.reference.getFeatures(
                        QgsFeatureRequest().setFilterFid(id))))
        return candidates

    def getFeatures(self, geom):
        """
        Gets the features that intersect geom to be updated
        """
        #features that satisfy the query
        ret = []

        rect = geom.boundingBox()
        candidates = self.getCandidates(rect)
        for candidate in candidates:
            featGeom = candidate.geometry()
            if featGeom.intersects(geom):
                ret.append(candidate)

        return ret

    def getKey(self, item):
        """
        Gets the key
        """
        return item[0]

    def sortFeatures(self, geom, features):
        """
        Sorts features according to the distance
        """
        #sorting by distance
        distances = []

        firstPoint = geom.asPolyline()[0]
        pointGeom = QgsGeometry.fromPointXY(firstPoint)

        for intersected in features:
            intersection = geom.intersection(intersected.geometry())
            if intersection.type() == QgsWkbTypes.PointGeometry:
                distance = intersection.distance(pointGeom)
                distances.append((distance, intersected))

        ordered = sorted(distances, key=self.getKey)
        #returning a list of tuples (distance, feature)
        return ordered

    def reproject(self, geom, canvasCrs):
        """
        Reprojects geom to the reference layer crs
        """
        destCrs = self.reference.crs()
        if canvasCrs.authid() != destCrs.authid():
            coordinateTransformer = QgsCoordinateTransform(
                canvasCrs, destCrs, QgsProject.instance())
            geom.transform(coordinateTransformer)

    def setFirstValue(self, value):
        self.first_value = value

    def assignValues(self, attribute, pace, geom, canvasCrs):
        """
        Assigns attribute values to all features that intersect geom.
        """
        self.reproject(geom, canvasCrs)
        features = self.getFeatures(geom)
        if len(features) == 0:
            return -2

        ordered = self.sortFeatures(geom, features)
        if len(ordered) == 0:
            return -1

        self.reference.startEditing()
        #the first feature must have the initial value already assigned
        first_feature = ordered[0][1]
        #getting the filed index that must be updated
        fieldIndex = self.reference.fields().indexFromName(attribute)
        #getting the initial value
        first_value = first_feature.attribute(attribute)
        if not first_value:
            first_value_dlg = ContourValue(self)
            retorno = first_value_dlg.exec_()
            if self.first_value:
                id = first_feature.id()
                first_value = self.first_value
                if not self.reference.changeAttributeValue(
                        id, fieldIndex, self.first_value):
                    return 0
            else:
                return -3
            self.first_value = None

        for i in range(1, len(ordered)):
            #value to be adjusted
            value = first_value + pace * i
            #feature that will be updated
            feature = ordered[i][1]
            #feature id that will be updated
            id = feature.id()
            #actual update in the layer
            if not self.reference.changeAttributeValue(id, fieldIndex, value):
                return 0
        return 1
示例#25
0
    def processAlgorithm(self, parameters, context, feedback):  #pylint: disable=unused-argument,missing-docstring

        layer = self.parameterAsSource(parameters, self.INPUT, context)

        # Step 1
        feedback.setProgressText(self.tr("[1/4] Get Line Endpoints ..."))

        total = 100.0 / layer.featureCount() if layer.featureCount() else 0
        coordinates = list()

        @simple_linestring_op
        def extract_coordinates(polyline):
            """ Extract endpoints coordinates
            """

            a = polyline[0]
            b = polyline[-1]
            coordinates.append(tuple(a))
            coordinates.append(tuple(b))

        for current, feature in enumerate(layer.getFeatures()):

            if feedback.isCanceled():
                break

            extract_coordinates(feature.geometry())
            feedback.setProgress(int(total * current))

        # Step 2
        feedback.setProgressText(self.tr("[2/4] Quantize coordinates ..."))

        coordinates = np.array(coordinates)
        minx = np.min(coordinates[:, 0])
        miny = np.min(coordinates[:, 1])
        maxx = np.max(coordinates[:, 0])
        maxy = np.max(coordinates[:, 1])

        quantization = 1e8
        kx = (minx == maxx) and 1 or (maxx - minx)
        ky = (miny == maxy) and 1 or (maxy - miny)
        sx = kx / quantization
        sy = ky / quantization

        coordinates = np.int32(
            np.round((coordinates - (minx, miny)) / (sx, sy)))

        # Step 3
        feedback.setProgressText(self.tr("[3/4] Build Endpoints Index ..."))

        fields = QgsFields()
        fields.append(QgsField('GID', type=QVariant.Int, len=10))

        (sink, nodes_id) = self.parameterAsSink(parameters, self.NODES,
                                                context, fields,
                                                QgsWkbTypes.Point,
                                                layer.sourceCrs())

        point_index = QgsSpatialIndex()
        # point_list = list()
        coordinates_map = dict()
        gid = 0

        total = 100.0 / len(coordinates)

        for i, coordinate in enumerate(coordinates):

            if feedback.isCanceled():
                break

            c = tuple(coordinate)

            if c not in coordinates_map:

                coordinates_map[c] = i
                # point_list.append(c)

                geometry = QgsGeometry.fromPointXY(
                    QgsPointXY(c[0] * sx + minx, c[1] * sy + miny))
                point_feature = QgsFeature()
                point_feature.setId(gid)
                point_feature.setAttributes([gid])
                point_feature.setGeometry(geometry)

                point_index.addFeature(point_feature)
                sink.addFeature(point_feature)

                gid = gid + 1

            feedback.setProgress(int(total * i))

        del coordinates
        del coordinates_map

        # Step 4
        feedback.setProgressText(
            self.tr("[4/4] Output Lines with Node Attributes ..."))

        fields = QgsFields(layer.fields())
        fields.append(QgsField('NODEA', QVariant.Int, len=10))
        fields.append(QgsField('NODEB', QVariant.Int, len=10))

        (sink, output_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                                 context, fields,
                                                 layer.wkbType(),
                                                 layer.sourceCrs())

        def nearest(point):
            """ Return the nearest point in the point index
            """

            for candidate in point_index.nearestNeighbor(point, 1):
                return candidate

            return None

        @simple_linestring_op
        def output_simple_features(polyline, feature):
            """ Split multi-polylines into simple polyline if required,
                match endpoints into the node index,
                and output one or more stream features with node attributes
            """

            a = polyline[0]
            b = polyline[-1]
            simple_geom = QgsGeometry.fromPolylineXY(polyline)

            out_feature = QgsFeature()
            out_feature.setGeometry(simple_geom)
            out_feature.setAttributes(feature.attributes() +
                                      [nearest(a), nearest(b)])

            sink.addFeature(out_feature)

        total = 100.0 / layer.featureCount() if layer.featureCount() else 0

        for current, feature in enumerate(layer.getFeatures()):

            if feedback.isCanceled():
                break

            geom = feature.geometry()

            if not geom.isMultipart():

                polyline = geom.asPolyline()
                a = polyline[0]
                b = polyline[-1]

                out_feature = QgsFeature()
                out_feature.setGeometry(geom)
                out_feature.setAttributes(feature.attributes() +
                                          [nearest(a), nearest(b)])

                sink.addFeature(out_feature)

            else:
                output_simple_features(geom, feature)

            feedback.setProgress(int(total * current))

        return {self.OUTPUT: output_id, self.NODES: nodes_id}
示例#26
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        proximity = self.parameterAsDouble(parameters, self.PROXIMITY, context)
        radius = self.parameterAsDouble(parameters, self.DISTANCE, context)
        horizontal = self.parameterAsBool(parameters, self.HORIZONTAL, context)

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, source.fields(),
                                               source.wkbType(),
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        features = source.getFeatures()

        total = 100.0 / source.featureCount() if source.featureCount() else 0

        def searchRect(p):
            return QgsRectangle(p.x() - proximity,
                                p.y() - proximity,
                                p.x() + proximity,
                                p.y() + proximity)

        index = QgsSpatialIndex()

        # NOTE: this is a Python port of QgsPointDistanceRenderer::renderFeature. If refining this algorithm,
        # please port the changes to QgsPointDistanceRenderer::renderFeature also!

        clustered_groups = []
        group_index = {}
        group_locations = {}
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            if not f.hasGeometry():
                continue

            point = f.geometry().asPoint()

            other_features_within_radius = index.intersects(searchRect(point))
            if not other_features_within_radius:
                index.addFeature(f)
                group = [f]
                clustered_groups.append(group)
                group_index[f.id()] = len(clustered_groups) - 1
                group_locations[f.id()] = point
            else:
                # find group with closest location to this point (may be more than one within search tolerance)
                min_dist_feature_id = other_features_within_radius[0]
                min_dist = group_locations[min_dist_feature_id].distance(point)
                for i in range(1, len(other_features_within_radius)):
                    candidate_id = other_features_within_radius[i]
                    new_dist = group_locations[candidate_id].distance(point)
                    if new_dist < min_dist:
                        min_dist = new_dist
                        min_dist_feature_id = candidate_id

                group_index_pos = group_index[min_dist_feature_id]
                group = clustered_groups[group_index_pos]

                # calculate new centroid of group
                old_center = group_locations[min_dist_feature_id]
                group_locations[min_dist_feature_id] = QgsPointXY(
                    (old_center.x() * len(group) + point.x()) /
                    (len(group) + 1.0),
                    (old_center.y() * len(group) + point.y()) /
                    (len(group) + 1.0))
                # add to a group
                clustered_groups[group_index_pos].append(f)
                group_index[f.id()] = group_index_pos

            feedback.setProgress(int(current * total))

        current = 0
        total = 100.0 / len(clustered_groups) if clustered_groups else 1
        feedback.setProgress(0)

        fullPerimeter = 2 * math.pi

        for group in clustered_groups:
            if feedback.isCanceled():
                break

            count = len(group)
            if count == 1:
                sink.addFeature(group[0], QgsFeatureSink.FastInsert)
            else:
                angleStep = fullPerimeter / count
                if count == 2 and horizontal:
                    currentAngle = math.pi / 2
                else:
                    currentAngle = 0

                old_point = group_locations[group[0].id()]

                for f in group:
                    if feedback.isCanceled():
                        break

                    sinusCurrentAngle = math.sin(currentAngle)
                    cosinusCurrentAngle = math.cos(currentAngle)
                    dx = radius * sinusCurrentAngle
                    dy = radius * cosinusCurrentAngle

                    # we want to keep any existing m/z values
                    point = f.geometry().constGet().clone()
                    point.setX(old_point.x() + dx)
                    point.setY(old_point.y() + dy)
                    f.setGeometry(QgsGeometry(point))

                    sink.addFeature(f, QgsFeatureSink.FastInsert)
                    currentAngle += angleStep

            current += 1
            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
示例#27
0
    def _append_pipes(self, params, out):

        out.extend(InpFile.build_section_keyword(Pipe.section_name))
        out.append(InpFile.build_section_header(Pipe.section_header))
        # out.append(InpFile.build_dashline(Pipe.section_header))

        pipe_fts = params.pipes_vlay.getFeatures()

        # Build nodes spatial index
        sindex = QgsSpatialIndex()
        for feat in params.junctions_vlay.getFeatures():
            #sindex.insertFeature(feat)
            sindex.addFeature(feat)
        for feat in params.reservoirs_vlay.getFeatures():
            #sindex.insertFeature(feat)
            sindex.addFeature(feat)
        for feat in params.tanks_vlay.getFeatures():
            #sindex.insertFeature(feat)
            sindex.addFeature(feat)

        for pipe_ft in pipe_fts:

            eid = pipe_ft.attribute(Pipe.field_name_eid)

            # Find start/end nodes
            # adj_nodes = NetworkUtils.find_start_end_nodes(params, pipe_ft.geometry())
            adj_nodes = NetworkUtils.find_start_end_nodes_sindex(
                params, sindex, pipe_ft.geometry())

            start_node_id = adj_nodes[0].attribute(Junction.field_name_eid)
            end_node_id = adj_nodes[1].attribute(Junction.field_name_eid)

            length = pipe_ft.attribute(Pipe.field_name_length)
            #length_units = pipe_ft.attribute(QPipe.field_name_length_units)
            diameter = pipe_ft.attribute(Pipe.field_name_diameter)
            #diameter_units = pipe_ft.attribute(QPipe.field_name_diameter_units)
            roughness = pipe_ft.attribute(Pipe.field_name_roughness)
            minor_loss = pipe_ft.attribute(Pipe.field_name_minor_loss)
            status = pipe_ft.attribute(Pipe.field_name_status)
            description = pipe_ft.attribute(Pipe.field_name_description)
            tag_name = pipe_ft.attribute(Pipe.field_name_tag)
            #num_edu = int(pipe_ft.attribute(QPipe.field_name_num_edu))
            #zone_id = int(pipe_ft.attribute(QPipe.field_name_zone_id))
            #velocity = float(pipe_ft.attribute(QPipe.field_name_velocity))
            #velocity_units = pipe_ft.attribute(QPipe.field_name_velocity_units)
            #friction_loss = float(pipe_ft.attribute(QPipe.field_name_frictionloss))
            #friction_loss_units = pipe_ft.attribute(QPipe.field_name_frictionloss_units)

            # Line
            line = InpFile.pad(eid, InpFile.pad_19)
            line += InpFile.pad(start_node_id, InpFile.pad_19)
            line += InpFile.pad(end_node_id, InpFile.pad_19)
            if not isinstance(length, unicode):
                line += InpFile.pad('{0:.2f}'.format(length), InpFile.pad_19)
            else:
                line += InpFile.pad(length, InpFile.pad_19)
            #line += InpFile.pad(length_units, InpFile.pad_19)
            if not isinstance(diameter, unicode):
                line += InpFile.pad('{0:.2f}'.format(diameter), InpFile.pad_19)
            else:
                line += InpFile.pad(diameter, InpFile.pad_19)
            #line += InpFile.pad(diameter_units, InpFile.pad_19)
            if not isinstance(roughness, unicode):
                line += InpFile.pad('{0:.2f}'.format(roughness),
                                    InpFile.pad_19)
            else:
                line += InpFile.pad(roughness, InpFile.pad_19)
            if not isinstance(minor_loss, unicode):
                line += InpFile.pad('{0:.2f}'.format(minor_loss),
                                    InpFile.pad_19)
            else:
                line += InpFile.pad(minor_loss, InpFile.pad_19)
            line += InpFile.pad(status, InpFile.pad_19)
            #line += InpFile.pad(num_edu, InpFile.pad_19)
            #line += InpFile.pad(zone_id, InpFile.pad_19)
            #line += InpFile.pad(velocity, InpFile.pad_19)
            #line += InpFile.pad(velocity_units, InpFile.pad_19)
            #line += InpFile.pad(frictionloss, InpFile.pad_19)
            #line += InpFile.pad(frictionloss_units, InpFile.pad_19)

            if description is not None and description != '':
                line += ';' + description

            if tag_name is not None and tag_name != NULL and tag_name != '':
                self.tags.append(Tag(Tag.element_type_link, eid, tag_name))

            #if not isinstance(num_edu, unicode):
            #    line += InpFile.pad('{0:.2f}'.format(num_edu), InpFile.pad_19)
            #else:
            #    line += InpFile.pad(num_edu, InpFile.pad_19)
            #
            # if zone_id != NULL:
            #    if not isinstance(zone_id, unicode):
            #        line += InpFile.pad('{0:.2f}'.format(zone_id), InpFile.pad_19)
            #    else:
            #        line += InpFile.pad(zone_id, InpFile.pad_19)
            # else:
            #    zone_id = 0

            out.append(line)
示例#28
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               source.fields(), source.wkbType(), source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]))

        total = 100.0 / source.featureCount() if source.featureCount() else 0
        geoms = dict()
        index = QgsSpatialIndex()
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            geoms[f.id()] = f.geometry()
            index.addFeature(f)

            feedback.setProgress(int(0.10 * current * total)) # takes about 10% of time

        # start by assuming everything is unique, and chop away at this list
        unique_features = dict(geoms)

        current = 0
        for feature_id, geometry in geoms.items():
            if feedback.isCanceled():
                break

            if feature_id not in unique_features:
                # feature was already marked as a duplicate
                continue

            candidates = index.intersects(geometry.boundingBox())
            candidates.remove(feature_id)

            for candidate_id in candidates:
                if candidate_id not in unique_features:
                    # candidate already marked as a duplicate (not sure if this is possible,
                    # since it would mean the current feature would also have to be a duplicate!
                    # but let's be safe!)
                    continue

                if geometry.isGeosEqual(geoms[candidate_id]):
                    # candidate is a duplicate of feature
                    del unique_features[candidate_id]

            current += 1
            feedback.setProgress(int(0.80 * current * total) + 10)  # takes about 80% of time

        total = 100.0 / len(unique_features) if unique_features else 1

        # now, fetch all the feature attributes for the unique features only
        # be super-smart and don't re-fetch geometries
        request = QgsFeatureRequest().setFilterFids(list(unique_features.keys())).setFlags(QgsFeatureRequest.NoGeometry)
        for current, f in enumerate(source.getFeatures(request)):
            if feedback.isCanceled():
                break

            # use already fetched geometry
            f.setGeometry(unique_features[f.id()])
            sink.addFeature(f, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(0.10 * current * total) + 90) # takes about 10% of time

        return {self.OUTPUT: dest_id}
示例#29
0
class EdgeCluster():
    def __init__(self, edges, initial_step_size, iterations, cycles,
                 compatibility):
        self.S = initial_step_size  # Weighting factor (needs to be cached, because will be decreased in every cycle)
        self.I = iterations  # Number of iterations per cycle (needs to be cached, because will be decreased in every cycle)
        self.edges = edges  # Edges to bundle in this cluster
        self.edge_lengths = []  # Array to cache edge lenghts
        self.E = len(edges)  # Number of edges
        self.EP = 2  # Current number of edge points
        self.SP = 0  # Current number of subdivision points
        self.compatibility = compatibility
        self.cycles = cycles
        self.compatibility_matrix = np.zeros(
            shape=(self.E, self.E))  # Compatibility matrix
        self.direction_matrix = np.zeros(
            shape=(self.E, self.E))  # Encodes direction of edge pairs
        self.N = (2**cycles) + 1  # Maximum number of points per edge
        self.epm_x = np.zeros(shape=(self.E,
                                     self.N))  # Bundles edges (x-values)
        self.epm_y = np.zeros(shape=(self.E,
                                     self.N))  # Bundles edges (y-values)

        self.segments = {}  # dict of Edges containing {id_seg: seg}
        self.allSegments = {}  # {id_seg: [seg as Edge, MainEdge id]}

    def get_size(self):
        return len(self.edges)

    def get_segments(self, edge):
        return self.segments[edge.id()]

    def create_segments(self, weight_index, feedback):
        self.index = QgsSpatialIndex()
        seg_id = 0
        for i, edge in enumerate(self.edges):
            if feedback.isCanceled(): return
            feedback.setProgress(100.0 * (i + 1) / len(self.edges))
            geom = edge.geometry()
            attrs = edge.attributes()
            polyline = geom.asPolyline()
            segments = {}
            for j in range(0, len(polyline) - 1):
                feat = QgsFeature()
                feat.setId(seg_id)
                seg_id += 1
                g = QgsGeometry.fromPolylineXY([polyline[j], polyline[j + 1]])
                feat.setGeometry(g)
                feat.setAttributes(attrs + [j])  #Add the rank of the segment
                segEdge = Edge(feat, weight_index)
                self.allSegments[segEdge.id()] = [segEdge, edge.id()]
                self.index.addFeature(segEdge)
                segments[segEdge.id()] = segEdge
            self.segments[edge.id()] = segments

    def collapse_lines(self, max_distance, feedback):
        for i, edge1 in enumerate(self.edges):
            feedback.setProgress(100.0 * (i + 1) / len(self.edges))
            if feedback.isCanceled(): return
            segments1 = self.get_segments(edge1)
            for key1, segment1 in segments1.items():
                geom1 = segment1.geometry()
                # get other edges in the vicinty
                tolerance = min(max_distance, geom1.length() / 2)
                ids = self.index.intersects(
                    geom1.buffer(tolerance, 4).boundingBox())
                # feedback.setProgressText("{0} matching segments with tolerance of {1}".format(len(ids), tolerance))
                keys2pop = []
                for id in ids:
                    edge2_id = self.allSegments[id][1]
                    if edge2_id == edge1.id(): continue
                    segment2 = self.allSegments[id][0]
                    geom2 = segment2.geometry()
                    d0 = geom1.vertexAt(0).distance(geom2.vertexAt(0))
                    d1 = geom1.vertexAt(1).distance(geom2.vertexAt(1))
                    if d0 <= (tolerance / 2) and d1 <= (tolerance / 2):
                        segment1.increase_weight(segment2.get_weight())
                        keys2pop.append(id)
                    d0 = geom1.vertexAt(0).distance(geom2.vertexAt(1))
                    d1 = geom1.vertexAt(1).distance(geom2.vertexAt(0))
                    if d0 <= (tolerance / 2) and d1 <= (tolerance / 2):
                        segment1.increase_weight(segment2.get_weight())
                        keys2pop.append(id)
                for id in keys2pop:
                    self.index.deleteFeature(self.allSegments[id][0])
                    self.segments[self.allSegments[id][1]].pop(id)
                    self.allSegments.pop(id)

    def compute_compatibilty_matrix(self, feedback):
        """
        Compatibility is stored in a matrix (rows = edges, columns = edges).
        Every coordinate in the matrix tells whether the two edges (r,c)/(c,r)
        are compatible, or not. The diagonal is always zero, and the other fields
        are filled with either -1 (not compatible) or 1 (compatible).
        The matrix is symmetric.
        """
        feedback.setProgressText("Compute compatibility matrix")
        edges_as_geom = []
        edges_as_vect = []
        for e_idx, edge in enumerate(self.edges):
            if feedback.isCanceled(): return
            geom = edge.geometry()
            edges_as_geom.append(geom)
            edges_as_vect.append(
                QgsVector(
                    geom.vertexAt(1).x() - geom.vertexAt(0).x(),
                    geom.vertexAt(1).y() - geom.vertexAt(0).y()))
            self.edge_lengths.append(edges_as_vect[e_idx].length())

        progress = 0
        for i in range(self.E - 1):
            if feedback.isCanceled(): return
            feedback.setProgress(100.0 * (i + 1) / (self.E - 1))
            for j in range(i + 1, self.E):
                if feedback.isCanceled(): return
                # Parameters
                lavg = (self.edge_lengths[i] + self.edge_lengths[j]) / 2.0
                dot = edges_as_vect[i].normalized(
                ) * edges_as_vect[j].normalized()

                # Angle compatibility
                angle_comp = abs(dot)

                # Scale compatibility
                scale_comp = 2.0 / (
                    lavg / min(self.edge_lengths[i], self.edge_lengths[j]) +
                    max(self.edge_lengths[i], self.edge_lengths[j]) / lavg)

                # Position compatibility
                i0 = edges_as_geom[i].vertexAt(0)
                i1 = edges_as_geom[i].vertexAt(1)
                j0 = edges_as_geom[j].vertexAt(0)
                j1 = edges_as_geom[j].vertexAt(1)
                e1_mid = QgsPoint((i0.x() + i1.x()) / 2.0,
                                  (i0.y() + i1.y()) / 2.0)
                e2_mid = QgsPoint((j0.x() + j1.x()) / 2.0,
                                  (j0.y() + j1.y()) / 2.0)
                diff = QgsVector(e2_mid.x() - e1_mid.x(),
                                 e2_mid.y() - e1_mid.y())
                pos_comp = lavg / (lavg + diff.length())

                # Visibility compatibility
                mid_E1 = edges_as_geom[i].centroid()
                mid_E2 = edges_as_geom[j].centroid()
                #dist = mid_E1.distance(mid_E2)
                I0 = MiscUtils.project_point_on_line(j0, edges_as_geom[i])
                I1 = MiscUtils.project_point_on_line(j1, edges_as_geom[i])
                mid_I = QgsGeometry.fromPolyline([I0, I1]).centroid()
                dist_I = I0.distance(I1)
                if dist_I == 0.0:
                    visibility1 = 0.0
                else:
                    visibility1 = max(
                        0, 1 - ((2 * mid_E1.distance(mid_I)) / dist_I))
                J0 = MiscUtils.project_point_on_line(i0, edges_as_geom[j])
                J1 = MiscUtils.project_point_on_line(i1, edges_as_geom[j])
                mid_J = QgsGeometry.fromPolyline([J0, J1]).centroid()
                dist_J = J0.distance(J1)
                if dist_J == 0.0:
                    visibility2 = 0.0
                else:
                    visibility2 = max(
                        0, 1 - ((2 * mid_E2.distance(mid_J)) / dist_J))
                visibility_comp = min(visibility1, visibility2)

                # Compatibility score
                comp_score = angle_comp * scale_comp * pos_comp * visibility_comp

                # Fill values into the matrix (1 = yes, -1 = no) and use matrix symmetry (i/j = j/i)
                if comp_score >= self.compatibility:
                    self.compatibility_matrix[i, j] = 1
                    self.compatibility_matrix[j, i] = 1
                else:
                    self.compatibility_matrix[i, j] = -1
                    self.compatibility_matrix[j, i] = -1

                # Store direction
                distStart1 = j0.distance(i0)
                distStart2 = j1.distance(i0)
                if distStart1 > distStart2:
                    self.direction_matrix[i, j] = -1
                    self.direction_matrix[j, i] = -1
                else:
                    self.direction_matrix[i, j] = 1
                    self.direction_matrix[j, i] = 1

    def force_directed_eb(self, feedback):
        """ Force-directed edge bundling """
        if feedback.isCanceled(): return
        # Create compatibility matrix
        self.compute_compatibilty_matrix(feedback)
        feedback.setCurrentStep(2)
        if feedback.isCanceled(): return

        for e_idx, edge in enumerate(self.edges):
            vertices = edge.geometry().asPolyline()
            self.epm_x[e_idx, 0] = vertices[0].x()
            self.epm_y[e_idx, 0] = vertices[0].y()
            self.epm_x[e_idx, self.N - 1] = vertices[1].x()
            self.epm_y[e_idx, self.N - 1] = vertices[1].y()

        # For each cycle
        feedback.setProgressText('Compute force-directed layout')
        for c in range(self.cycles):
            if feedback.isCanceled(): return
            # New number of subdivision points
            current_num = self.EP
            currentindeces = []
            for i in range(current_num):
                idx = int(
                    (float(i) / float(current_num - 1)) * float(self.N - 1))
                currentindeces.append(idx)
            self.SP += 2**c
            self.EP = self.SP + 2
            edgeindeces = []
            newindeces = []
            for i in range(self.EP):
                idx = int((float(i) / float(self.EP - 1)) * float(self.N - 1))
                edgeindeces.append(idx)
                if idx not in currentindeces:
                    newindeces.append(idx)
            pointindeces = edgeindeces[1:self.EP - 1]

            # Calculate position of new points
            for idx in newindeces:
                if feedback.isCanceled(): return
                i = int((float(idx) / float(self.N - 1)) * float(self.EP - 1))
                left = i - 1
                leftidx = int(
                    (float(left) / float(self.EP - 1)) * float(self.N - 1))
                right = i + 1
                rightidx = int(
                    (float(right) / float(self.EP - 1)) * float(self.N - 1))
                self.epm_x[:, idx] = (self.epm_x[:, leftidx] +
                                      self.epm_x[:, rightidx]) / 2.0
                self.epm_y[:, idx] = (self.epm_y[:, leftidx] +
                                      self.epm_y[:, rightidx]) / 2.0

            # Needed for spring forces
            KP0 = np.zeros(shape=(self.E, 1))
            KP0[:, 0] = np.asarray(self.edge_lengths)
            KP = K / (KP0 * (self.EP - 1))

            # For all iterations (number decreased in every cycle)
            for iteration in range(self.I):
                if feedback.isCanceled(): return
                if (iteration + 1) % 5 == 0:
                    feedback.pushCommandInfo(
                        "Cycle {0} and Iteration {1}".format(
                            c + 1, iteration + 1))
                    feedback.setProgress(100.0 * (iteration + 1) / self.I)
                # Spring forces
                middlepoints_x = self.epm_x[:, pointindeces]
                middlepoints_y = self.epm_y[:, pointindeces]
                neighbours_left_x = self.epm_x[:, edgeindeces[0:self.EP - 2]]
                neighbours_left_y = self.epm_y[:, edgeindeces[0:self.EP - 2]]
                neighbours_right_x = self.epm_x[:, edgeindeces[2:self.EP]]
                neighbours_right_y = self.epm_y[:, edgeindeces[2:self.EP]]
                springforces_x = (neighbours_left_x - middlepoints_x +
                                  neighbours_right_x - middlepoints_x) * KP
                springforces_y = (neighbours_left_y - middlepoints_y +
                                  neighbours_right_y - middlepoints_y) * KP

                # Electrostatic forces
                electrostaticforces_x = np.zeros(shape=(self.E, self.SP))
                electrostaticforces_y = np.zeros(shape=(self.E, self.SP))
                # Loop through all edges
                for e_idx, edge in enumerate(self.edges):
                    if feedback.isCanceled(): return
                    # Loop through compatible edges
                    comp_list = np.where(
                        self.compatibility_matrix[:, e_idx] > 0)
                    for other_idx in np.nditer(comp_list, ['zerosize_ok']):
                        if feedback.isCanceled(): return
                        otherindeces = pointindeces[:]
                        if self.direction_matrix[e_idx, other_idx] < 0:
                            otherindeces.reverse()
                        # Distance between points
                        subtr_x = self.epm_x[
                            other_idx, otherindeces] - self.epm_x[e_idx,
                                                                  pointindeces]
                        subtr_y = self.epm_y[
                            other_idx, otherindeces] - self.epm_y[e_idx,
                                                                  pointindeces]
                        distance = np.sqrt(
                            np.add(np.multiply(subtr_x, subtr_x),
                                   np.multiply(subtr_y, subtr_y)))
                        flocal_x = map(forcecalcx, subtr_x, subtr_y, distance)
                        flocal_y = map(forcecalcy, subtr_x, subtr_y, distance)
                        # Sum of forces
                        electrostaticforces_x[e_idx, :] += np.array(
                            list(flocal_x))
                        electrostaticforces_y[e_idx, :] += np.array(
                            list(flocal_y))

                # Compute total forces
                force_x = (springforces_x + electrostaticforces_x) * self.S
                force_y = (springforces_y + electrostaticforces_y) * self.S

                # Compute new point positions
                self.epm_x[:, pointindeces] += force_x
                self.epm_y[:, pointindeces] += force_y

            # Adjustments for next cycle
            self.S = self.S * sdc  # Decrease weighting factor
            self.I = int(round(self.I * idc))  # Decrease iterations

        feedback.setProgressText("Ongoing final calculations")
        for e_idx in range(self.E):
            if feedback.isCanceled(): return
            # Create a new polyline out of the line array
            line = map(lambda p, q: QgsPoint(p, q), self.epm_x[e_idx],
                       self.epm_y[e_idx])
            self.edges[e_idx].setGeometry(QgsGeometry.fromPolyline(line))
示例#30
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER,
                                            context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE,
                                             context)

        bbox = source.sourceExtent()
        sourceIndex = QgsSpatialIndex(source, feedback)

        fields = QgsFields()
        fields.append(QgsField('id', QVariant.Int, '', 10, 0))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, fields,
                                               QgsWkbTypes.Point,
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        nPoints = 0
        nIterations = 0
        maxIterations = pointCount * 200
        total = 100.0 / pointCount if pointCount else 1

        index = QgsSpatialIndex()
        points = dict()

        random.seed()

        while nIterations < maxIterations and nPoints < pointCount:
            if feedback.isCanceled():
                break

            rx = bbox.xMinimum() + bbox.width() * random.random()
            ry = bbox.yMinimum() + bbox.height() * random.random()

            p = QgsPointXY(rx, ry)
            geom = QgsGeometry.fromPointXY(p)
            ids = sourceIndex.intersects(geom.buffer(5, 5).boundingBox())
            if len(ids) > 0 and \
                    vector.checkMinDistance(p, index, minDistance, points):
                request = QgsFeatureRequest().setFilterFids(
                    ids).setSubsetOfAttributes([])
                for f in source.getFeatures(request):
                    if feedback.isCanceled():
                        break

                    tmpGeom = f.geometry()
                    if geom.within(tmpGeom):
                        f = QgsFeature(nPoints)
                        f.initAttributes(1)
                        f.setFields(fields)
                        f.setAttribute('id', nPoints)
                        f.setGeometry(geom)
                        sink.addFeature(f, QgsFeatureSink.FastInsert)
                        index.addFeature(f)
                        points[nPoints] = p
                        nPoints += 1
                        feedback.setProgress(int(nPoints * total))
            nIterations += 1

        if nPoints < pointCount:
            feedback.pushInfo(
                self.tr(
                    'Could not generate requested number of random points. '
                    'Maximum number of attempts exceeded.'))

        return {self.OUTPUT: dest_id}
示例#31
0
class GsCollection:
    """Class used for managing the QgsFeature spatially.

    QgsSpatialIndex class is used to store and retrieve the features.
    """

    __slots__ = ('_spatial_index', '_dict_qgs_segment', '_id_qgs_segment')

    def __init__(self):
        """Constructor that initialize the GsCollection.

        """

        self._spatial_index = QgsSpatialIndex()
        self._dict_qgs_segment = {
        }  # Contains a reference to the original geometry
        self._id_qgs_segment = 0

    def _get_next_id_segment(self):
        """Increment the id of the segment.

        :return: Value of the next ID
        :rtype: int
        """

        self._id_qgs_segment += 1

        return self._id_qgs_segment

    def _create_rectangle(self, geom_id, qgs_geom):
        """Creates a new QgsRectangle to load in the QgsSpatialIndex.

        :param: geom_id: Integer ID of the geometry
        :param: qgs_geom: QgsGeometry to use for bounding box extraction
        :return: The feature created
        :rtype: QgsFeature
        """

        id_segment = self._get_next_id_segment()
        self._dict_qgs_segment[id_segment] = (
            geom_id, qgs_geom)  # Reference to the RbGeom ID and geometry

        return id_segment, qgs_geom.boundingBox()

    def add_features(self, rb_geoms, feedback):
        """Add a RbGeom object in the spatial index.

        For the LineString geometries. The geometry is broken into each line segment that are individually
        loaded in the QgsSpatialIndex.  This strategy accelerate the validation of the spatial constraints.

        :param: rb_geoms: List of RbGeom to load in the QgsSpatialIndex
        :feedback: QgsFeedback handle used to update the progress bar
        """

        progress_bar = ProgressBar(feedback, len(rb_geoms),
                                   "Building internal structure...")
        for val, rb_geom in enumerate(rb_geoms):
            progress_bar.set_value(val)
            qgs_rectangles = []
            if rb_geom.qgs_geom.wkbType() == QgsWkbTypes.Point:
                qgs_rectangles.append(
                    self._create_rectangle(rb_geom.id, rb_geom.qgs_geom))
            else:
                qgs_points = rb_geom.qgs_geom.constGet().points()
                for i in range(0, (len(qgs_points) - 1)):
                    qgs_geom = QgsGeometry(
                        QgsLineString(qgs_points[i], qgs_points[i + 1]))
                    qgs_rectangles.append(
                        self._create_rectangle(rb_geom.id, qgs_geom))

            for geom_id, qgs_rectangle in qgs_rectangles:
                self._spatial_index.addFeature(geom_id, qgs_rectangle)

        return

    def get_segment_intersect(self, qgs_geom_id, qgs_rectangle,
                              qgs_geom_subline):
        """Find the feature that intersects the bounding box.

        Once the line string intersecting the bounding box are found. They are separated into 2 lists.
        The first one being the line string with the same id (same line) the second one all the others line string.

        :param qgs_geom_id: ID of the line string that is being simplified
        :param qgs_rectangle: QgsRectangle used for feature intersection
        :param qgs_geom_subline: LineString used to remove line segment superimposed to this line string
        :return: Two lists  of line string segment. First: Line string with same id; Second all the others
        :rtype: tuple of 2 lists
        """

        qgs_geoms_with_itself = []
        qgs_geoms_with_others = []
        qgs_rectangle.grow(
            Epsilon.ZERO_RELATIVE *
            100.)  # Always increase the b_box to avoid degenerated b_box
        ids = self._spatial_index.intersects(qgs_rectangle)
        for geom_id in ids:
            target_qgs_geom_id, target_qgs_geom = self._dict_qgs_segment[
                geom_id]
            if target_qgs_geom_id is None:
                # Nothing to do; segment was deleted
                pass
            else:
                if target_qgs_geom_id == qgs_geom_id:
                    # Test that the segment is not part of qgs_subline
                    if not target_qgs_geom.within(qgs_geom_subline):
                        qgs_geoms_with_itself.append(target_qgs_geom)
                else:
                    qgs_geoms_with_others.append(target_qgs_geom)

        return qgs_geoms_with_itself, qgs_geoms_with_others

    def _delete_segment(self, qgs_geom_id, qgs_pnt0, qgs_pnt1):
        """Delete a line segment in the spatial index based on start/end points.

        To minimise the number of feature returned we search for a very small bounding box located in the middle
        of the line segment.  Usually only one line segment is returned.

        :param qgs_geom_id: Integer ID of the geometry
        :param qgs_pnt0 : QgsPoint start point of the target line segment.
        :param qgs_pnt1 : QgsPoint end point of the target line segment.
        """

        qgs_geom_to_delete = QgsGeometry(QgsLineString(qgs_pnt0, qgs_pnt1))
        qgs_mid_point = QgsGeometryUtils.midpoint(qgs_pnt0, qgs_pnt1)
        qgs_rectangle = qgs_mid_point.boundingBox()
        qgs_rectangle.grow(Epsilon.ZERO_RELATIVE * 100)
        deleted = False
        ids = self._spatial_index.intersects(qgs_rectangle)
        for geom_id in ids:
            target_qgs_geom_id, target_qgs_geom = self._dict_qgs_segment[
                geom_id]  # Extract id and geometry
            if qgs_geom_id == target_qgs_geom_id:
                # Only check for the same ID
                if target_qgs_geom.equals(
                        qgs_geom_to_delete):  # Check if it's the same geometry
                    deleted = True
                    self._dict_qgs_segment[geom_id] = (
                        None, None)  # Delete from the internal structure
                    break

        if not deleted:
            raise Exception(
                QgsProcessingException("Internal structure corruption..."))

        return

    def _delete_vertex(self, rb_geom, v_id_start, v_id_end):
        """Delete consecutive vertex in the line and update the spatial index.

        When a vertex in a line string is deleted.  Two line segments are deleted and one line segment is
        created in the spatial index.  Cannot delete the first/last vertex of a line string

        :param rb_geom: LineString object to update.
        :param v_id_start: start of the vertex to delete.
        :param v_id_end: end of the vertex to delete.
        """

        is_closed = rb_geom.qgs_geom.constGet().isClosed()
        v_ids_to_del = list(range(v_id_start, v_id_end + 1))
        if v_id_start == 0 and is_closed:
            # Special case for closed line where we simulate a circular array
            nbr_vertice = rb_geom.qgs_geom.constGet().numPoints()
            v_ids_to_del.insert(0, nbr_vertice - 2)
        else:
            v_ids_to_del.insert(0, v_ids_to_del[0] - 1)
        v_ids_to_del.append(v_ids_to_del[-1] + 1)

        # Delete the line segment in the spatial index
        for i in range(len(v_ids_to_del) - 1):
            qgs_pnt0 = rb_geom.qgs_geom.vertexAt(v_ids_to_del[i])
            qgs_pnt1 = rb_geom.qgs_geom.vertexAt(v_ids_to_del[i + 1])
            self._delete_segment(rb_geom.id, qgs_pnt0, qgs_pnt1)

        # Add the new line segment in the spatial index
        qgs_pnt0 = rb_geom.qgs_geom.vertexAt(v_ids_to_del[0])
        qgs_pnt1 = rb_geom.qgs_geom.vertexAt(v_ids_to_del[-1])
        qgs_geom_segment = QgsGeometry(QgsLineString(qgs_pnt0, qgs_pnt1))
        geom_id, qgs_rectangle = self._create_rectangle(
            rb_geom.id, qgs_geom_segment)
        self._spatial_index.addFeature(geom_id, qgs_rectangle)

        # Delete the vertex in the line string geometry
        for v_id_to_del in reversed(range(v_id_start, v_id_end + 1)):
            rb_geom.qgs_geom.deleteVertex(v_id_to_del)
            if v_id_start == 0 and is_closed:
                # Special case for closed line where we simulate a circular array
                nbr_vertice = rb_geom.qgs_geom.constGet().numPoints()
                qgs_pnt_first = rb_geom.qgs_geom.vertexAt(0)
                rb_geom.qgs_geom.insertVertex(qgs_pnt_first, nbr_vertice - 1)
                rb_geom.qgs_geom.deleteVertex(nbr_vertice)

        return

    def delete_vertex(self, rb_geom, v_id_start, v_id_end):
        """Manage deletion of consecutives vertex.

        If v_id_start is greater than v_id_end the delete is broken into up to 3 calls

        :param rb_geom: LineString object to update.
        :param v_id_start: start of the vertex to delete.
        :param v_id_end: end of the vertex to delete.
        """

        num_points = rb_geom.qgs_geom.constGet().numPoints()
        # Manage closes line where first/last vertice are the same
        if v_id_start == num_points - 1:
            v_id_start = 0  # Last point is the same as the first vertice
        if v_id_end == -1:
            v_id_end = num_points - 2  # Preceding point the first/last vertice

        if v_id_start <= v_id_end:
            self._delete_vertex(rb_geom, v_id_start, v_id_end)
        else:
            self._delete_vertex(rb_geom, v_id_start, num_points - 2)
            self._delete_vertex(rb_geom, 0, 0)
            if v_id_end > 0:
                self._delete_vertex(rb_geom, 1, v_id_end)


#            lst_vertex_to_del = list(range(v_id_start, num_points)) + list(range(0, v_id_end+1))
#            for vertex_to_del in lst_vertex_to_del:
#                self._delete_vertex(rb_geom, vertex_to_del, vertex_to_del)

#        num_points = rb_geom.qgs_geom.constGet().numPoints()
#        lst_vertex_to_del = list(range(v_id_start, num_points)) + list(range(0, v_id_end + 1))
#        for vertex_to_del in lst_vertex_to_del:
#            self._delete_vertex(rb_geom, vertex_to_del, vertex_to_del)

    def add_vertex(self, rb_geom, bend_i, bend_j, qgs_geom_new_subline):
        """Update the line segment in the spatial index

        :param rb_geom: RbGeom line to update
        :param bend_i: Start of the bend to delete
        :param bend_j: End of the bend to delete (always bend_i + 1)
        :param qgs_geom_new_subline: New sub line string to add in the spatial index
        :return:
        """

        # Delete the base of the bend
        qgs_pnt0 = rb_geom.qgs_geom.vertexAt(bend_i)
        qgs_pnt1 = rb_geom.qgs_geom.vertexAt(bend_j)
        self._delete_segment(rb_geom.id, qgs_pnt0, qgs_pnt1)

        qgs_points = qgs_geom_new_subline.constGet().points()
        tmp_qgs_points = qgs_points[1:-1]  # Drop first/last item
        # Insert the new vertex in the QgsGeometry. Work reversely to facilitate insertion
        for qgs_point in reversed(tmp_qgs_points):
            rb_geom.qgs_geom.insertVertex(qgs_point, bend_j)

        # Add the new segment in the spatial container
        for i in range(len(qgs_points) - 1):
            qgs_geom_segment = QgsGeometry(
                QgsLineString(qgs_points[i], qgs_points[i + 1]))
            geom_id, qgs_rectangle = self._create_rectangle(
                rb_geom.id, qgs_geom_segment)
            self._spatial_index.addFeature(geom_id, qgs_rectangle)

        return

    def validate_integrity(self, rb_geoms):
        """This method is used to validate the data structure at the end of the process

        This method is executed only when requested and for debug purpose only.  It's validating the data structure
        by removing element from it the data structure is unusable after. Validate integrity must be the last
        operation before ending the program as it destroy the data structure...

        :param rb_geoms: Geometry contained in the spatial container
        :return: Flag indicating if the structure is valid. True: is valid; False: is not valid
        :rtype: Boolean
        """

        is_structure_valid = True
        # from the geometry remove all the segment in the spatial index.
        for rb_geom in rb_geoms:
            qgs_line_string = rb_geom.qgs_geom.constGet()
            if qgs_line_string.wkbType() == QgsWkbTypes.LineString:
                qgs_points = qgs_line_string.points()
                for i in range(len(qgs_points) - 1):
                    self._delete_segment(rb_geom.id, qgs_points[i],
                                         qgs_points[i + 1])

        if is_structure_valid:
            # Verify that there are no other feature in the spatial index; except for QgsPoint
            qgs_rectangle = QgsRectangle(-sys.float_info.max,
                                         -sys.float_info.max,
                                         sys.float_info.max,
                                         sys.float_info.max)
            feat_ids = self._spatial_index.intersects(qgs_rectangle)
            for feat_id in feat_ids:
                qgs_geom = self._spatial_index.geometry(feat_id)
                if qgs_geom.wkbType() == QgsWkbTypes.Point:
                    pass
                else:
                    # Error
                    is_structure_valid = False

        return is_structure_valid
示例#32
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        proximity = self.parameterAsDouble(parameters, self.PROXIMITY, context)
        radius = self.parameterAsDouble(parameters, self.DISTANCE, context)
        horizontal = self.parameterAsBool(parameters, self.HORIZONTAL, context)

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               source.fields(), source.wkbType(), source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        features = source.getFeatures()

        total = 100.0 / source.featureCount() if source.featureCount() else 0

        def searchRect(p):
            return QgsRectangle(p.x() - proximity, p.y() - proximity, p.x() + proximity, p.y() + proximity)

        index = QgsSpatialIndex()

        # NOTE: this is a Python port of QgsPointDistanceRenderer::renderFeature. If refining this algorithm,
        # please port the changes to QgsPointDistanceRenderer::renderFeature also!

        clustered_groups = []
        group_index = {}
        group_locations = {}
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            if not f.hasGeometry():
                continue

            point = f.geometry().asPoint()

            other_features_within_radius = index.intersects(searchRect(point))
            if not other_features_within_radius:
                index.addFeature(f)
                group = [f]
                clustered_groups.append(group)
                group_index[f.id()] = len(clustered_groups) - 1
                group_locations[f.id()] = point
            else:
                # find group with closest location to this point (may be more than one within search tolerance)
                min_dist_feature_id = other_features_within_radius[0]
                min_dist = group_locations[min_dist_feature_id].distance(point)
                for i in range(1, len(other_features_within_radius)):
                    candidate_id = other_features_within_radius[i]
                    new_dist = group_locations[candidate_id].distance(point)
                    if new_dist < min_dist:
                        min_dist = new_dist
                        min_dist_feature_id = candidate_id

                group_index_pos = group_index[min_dist_feature_id]
                group = clustered_groups[group_index_pos]

                # calculate new centroid of group
                old_center = group_locations[min_dist_feature_id]
                group_locations[min_dist_feature_id] = QgsPointXY((old_center.x() * len(group) + point.x()) / (len(group) + 1.0),
                                                                  (old_center.y() * len(group) + point.y()) / (len(group) + 1.0))
                # add to a group
                clustered_groups[group_index_pos].append(f)
                group_index[f.id()] = group_index_pos

            feedback.setProgress(int(current * total))

        current = 0
        total = 100.0 / len(clustered_groups) if clustered_groups else 1
        feedback.setProgress(0)

        fullPerimeter = 2 * math.pi

        for group in clustered_groups:
            if feedback.isCanceled():
                break

            count = len(group)
            if count == 1:
                sink.addFeature(group[0], QgsFeatureSink.FastInsert)
            else:
                angleStep = fullPerimeter / count
                if count == 2 and horizontal:
                    currentAngle = math.pi / 2
                else:
                    currentAngle = 0

                old_point = group_locations[group[0].id()]

                for f in group:
                    if feedback.isCanceled():
                        break

                    sinusCurrentAngle = math.sin(currentAngle)
                    cosinusCurrentAngle = math.cos(currentAngle)
                    dx = radius * sinusCurrentAngle
                    dy = radius * cosinusCurrentAngle

                    # we want to keep any existing m/z values
                    point = f.geometry().constGet().clone()
                    point.setX(old_point.x() + dx)
                    point.setY(old_point.y() + dy)
                    f.setGeometry(QgsGeometry(point))

                    sink.addFeature(f, QgsFeatureSink.FastInsert)
                    currentAngle += angleStep

            current += 1
            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
示例#33
0
文件: __init__.py 项目: mcgoldba/XPSS
    def initialize_from_qepanet(self):
        """
        get system details from the qgis vector layers (a lot of this taken
        from in_writer.py in qepanet)
        """

        #Initalize data Matrices and vectors for calculations

        C = [
        ]  #Connection matrix:  describes how each pipe the pipes are connected together
        Pipe_props_raw = [
        ]  #Pipe Property Matrix:  stores properties of each pipes as
        # [ id ]
        Pipe_nodes_raw = [
        ]  #Pipe Nodes:  stores pipe start and end nodes to build thet connection matrix
        Node_props_raw = [
        ]  #Node Property Matrix:  stores properties of each junction as
        # [ id, elev, zone_end ]

        # Build nodes spatial index
        sindex = QgsSpatialIndex()
        res = []
        res_elev = []
        j = 0
        r = 0
        t = 0

        pipe_fts = self.qgisparams.pipes_vlay.getFeatures()
        junc_fts = self.qgisparams.junctions_vlay.getFeatures()
        res_fts = self.qgisparams.reservoirs_vlay.getFeatures()
        #tank_fts = self.qgisparams.tanks_vlay.getFeatures()
        #pump_fts = self.qgisparams.pumps_vlay.getFeatures()

        for feat in junc_fts:
            sindex.addFeature(feat)
            j += 1
        for feat in res_fts:
            sindex.addFeature(feat)
            res.append(feat.attribute(Reservoir.field_name_eid))
            res_elev.append(feat.attribute(Reservoir.field_name_elev))
            r += 1
        #logger.progress("Reservoir: "+str(res))
        # for feat in tank_fts:
        #     sindex.addFeature(feat)
        #     t+=1

        if len(
                res
        ) != 1:  #check to see if there is a single reservoir.  Analysis assumes that all end nodes are pumps pump to a single outlet
            logger.error(
                "The number of reserviors in the network must equal 1.")

        for pipe_ft in pipe_fts:

            eid = pipe_ft.attribute(Pipe.field_name_eid)

            # Find start/end nodes
            # adj_nodes = NetworkUtils.find_start_end_nodes(params, pipe_ft.geometry())
            adj_nodes = NetworkUtils.find_start_end_nodes_sindex(
                self.qgisparams, sindex, pipe_ft.geometry())

            start_node_id = adj_nodes[0].attribute(Junction.field_name_eid)
            end_node_id = adj_nodes[1].attribute(Junction.field_name_eid)

            Pipe_nodes_raw.append([eid, start_node_id, end_node_id])

            #length = pipe_ft.attribute(Pipe.field_name_length)
            #diameter = pipe_ft.attribute(Pipe.field_name_diameter)
            #roughness = pipe_ft.attribute(Pipe.field_name_roughness)
            #minor_loss = pipe_ft.attribute(Pipe.field_name_minor_loss)
            #status = pipe_ft.attribute(Pipe.field_name_status)
            #description = pipe_ft.attribute(Pipe.field_name_description)
            #tag_name = pipe_ft.attribute(Pipe.field_name_tag)
            #num_edu = pipe_ft.attribute(Pipe.field_name_num_edu)

            Pipe_props_raw.append([eid])

        #reset the iterator for junc_fts
        junc_fts = self.qgisparams.junctions_vlay.getFeatures()

        for junc_ft in junc_fts:

            eid = junc_ft.attribute(Junction.field_name_eid)
            elev = junc_ft.attribute(Junction.field_name_elev)
            zone_end = junc_ft.attribute(QJunction.field_name_zone_end)

            Node_props_raw.append([eid, elev, zone_end])

        Pipe_nodes = pd.DataFrame(
            Pipe_nodes_raw, columns=['Pipe ID', 'Node 1 ID', 'Node 2 ID'])
        Pipe_props = pd.DataFrame(Pipe_props_raw,
                                  columns=['Pipe ID'],
                                  dtype='float')

        Node_props = pd.DataFrame(
            Node_props_raw,
            columns=['Node ID', 'Elevation [ft]', 'Zone End'],
            dtype='float')

        #logger.progress("Node_props:\n", Node_props)

        #logger.progress(str(Pipe_nodes))
        #logger.progress("Node_props: \n", Node_props)
        #logger.progress(Node_props.dtypes)
        #logger.progress("'Pipe_nodes' and 'Pipe_props' vectors populated...")

        return [Pipe_props, Node_props, Pipe_nodes, res, res_elev]
示例#34
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context)

        bbox = source.sourceExtent()
        sourceIndex = QgsSpatialIndex(source, feedback)

        fields = QgsFields()
        fields.append(QgsField('id', QVariant.Int, '', 10, 0))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.Point, source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        nPoints = 0
        nIterations = 0
        maxIterations = pointCount * 200
        total = 100.0 / pointCount if pointCount else 1

        index = QgsSpatialIndex()
        points = dict()

        random.seed()

        while nIterations < maxIterations and nPoints < pointCount:
            if feedback.isCanceled():
                break

            rx = bbox.xMinimum() + bbox.width() * random.random()
            ry = bbox.yMinimum() + bbox.height() * random.random()

            p = QgsPointXY(rx, ry)
            geom = QgsGeometry.fromPointXY(p)
            ids = sourceIndex.intersects(geom.buffer(5, 5).boundingBox())
            if len(ids) > 0 and \
                    vector.checkMinDistance(p, index, minDistance, points):
                request = QgsFeatureRequest().setFilterFids(ids).setSubsetOfAttributes([])
                for f in source.getFeatures(request):
                    if feedback.isCanceled():
                        break

                    tmpGeom = f.geometry()
                    if geom.within(tmpGeom):
                        f = QgsFeature(nPoints)
                        f.initAttributes(1)
                        f.setFields(fields)
                        f.setAttribute('id', nPoints)
                        f.setGeometry(geom)
                        sink.addFeature(f, QgsFeatureSink.FastInsert)
                        index.addFeature(f)
                        points[nPoints] = p
                        nPoints += 1
                        feedback.setProgress(int(nPoints * total))
            nIterations += 1

        if nPoints < pointCount:
            feedback.pushInfo(self.tr('Could not generate requested number of random points. '
                                      'Maximum number of attempts exceeded.'))

        return {self.OUTPUT: dest_id}
示例#35
0
文件: __init__.py 项目: mcgoldba/XPSS
    def check(self, check_pipe_conns, check_node_conns):
        #TODO:  rewrite to use a pandas dataframe

        sindex = QgsSpatialIndex()
        pipe_fts = self.qgisparams.pipes_vlay.getFeatures()
        junc_fts = self.qgisparams.junctions_vlay.getFeatures()
        res_fts = self.qgisparams.reservoirs_vlay.getFeatures()
        # tank_fts = self.qgisparams.tanks_vlay.getFeatures()
        # pump_fts = self.qgisparams.pumps_vlay.getFeatures()

        logger.progress("%%%%%%%%%%% BEGIN CHECK OF PSS SYSTEM %%%%%%%%%%%")

        #get system details from the qgis vector layers (a lot of this taken from in_writer.py in qepanet)

        #logger.progress("pipe_lst: "+str(np.asarray(pipe_lst).shape))

        # Build nodes spatial index
        #sindex = QgsSpatialIndex()
        res = []
        j = 0
        r = 0
        t = 0
        l = 0
        p = 0

        has_error = False
        num_entity_err = False
        logger.progress("Summary of System Components:")
        for feat in junc_fts:
            sindex.addFeature(feat)
            j += 1
        logger.progress(str(j) + " Junctions")

        for feat in res_fts:
            sindex.addFeature(feat)
            res.append(feat.attribute(Reservoir.field_name_eid))
            r += 1
        logger.progress(str(r) + " Reservoirs")

        # for feat in tank_fts:
        #     sindex.addFeature(feat)
        #     t+=1
        # logger.progress(str(t)+" Tanks")

        for feat in pipe_fts:
            l += 1
        logger.progress(str(l) + " Pipes")

        # for feat in pump_fts:
        #     p+=1
        # logger.progress(str(p)+" Pumps")

        if len(
                res
        ) != 1:  #check to see if there is a single reservoir.  Analysis assumes that all end nodes are pumps pump to a single outlet
            logger.error(
                "The number of reserviors in the network must equal 1.")
            has_error = True

        if j != l:  #check to see if the number of pipes equals the number of junctions.  This is true for a directed tree graph with a reservior as its end node.
            logger.error(
                "The number of junctions is not equal to the number of pipes.")
            #has_error = True  #commented to continue calc to get a more detailed error in "create_conn_matrix()
            num_entity_err = True

        #reset QGIS iterators
        pipe_fts = self.qgisparams.pipes_vlay.getFeatures()
        junc_fts = self.qgisparams.junctions_vlay.getFeatures()
        res_fts = self.qgisparams.reservoirs_vlay.getFeatures()
        # tank_fts = self.qgisparams.tanks_vlay.getFeatures()
        # pump_fts = self.qgisparams.pumps_vlay.getFeatures()

        node_lst = [junc_fts, res_fts]
        node_Class = [Junction, Reservoir]

        pipe_lst = [pipe_fts]
        pipe_Class = [Pipe]

        disconn_pipes = []
        disconn_juncs = []

        if check_pipe_conns is True:
            logger.progress("Cycling through all pipes to check connections..."
                            )  #TODO:  This is not working properly

            Pipe_nodes = []  #List of start and end nodes for each pipe

            all_pipes = []

            #TODO:  update to include all layers and check if each layer is valid

            for i in range(len(pipe_lst)):
                for ft in pipe_lst[i]:

                    #logger.progress("Checking pipe connection...")

                    eid = ft.attribute(pipe_Class[i].field_name_eid)

                    if num_entity_err == True:
                        all_pipes.append(eid)

                    #sindex = self.qgisparams.nodes_sindex
                    # Find start/end nodes
                    adj_nodes = NetworkUtils.find_start_end_nodes_sindex(
                        self.qgisparams, sindex, ft.geometry())

                    #adj_nodes = NetworkUtils.find_start_end_nodes(self.qgisparams, ft.geometry())  #TODO:  Determine why the 'sindex' version does not work

                    found_nodes = []

                    #try:
                    start_node_id = adj_nodes[0].attribute(
                        Junction.field_name_eid
                    )  #TODO:  Get Reservoir eid name in case it is different.
                    end_node_id = adj_nodes[1].attribute(
                        Junction.field_name_eid)

                    found_nodes.append(start_node_id)
                    found_nodes.append(end_node_id)

                    if start_node_id == end_node_id:
                        logger.error(
                            "Pipe " + eid +
                            " is connected to itself.  Connected nodes are " +
                            start_node_id + " and " + end_node_id + ".")
                        has_error = True

                    #check for any very short pipes:  #TODO:  This is not working correctly
                    short_pipes_nodes = []
                    if float(ft.attribute(Pipe.field_name_length)
                             ) < 2:  #TODO:  units are assumed to be ft
                        logger.error("Pipe " + eid +
                                     " is very short.  Connected nodes are" +
                                     start_node_id + " and " + end_node_id +
                                     ".")
                        if len(short_pipes) != 0:
                            start_node = False
                            end_node = False
                            for i in range(len(short_pipes_nodes)):
                                if start_node_id == short_pipe_nodes[i]:
                                    short_pipe_nodes.append(start_node_id)
                                    start_node = True
                                if end_node_id == short_pipe_nodes[i]:
                                    short_pipe_nodes.append(end_node_id)
                                    end_node == True
                                if (start_node == True) and (end_node == True):
                                    break

                        has_error = True

                    if len(short_pipe_nodes) > 0:
                        self.select_qgis_feature(
                            self.qgisparams.junctions_vlay, short_pipe_nodes)

                    Pipe_nodes.append([eid, start_node_id, end_node_id])

                    # except:
                    #     print_str = "Connected nodes are"
                    #     found_node = False
                    #     if start_node_id:
                    #         print_str += " "+start_node_id
                    #         found_node = True
                    #     if end_node_id:
                    #         print_str += " "+end_node_id
                    #         found_node = True
                    #     if found_node is False:
                    #         print_str = "No nodes were found for pipe"
                    #     logger.error("Pipe "+eid+" has less than 2 nodes connected.  "+print_str+".")
                    #     disconn_pipes.append(eid)
                    #     has_error = True

            if len(disconn_pipes) > 0:
                self.select_qgis_features(
                    self.qgisparams.pipes_vlay,
                    disconn_pipes)  #TODO:  assumes the vector layer is Pipes

        else:
            logger.progress("Pipe connection checks were not performed.")

            #logger.progress(str(Pipe_nodes))
        if check_node_conns is True:
            if check_pipe_conns is False:
                logger.warning(
                    "Pipe checks must be performed before checking nodes.")
                return [False, False, l, j]
            logger.progress("Cycling through all nodes to check connections..."
                            )  #TODO:  This is not working properly

            for i in range(len(node_lst)):
                for ft in node_lst[i]:
                    #logger.progress("Checking node connection...")
                    num_conn = 0
                    eid = ft.attribute(node_Class[i].field_name_eid)
                    for j in range(len(found_nodes)):
                        if found_nodes[j] == eid:
                            num_conn += 1
                    logger.progress("Connections for Node " + eid + ": " +
                                    str(num_conn))
                    if num_conn == 0:
                        logger.error("The node " + eid +
                                     " is not connected to any pipes.")
                        disconn_juncs.append(eid)
                        has_error = True

            if len(disconn_juncs) > 0:
                self.select_qgis_features(
                    self.qgisparams.junctions_vlay, disconn_juncs
                )  #TODO:  assumes the vector layer is Junctions
        else:
            logger.progress("Node connection checks were not performed.")

        if num_entity_err:
            logger.progress(
                "An Error was found!  The number of pipes does not match the number of junctions.  Additional details will be provided after parsing through the system."
            )

        elif has_error == True:
            logger.error(
                "Error(s) were found.  Check the system definition and correct.",
                stop=True)

        else:
            logger.progress("There are no errors found in the system!")

        logger.progress("%%%%%%%%%%% END CHECK OF PSS SYSTEM %%%%%%%%%%%")

        #return [has_error, num_entity_err, l, j]
        #Note:  'PSS.' needed so dervived classes do not overwrite existing values
        PSS.sysGeomError = has_error
        PSS.numEntityErr = num_entity_err
        PSS.nPipes = l
        PSS.nNodes = j
示例#36
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER,
                                            context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE,
                                             context)

        fields = QgsFields()
        fields.append(QgsField('id', QVariant.Int, '', 10, 0))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, fields,
                                               QgsWkbTypes.Point,
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        nPoints = 0
        nIterations = 0
        maxIterations = pointCount * 200
        featureCount = source.featureCount()
        total = 100.0 / pointCount if pointCount else 1

        index = QgsSpatialIndex()
        points = dict()

        da = QgsDistanceArea()
        da.setSourceCrs(source.sourceCrs(), context.transformContext())
        da.setEllipsoid(context.project().ellipsoid())

        request = QgsFeatureRequest()

        random.seed()

        while nIterations < maxIterations and nPoints < pointCount:
            if feedback.isCanceled():
                break

            # pick random feature
            fid = random.randint(0, featureCount - 1)
            f = next(
                source.getFeatures(
                    request.setFilterFid(fid).setSubsetOfAttributes([])))
            fGeom = f.geometry()

            if fGeom.isMultipart():
                lines = fGeom.asMultiPolyline()
                # pick random line
                lineId = random.randint(0, len(lines) - 1)
                vertices = lines[lineId]
            else:
                vertices = fGeom.asPolyline()

            # pick random segment
            if len(vertices) == 2:
                vid = 0
            else:
                vid = random.randint(0, len(vertices) - 2)
            startPoint = vertices[vid]
            endPoint = vertices[vid + 1]
            length = da.measureLine(startPoint, endPoint)
            dist = length * random.random()

            if dist > minDistance:
                d = dist / (length - dist)
                rx = (startPoint.x() + d * endPoint.x()) / (1 + d)
                ry = (startPoint.y() + d * endPoint.y()) / (1 + d)

                # generate random point
                p = QgsPointXY(rx, ry)
                geom = QgsGeometry.fromPointXY(p)
                if vector.checkMinDistance(p, index, minDistance, points):
                    f = QgsFeature(nPoints)
                    f.initAttributes(1)
                    f.setFields(fields)
                    f.setAttribute('id', nPoints)
                    f.setGeometry(geom)
                    sink.addFeature(f, QgsFeatureSink.FastInsert)
                    index.addFeature(f)
                    points[nPoints] = p
                    nPoints += 1
                    feedback.setProgress(int(nPoints * total))
            nIterations += 1

        if nPoints < pointCount:
            feedback.pushInfo(
                self.tr(
                    'Could not generate requested number of random points. '
                    'Maximum number of attempts exceeded.'))

        return {self.OUTPUT: dest_id}
示例#37
0
    def resampleLayer(self, inputLayer, fieldsToSample, weight_by,
                      inputIdField):
        ''' Disaggregates the polygon properties named in <fieldsToSample> from <inputLayer> at self.outputLayer features
        :param qgsVectorLayer with data to disaggregate:
        :param List of fields that should be downscaled spatially:
        :param Attribute of the OUTPUT shapefile by which to weight the resampled values that fall in the same input feature.
        *** value extracted from each inputLayer polygon will be multiplied by the fraction of that polygon intersected
        :param inputIdField: Field name of INPUT layer that contains unique identifiers for each entry
        :return: resampled layer
        '''

        # Make sure the fields to sample are actually present in the file. Throw exception if not
        extantFields = get_field_names(inputLayer)
        missing = list(set(fieldsToSample).difference(extantFields))
        if len(missing) > 0:
            raise ValueError(
                'The input shapefile %s is missing the following attributes:  %s'
                % (str(inputLayer.dataProvider().dataSourceUri()),
                   str(fieldsToSample)))

        # Create spatial index: assume input layer has more features than output
        inputIndex = QgsSpatialIndex()

        # Determine which input features intersect the bounding box of the output feature,
        # then do a real intersection.
        for feat in inputLayer.getFeatures():
            inputIndex.addFeature(feat)

        # If the inputLayer and outputLayer spatial units are the same, then disaggregation does not need to happen.
        if sameFeatures(inputLayer, self.outputLayer):
            return inputLayer

        # record what was used to label features
        #if self.logger is not None:
        #    self.logger.addEvent('Disagg', None, None, None, 'Resampling fields ' + str(fieldsToSample) + ', weighting by ' + str(weight_by))

        # Clone the output layer (populate this with [dis]aggregated data)
        newShapeFile = duplicateVectorLayer(self.outputLayer,
                                            targetEPSG=self.templateEpsgCode)
        # Keep track of which field name has which field index

        fieldIndices = {}
        newShapeFile.startEditing()
        existingFields = get_field_names(self.outputLayer)
        numFields = len(newShapeFile.dataProvider().fields())
        numFieldsAdded = 0

        for field in fieldsToSample:
            if field in existingFields:
                fieldIndices[field] = existingFields.index(field)
            else:
                newShapeFile.addAttribute(QgsField(field, QVariant.Double))
                newShapeFile.updateFields()
                fieldIndices[field] = numFields + numFieldsAdded
                numFieldsAdded += 1

        newShapeFile.commitChanges()
        newShapeFile.updateExtents()

        # Get read-across between so feature ID can be ascertained from name according to chosen ID field
        t = shapefile_attributes(newShapeFile)[self.templateIdField]

        def intorstring(x):
            try:
                return int(x)
            except:
                return str(x)

        readAcross = pd.Series(index=list(map(intorstring, t.values)),
                               data=list(map(intorstring, t.index)))
        t = None

        # Get areas of input shapefile intersected by output shapefile, and proportions covered, and attribute vals
        intersectedAreas = intersecting_amounts(fieldsToSample, inputIndex,
                                                inputLayer, newShapeFile,
                                                inputIdField,
                                                self.templateIdField)

        # Work out disaggregation factor baed on area intersected
        # Use "big" totals of weightings if the same attribute present in the input data file
        total_weightings = {}  # Assume no "big" totals are available
        #if type(weight_by) not in [str, unicode]:
        #    raise ValueError('Weighting attribute name not a string or unicode variable')

        if weight_by in get_field_names(inputLayer):
            atts = shapefile_attributes(inputLayer)[weight_by]
            total_weightings = {
                weight_by: {idx: atts[idx]
                            for idx in atts.index}
            }
            self.logger.addEvent(
                'Disagg', None, None, None, 'Found attribute ' +
                str(weight_by) + ' in shapefile to be disaggregated. '
                'Assuming this is the sum of ' + str(weight_by) +
                ' in the output features')
        else:
            # It's not in the input file: Record what happened in the log
            if weight_by is not None:
                self.logger.addEvent(
                    'Disagg', None, None, None,
                    'Total of Weighting Attribute ' + str(weight_by) +
                    ' not found in original shapefile, so calculating it from the output areas'
                )
            else:
                self.logger.addEvent(
                    'Disagg', None, None, None,
                    'No weighting attribute specified so disaggregated weighting by intersected feature area only'
                )

        if weight_by is None:
            disagg = disaggregate_weightings(intersectedAreas, newShapeFile,
                                             weight_by, total_weightings,
                                             self.templateIdField)['_AREA_']
        else:
            disagg = disaggregate_weightings(intersectedAreas, newShapeFile,
                                             weight_by, total_weightings,
                                             self.templateIdField)[weight_by]

        # Select successfully identified output areas

        newShapeFile.selectByIds(list(readAcross[list(disagg.keys())]))

        selectedOutputFeatures = newShapeFile.selectedFeatures()
        newShapeFile.startEditing()
        # Apply disaggregation to features
        for outputFeat in selectedOutputFeatures:  # For each output feature
            # Select the relevant features from the input layer
            area_weightings = {
                inputAreaId:
                disagg[outputFeat[self.templateIdField]][inputAreaId]
                for inputAreaId in list(disagg[outputFeat[
                    self.templateIdField]].keys())
            }
            # Calculate area-weighted average to get a single value for each output area
            for field in fieldsToSample:
                # The values to disaggregate in all regions touching this output feature
                input_values = {
                    inputAreaId: intersectedAreas[outputFeat[
                        self.templateIdField]][inputAreaId][field]
                    for inputAreaId in list(intersectedAreas[outputFeat[
                        self.templateIdField]].keys())
                }
                # If an output area is influenced by multiple input areas, and a subset of these is invalid,
                # assign them zero
                for i in list(input_values.keys()):
                    try:
                        input_values[i] = float(input_values[i])
                    except:
                        input_values[i] = 0
                # Combine values in all input regions touching this output feature. If disagg_weightings missed one out it's because no intersection or NULL data.
                # Any value intersecting an output area with NULL weighting will be excluded
                outputAreasToUse = set(input_values.keys()).intersection(
                    list(area_weightings.keys()))
                weighted_average = np.sum(
                    np.array([
                        input_values[in_id] * float(area_weightings[in_id])
                        for in_id in list(outputAreasToUse)
                    ]))

                newShapeFile.changeAttributeValue(outputFeat.id(),
                                                  fieldIndices[field],
                                                  float(weighted_average))

        newShapeFile.commitChanges()
        newShapeFile.selectByIds([])  # De-select all features
        return newShapeFile