Exemple #1
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.setFeatureId(fid)
                ft.setGeometry(QgsGeometry.fromPoint(QgsPoint(x, y)))
                idx.insertFeature(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(QgsPoint(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
Exemple #2
0
    def processAlgorithm(self, parameters, context, feedback):
        if parameters[self.INPUT] == parameters[self.HUBS]:
            raise QgsProcessingException(
                self.tr('Same layer given for both hubs and spokes'))

        point_source = self.parameterAsSource(parameters, self.INPUT, context)
        hub_source = self.parameterAsSource(parameters, self.HUBS, context)
        fieldName = self.parameterAsString(parameters, self.FIELD, context)

        units = self.UNITS[self.parameterAsEnum(parameters, self.UNIT, context)]

        fields = point_source.fields()
        fields.append(QgsField('HubName', QVariant.String))
        fields.append(QgsField('HubDist', QVariant.Double))

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.Point, point_source.sourceCrs())

        index = QgsSpatialIndex(hub_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(point_source.sourceCrs(), context.transformContext())))

        distance = QgsDistanceArea()
        distance.setSourceCrs(point_source.sourceCrs(), context.transformContext())
        distance.setEllipsoid(context.project().ellipsoid())

        # Scan source points, find nearest hub, and write to output file
        features = point_source.getFeatures()
        total = 100.0 / point_source.featureCount() if point_source.featureCount() else 0
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            if not f.hasGeometry():
                sink.addFeature(f, QgsFeatureSink.FastInsert)
                continue

            src = f.geometry().boundingBox().center()

            neighbors = index.nearestNeighbor(src, 1)
            ft = next(hub_source.getFeatures(QgsFeatureRequest().setFilterFid(neighbors[0]).setSubsetOfAttributes([fieldName], hub_source.fields()).setDestinationCrs(point_source.sourceCrs(), context.transformContext())))
            closest = ft.geometry().boundingBox().center()
            hubDist = distance.measureLine(src, closest)

            if units != self.LAYER_UNITS:
                hub_dist_in_desired_units = distance.convertLengthMeasurement(hubDist, units)
            else:
                hub_dist_in_desired_units = hubDist

            attributes = f.attributes()
            attributes.append(ft[fieldName])
            attributes.append(hub_dist_in_desired_units)

            feat = QgsFeature()
            feat.setAttributes(attributes)

            feat.setGeometry(QgsGeometry.fromPointXY(src))

            sink.addFeature(feat, QgsFeatureSink.FastInsert)
            feedback.setProgress(int(current * total))

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

        output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context)

        spatialIndex = QgsSpatialIndex(source, feedback)

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

        sumDist = 0.00
        A = source.sourceExtent()
        A = float(A.width() * A.height())

        features = source.getFeatures()
        count = source.featureCount()
        total = 100.0 / count if count else 1
        for current, feat in enumerate(features):
            if feedback.isCanceled():
                break

            neighbourID = spatialIndex.nearestNeighbor(
                feat.geometry().asPoint(), 2)[1]
            request = QgsFeatureRequest().setFilterFid(neighbourID).setSubsetOfAttributes([])
            neighbour = next(source.getFeatures(request))
            sumDist += distance.measureLine(neighbour.geometry().asPoint(),
                                            feat.geometry().asPoint())

            feedback.setProgress(int(current * total))

        do = float(sumDist) / count
        de = float(0.5 / math.sqrt(count / A))
        d = float(do / de)
        SE = float(0.26136 / math.sqrt(count ** 2 / A))
        zscore = float((do - de) / SE)

        results = {}
        results[self.OBSERVED_MD] = do
        results[self.EXPECTED_MD] = de
        results[self.NN_INDEX] = d
        results[self.POINT_COUNT] = count
        results[self.Z_SCORE] = zscore

        if output_file:
            data = []
            data.append('Observed mean distance: ' + str(do))
            data.append('Expected mean distance: ' + str(de))
            data.append('Nearest neighbour index: ' + str(d))
            data.append('Number of points: ' + str(count))
            data.append('Z-Score: ' + str(zscore))
            self.createHTML(output_file, data)
            results[self.OUTPUT_HTML_FILE] = output_file

        return results
Exemple #4
0
    def regularMatrix(self, parameters, context, source, inField, target_source, targetField,
                      nPoints, feedback):

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

        inIdx = source.fields().lookupField(inField)
        targetIdx = target_source.fields().lookupField(targetField)

        index = QgsSpatialIndex(target_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(source.sourceCrs(), context.transformContext())), feedback)

        first = True
        sink = None
        dest_id = None
        features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([inIdx]))
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        for current, inFeat in enumerate(features):
            if feedback.isCanceled():
                break

            inGeom = inFeat.geometry()
            if first:
                featList = index.nearestNeighbor(inGeom.asPoint(), nPoints)
                first = False
                fields = QgsFields()
                input_id_field = source.fields()[inIdx]
                input_id_field.setName('ID')
                fields.append(input_id_field)
                for f in target_source.getFeatures(QgsFeatureRequest().setFilterFids(featList).setSubsetOfAttributes([targetIdx]).setDestinationCrs(source.sourceCrs(), context.transformContext())):
                    fields.append(QgsField(str(f[targetField]), QVariant.Double))

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

            data = [inFeat[inField]]
            for target in target_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setFilterFids(featList).setDestinationCrs(source.sourceCrs(), context.transformContext())):
                if feedback.isCanceled():
                    break
                outGeom = target.geometry()
                dist = distArea.measureLine(inGeom.asPoint(),
                                            outGeom.asPoint())
                data.append(dist)

            out_feature = QgsFeature()
            out_feature.setGeometry(inGeom)
            out_feature.setAttributes(data)
            sink.addFeature(out_feature, QgsFeatureSink.FastInsert)
            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
Exemple #5
0
    def processAlgorithm(self, parameters, context, feedback):
        feedback.pushInfo(
            self.tr(
                "[QNEAT3Algorithm] This is a QNEAT3 Algorithm: '{}'".format(
                    self.displayName())))
        network = self.parameterAsVectorLayer(parameters, self.INPUT,
                                              context)  #QgsVectorLayer
        startPoint = self.parameterAsPoint(parameters,
                                           self.START_POINT, context,
                                           network.sourceCrs())  #QgsPointXY
        max_dist = self.parameterAsDouble(parameters, self.MAX_DIST,
                                          context)  #float
        cell_size = self.parameterAsInt(parameters, self.CELL_SIZE,
                                        context)  #int
        strategy = self.parameterAsEnum(parameters, self.STRATEGY,
                                        context)  #int
        interpolation_method = self.parameterAsEnum(parameters, self.METHOD,
                                                    context)  #int

        directionFieldName = self.parameterAsString(
            parameters, self.DIRECTION_FIELD,
            context)  #str (empty if no field given)
        forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD,
                                              context)  #str
        backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD,
                                               context)  #str
        bothValue = self.parameterAsString(parameters, self.VALUE_BOTH,
                                           context)  #str
        defaultDirection = self.parameterAsEnum(parameters,
                                                self.DEFAULT_DIRECTION,
                                                context)  #int
        speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD,
                                                context)  #str
        defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED,
                                              context)  #float
        tolerance = self.parameterAsDouble(parameters, self.TOLERANCE,
                                           context)  #float
        output_path = self.parameterAsOutputLayer(parameters, self.OUTPUT,
                                                  context)

        analysisCrs = network.sourceCrs()
        input_coordinates = [startPoint]
        input_point = getFeatureFromPointParameter(startPoint)

        if analysisCrs.isGeographic():
            raise QgsProcessingException(
                'QNEAT3 algorithms are designed to work with projected coordinate systems. Please use a projected coordinate system (eg. UTM zones) instead of geographic coordinate systems (eg. WGS84)!'
            )

        if analysisCrs != startPoint.sourceCrs():
            raise QgsProcessingException(
                'QNEAT3 algorithms require that all inputs to be the same projected coordinate reference system (including project coordinate system).'
            )

        feedback.pushInfo("[QNEAT3Algorithm] Building Graph...")
        feedback.setProgress(10)
        net = Qneat3Network(network, input_coordinates, strategy,
                            directionFieldName, forwardValue, backwardValue,
                            bothValue, defaultDirection, analysisCrs,
                            speedFieldName, defaultSpeed, tolerance, feedback)
        feedback.setProgress(40)

        analysis_point = Qneat3AnalysisPoint("point", input_point, "point_id",
                                             net, net.list_tiedPoints[0],
                                             feedback)

        feedback.pushInfo("[QNEAT3Algorithm] Calculating Iso-Pointcloud...")
        iso_pointcloud = net.calcIsoPoints([analysis_point], max_dist)
        feedback.setProgress(70)

        uri = "Point?crs={}&field=vertex_id:int(254)&field=cost:double(254,7)&field=origin_point_id:string(254)&index=yes".format(
            analysisCrs.authid())

        iso_pointcloud_layer = QgsVectorLayer(uri, "iso_pointcloud_layer",
                                              "memory")
        iso_pointcloud_provider = iso_pointcloud_layer.dataProvider()
        iso_pointcloud_provider.addFeatures(iso_pointcloud,
                                            QgsFeatureSink.FastInsert)

        feedback.pushInfo(
            "[QNEAT3Algorithm] Calculating Iso-Interpolation-Raster using QGIS TIN-Interpolator..."
        )
        if interpolation_method == 0:
            feedback.pushInfo(
                "[QNEAT3Algorithm] Calculating Iso-Interpolation-Raster using QGIS TIN-Interpolator..."
            )
            net.calcIsoTinInterpolation(iso_pointcloud_layer, cell_size,
                                        output_path)
            feedback.setProgress(99)
        else:

            #prepare numpy coordinate grids
            NoData_value = -9999
            raster_rectangle = iso_pointcloud_layer.extent()

            #implement spatial index for lines (closest line, etc...)
            spt_idx = QgsSpatialIndex(
                iso_pointcloud_layer.getFeatures(QgsFeatureRequest()),
                feedback)

            #top left point
            xmin = raster_rectangle.xMinimum()
            ymin = raster_rectangle.yMinimum()
            xmax = raster_rectangle.xMaximum()
            ymax = raster_rectangle.yMaximum()

            cols = int((xmax - xmin) / cell_size)
            rows = int((ymax - ymin) / cell_size)

            output_interpolation_raster = gdal.GetDriverByName('GTiff').Create(
                output_path, cols, rows, 1, gdal.GDT_Float64)
            output_interpolation_raster.SetGeoTransform(
                (xmin, cell_size, 0, ymax, 0, -cell_size))

            band = output_interpolation_raster.GetRasterBand(1)
            band.SetNoDataValue(NoData_value)

            #initialize zero array with 2 dimensions (according to rows and cols)
            raster_routingcost_data = zeros(shape=(rows, cols))

            #compute raster cell MIDpoints
            x_pos = linspace(xmin + (cell_size / 2), xmax - (cell_size / 2),
                             raster_routingcost_data.shape[1])
            y_pos = linspace(ymax - (cell_size / 2), ymin + (cell_size / 2),
                             raster_routingcost_data.shape[0])
            x_grid, y_grid = meshgrid(x_pos, y_pos)

            feedback.pushInfo(
                '[QNEAT3Network][calcQneatInterpolation] Beginning with interpolation'
            )
            total_work = rows * cols
            counter = 0

            feedback.pushInfo(
                '[QNEAT3Network][calcQneatInterpolation] Total workload: {} cells'
                .format(total_work))
            feedback.setProgress(0)
            for i in range(rows):
                for j in range(cols):
                    current_pixel_midpoint = QgsPointXY(
                        x_grid[i, j], y_grid[i, j])

                    nearest_vertex_fid = spt_idx.nearestNeighbor(
                        current_pixel_midpoint, 1)[0]

                    nearest_feature = iso_pointcloud_layer.getFeature(
                        nearest_vertex_fid)

                    nearest_vertex = net.network.vertex(
                        nearest_feature['vertex_id'])

                    #yields a list of all incoming and outgoing edges
                    edges = nearest_vertex.incomingEdges(
                    ) + nearest_vertex.outgoingEdges()

                    vertex_found = False
                    nearest_counter = 2
                    while vertex_found == False:
                        #find the second nearest vertex (eg, the vertex with least cost of all edges incoming to the first nearest vertex)
                        second_nearest_feature_fid = spt_idx.nearestNeighbor(
                            current_pixel_midpoint,
                            nearest_counter)[nearest_counter - 1]
                        second_nearest_feature = iso_pointcloud_layer.getFeature(
                            second_nearest_feature_fid)
                        second_nearest_vertex_id = second_nearest_feature[
                            'vertex_id']

                        for edge_id in edges:
                            from_vertex_id = net.network.edge(
                                edge_id).fromVertex()
                            to_vertex_id = net.network.edge(edge_id).toVertex()

                            if second_nearest_vertex_id == from_vertex_id:
                                vertex_found = True
                                vertex_type = "from_vertex"
                                from_point = second_nearest_feature.geometry(
                                ).asPoint()
                                from_vertex_cost = second_nearest_feature[
                                    'cost']

                            if second_nearest_vertex_id == to_vertex_id:
                                vertex_found = True
                                vertex_type = "to_vertex"
                                to_point = second_nearest_feature.geometry(
                                ).asPoint()
                                to_vertex_cost = second_nearest_feature['cost']

                        nearest_counter = nearest_counter + 1
                        """
                        if nearest_counter == 5:
                            vertex_found = True
                            vertex_type = "end_vertex"
                        """

                    if vertex_type == "from_vertex":
                        nearest_edge_geometry = QgsGeometry().fromPolylineXY(
                            [from_point, nearest_vertex.point()])
                        res = nearest_edge_geometry.closestSegmentWithContext(
                            current_pixel_midpoint)
                        segment_point = res[
                            1]  #[0: distance, 1: point, 2: left_of, 3: epsilon for snapping]
                        dist_to_segment = segment_point.distance(
                            current_pixel_midpoint)
                        dist_edge = from_point.distance(segment_point)
                        #feedback.pushInfo("dist_to_segment = {}".format(dist_to_segment))
                        #feedback.pushInfo("dist_on_edge = {}".format(dist_edge))
                        #feedback.pushInfo("cost = {}".format(from_vertex_cost))
                        pixel_cost = from_vertex_cost + dist_edge + dist_to_segment
                        raster_routingcost_data[i, j] = pixel_cost
                    elif vertex_type == "to_vertex":
                        nearest_edge_geometry = QgsGeometry().fromPolylineXY(
                            [nearest_vertex.point(), to_point])
                        res = nearest_edge_geometry.closestSegmentWithContext(
                            current_pixel_midpoint)
                        segment_point = res[
                            1]  #[0: distance, 1: point, 2: left_of, 3: epsilon for snapping]
                        dist_to_segment = segment_point.distance(
                            current_pixel_midpoint)
                        dist_edge = to_point.distance(segment_point)
                        #feedback.pushInfo("dist_to_segment = {}".format(dist_to_segment))
                        #feedback.pushInfo("dist_on_edge = {}".format(dist_edge))
                        #feedback.pushInfo("cost = {}".format(from_vertex_cost))
                        pixel_cost = to_vertex_cost + dist_edge + dist_to_segment
                        raster_routingcost_data[i, j] = pixel_cost
                    else:
                        pixel_cost = -99999  #nearest_feature['cost'] + (nearest_vertex.point().distance(current_pixel_midpoint))
                    """
                    nearest_feature_pointxy = nearest_feature.geometry().asPoint()
                    nearest_feature_cost = nearest_feature['cost']

                    dist_to_vertex = current_pixel_midpoint.distance(nearest_feature_pointxy)
                    #implement time cost
                    pixel_cost = dist_to_vertex + nearest_feature_cost

                    raster_data[i,j] = pixel_cost
                    """
                    counter = counter + 1
                    if counter % 1000 == 0:
                        feedback.pushInfo(
                            "[QNEAT3Network][calcQneatInterpolation] Interpolated {} cells..."
                            .format(counter))
                    feedback.setProgress((counter / total_work) * 100)

            band.WriteArray(raster_routingcost_data)
            outRasterSRS = osr.SpatialReference()
            outRasterSRS.ImportFromWkt(net.AnalysisCrs.toWkt())
            output_interpolation_raster.SetProjection(
                outRasterSRS.ExportToWkt())
            band.FlushCache()

        feedback.pushInfo("[QNEAT3Algorithm] Ending Algorithm")
        feedback.setProgress(100)

        results = {}
        results[self.OUTPUT] = output_path
        return results
Exemple #6
0
    def linearMatrix(self, parameters, context, source, inField, target_source, targetField, same_source_and_target,
                     matType, nPoints, feedback):

        if same_source_and_target:
            # need to fetch an extra point from the index, since the closest match will always be the same
            # as the input feature
            nPoints += 1

        inIdx = source.fields().lookupField(inField)
        outIdx = target_source.fields().lookupField(targetField)

        fields = QgsFields()
        input_id_field = source.fields()[inIdx]
        input_id_field.setName('InputID')
        fields.append(input_id_field)
        if matType == 0:
            target_id_field = target_source.fields()[outIdx]
            target_id_field.setName('TargetID')
            fields.append(target_id_field)
            fields.append(QgsField('Distance', QVariant.Double))
        else:
            fields.append(QgsField('MEAN', QVariant.Double))
            fields.append(QgsField('STDDEV', QVariant.Double))
            fields.append(QgsField('MIN', QVariant.Double))
            fields.append(QgsField('MAX', QVariant.Double))

        out_wkb = QgsWkbTypes.multiType(source.wkbType()) if matType == 0 else source.wkbType()
        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, out_wkb, source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

        index = QgsSpatialIndex(target_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(source.sourceCrs(), context.transformContext())), feedback)

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

        features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([inIdx]))
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        for current, inFeat in enumerate(features):
            if feedback.isCanceled():
                break

            inGeom = inFeat.geometry()
            inID = str(inFeat.attributes()[inIdx])
            featList = index.nearestNeighbor(inGeom.asPoint(), nPoints)
            distList = []
            vari = 0.0
            request = QgsFeatureRequest().setFilterFids(featList).setSubsetOfAttributes([outIdx]).setDestinationCrs(source.sourceCrs(), context.transformContext())
            for outFeat in target_source.getFeatures(request):
                if feedback.isCanceled():
                    break

                if same_source_and_target and inFeat.id() == outFeat.id():
                    continue

                outID = outFeat.attributes()[outIdx]
                outGeom = outFeat.geometry()
                dist = distArea.measureLine(inGeom.asPoint(),
                                            outGeom.asPoint())

                if matType == 0:
                    out_feature = QgsFeature()
                    out_geom = QgsGeometry.unaryUnion([inFeat.geometry(), outFeat.geometry()])
                    out_feature.setGeometry(out_geom)
                    out_feature.setAttributes([inID, outID, dist])
                    sink.addFeature(out_feature, QgsFeatureSink.FastInsert)
                else:
                    distList.append(float(dist))

            if matType != 0:
                mean = sum(distList) / len(distList)
                for i in distList:
                    vari += (i - mean) * (i - mean)
                vari = math.sqrt(vari / len(distList))

                out_feature = QgsFeature()
                out_feature.setGeometry(inFeat.geometry())
                out_feature.setAttributes([inID, mean, vari, min(distList), max(distList)])
                sink.addFeature(out_feature, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
    def processAlgorithm(self, parameters, context, feedback):

        layer = self.parameterAsVectorLayer(parameters, self.Centerline,
                                            context)
        layer2 = self.parameterAsVectorLayer(parameters, self.Direction,
                                             context)

        context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck)

        index = QgsSpatialIndex(layer2.getFeatures())

        fet = QgsFeature()
        fields = QgsFields()
        fields.append(QgsField("ID", QVariant.Int))
        field_names = ['Distance', 'RDistance', 'SP_Dist', 'SP_RDist']

        for name in field_names:
            fields.append(QgsField(name, QVariant.Double))

        AD = False
        if layer2.fields().indexFromName('Distance') != -1:
            AD = True
            fields.append(QgsField('AlongDist', QVariant.Double))

        (writer, dest_id) = self.parameterAsSink(parameters, self.Output,
                                                 context, fields,
                                                 QgsWkbTypes.LineString,
                                                 layer.sourceCrs())

        Precision = 5
        SPS = {}
        SPE = {}
        values = {}
        values2 = {}
        data = {feature.id(): feature for feature in layer2.getFeatures()}
        total = 0
        feedback.pushInfo(
            QCoreApplication.translate('Update', 'Defining Centerline Paths'))
        for enum, feature in enumerate(layer.getFeatures()):
            total += 1
            try:
                pnt = feature.geometry()
                if pnt.isMultipart():
                    pnt = pnt.asMultiPolyline()[0]
                else:
                    pnt = pnt.asPolyline()

                startx, starty = round(pnt[0][0],
                                       Precision), round(pnt[0][1], Precision)
                endx, endy = round(pnt[-1][0],
                                   Precision), round(pnt[-1][1], Precision)
                ID = feature['ID']
                c = feature['Distance']
                if ID in SPS:  #Get start and endpoint of each centerline
                    v = values[ID]
                    v2 = values2[ID]

                    if c > v:
                        SPS[ID] = [(startx, starty), (endx, endy)]
                        values[ID] = c
                    if c < v2:
                        SPE[ID] = [(startx, starty), (endx, endy)]
                        values2[ID] = c

                else:
                    SPS[ID] = [(startx, starty), (endx, endy)]
                    values[ID] = c
                    SPE[ID] = [(startx, starty), (endx, endy)]
                    values2[ID] = c

            except Exception as e:
                #feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))
                continue  ##Possible Collapsed Polyline?

        del values, values2
        feedback.pushInfo(
            QCoreApplication.translate('Update',
                                       'Correcting Centerline Direction'))
        total = 100.0 / float(total)
        ID = None

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

            if total != -1:
                feedback.setProgress(int(enum * total))
            try:
                try:
                    geom = feature.geometry().asPolyline()
                except Exception:
                    geom = feature.geometry().asMultiPolyline()[0]
                start, end = geom[0], geom[-1]
                startx, starty = start
                endx, endy = end
                curID = feature['ID']
                if curID != ID:
                    ID = curID
                    reverse = False

                    SP = SPS[curID]
                    EP = SPE[curID]

                    startx, starty = round(SP[0][0], Precision), round(
                        SP[0][1], Precision)
                    v = index.nearestNeighbor(QgsPointXY(startx, starty), 1)

                    midx, midy = data[v[0]].geometry().centroid().asPoint()

                    dx, dy = startx - midx, starty - midy
                    if AD:
                        shortestPath = data[v[0]]['Distance'] + sqrt((dx**2) +
                                                                     (dy**2))
                        startx, starty = round(SP[1][0], Precision), round(
                            SP[1][1], Precision)
                        v = index.nearestNeighbor(QgsPointXY(startx, starty),
                                                  1)
                        dx, dy = startx - midx, starty - midy
                        SPd = data[v[0]]['Distance'] + sqrt((dx**2) + (dy**2))
                        if SPd < shortestPath:
                            shortestPath = SPd
                    else:
                        shortestPath = sqrt((dx**2) + (dy**2))

                    startx, starty = round(EP[0][0], Precision), round(
                        EP[0][1], Precision)

                    v = index.nearestNeighbor(QgsPointXY(startx, starty), 1)

                    midx, midy = data[v[0]].geometry().centroid().asPoint()
                    dx, dy = startx - midx, starty - midy

                    if AD:
                        shortestPath2 = data[v[0]]['Distance'] + sqrt((dx**2) +
                                                                      (dy**2))
                        startx, starty = round(SP[1][0], Precision), round(
                            SP[1][1], Precision)
                        v = index.nearestNeighbor(QgsPointXY(startx, starty),
                                                  1)
                        dx, dy = startx - midx, starty - midy
                        SPd = data[v[0]]['Distance'] + sqrt((dx**2) + (dy**2))
                        if SPd < shortestPath2:
                            shortestPath2 = SPd
                    else:
                        shortestPath2 = sqrt((dx**2) + (dy**2))

                    if shortestPath2 > shortestPath:
                        reverse = True
                        dist = shortestPath
                    else:
                        dist = shortestPath2

                D = feature['Distance']
                D2 = feature['RDistance']
                SP = feature['SP_Dist']
                SP2 = feature['SP_RDist']
                if reverse:
                    rows = [curID, D2, D, SP2, SP]

                else:
                    rows = [curID, D, D2, SP, SP2]
                    D2 = D
                if AD:
                    rows.append(float(dist) + D2)

                fet.setGeometry(feature.geometry())
                fet.setAttributes(rows)
                writer.addFeature(fet)

            except Exception as e:
                feedback.pushInfo(
                    QCoreApplication.translate('Update', '%s' % (e)))
                continue

        return {self.Output: dest_id}
Exemple #8
0
    def processAlgorithm(self, parameters, context, feedback):
        if parameters[self.INPUT] == parameters[self.HUBS]:
            raise QgsProcessingException(
                self.tr('Same layer given for both hubs and spokes'))

        point_source = self.parameterAsSource(parameters, self.INPUT, context)
        if point_source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        hub_source = self.parameterAsSource(parameters, self.HUBS, context)
        if hub_source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.HUBS))

        fieldName = self.parameterAsString(parameters, self.FIELD, context)

        units = self.UNITS[self.parameterAsEnum(parameters, self.UNIT,
                                                context)]

        fields = point_source.fields()
        fields.append(QgsField('HubName', QVariant.String))
        fields.append(QgsField('HubDist', QVariant.Double))

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

        index = QgsSpatialIndex(
            hub_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(
                []).setDestinationCrs(point_source.sourceCrs(),
                                      context.transformContext())))

        distance = QgsDistanceArea()
        distance.setSourceCrs(point_source.sourceCrs(),
                              context.transformContext())
        distance.setEllipsoid(context.project().ellipsoid())

        # Scan source points, find nearest hub, and write to output file
        features = point_source.getFeatures()
        total = 100.0 / point_source.featureCount(
        ) if point_source.featureCount() else 0
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            if not f.hasGeometry():
                sink.addFeature(f, QgsFeatureSink.FastInsert)
                continue

            src = f.geometry().boundingBox().center()

            neighbors = index.nearestNeighbor(src, 1)
            ft = next(
                hub_source.getFeatures(QgsFeatureRequest().setFilterFid(
                    neighbors[0]).setSubsetOfAttributes(
                        [fieldName], hub_source.fields()).setDestinationCrs(
                            point_source.sourceCrs(),
                            context.transformContext())))
            closest = ft.geometry().boundingBox().center()
            hubDist = distance.measureLine(src, closest)

            if units != self.LAYER_UNITS:
                hub_dist_in_desired_units = distance.convertLengthMeasurement(
                    hubDist, units)
            else:
                hub_dist_in_desired_units = hubDist

            attributes = f.attributes()
            attributes.append(ft[fieldName])
            attributes.append(hub_dist_in_desired_units)

            feat = QgsFeature()
            feat.setAttributes(attributes)

            feat.setGeometry(QgsGeometry.fromPointXY(src))

            sink.addFeature(feat, QgsFeatureSink.FastInsert)
            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
class PolygonsParallelToLineAlgorithm(GeoAlgorithm):

    OUTPUT_LAYER = 'OUTPUT_LAYER'
    LINE_LAYER = 'LINE_LAYER'
    POLYGON_LAYER = 'POLYGON_LAYER'
    SELECTED = 'SELECTED'
    WRITE_SELECTED = 'WRITE_SELECTED'
    LONGEST = 'LONGEST'
    MULTI = 'MULTI'
    DISTANCE = 'DISTANCE'
    ANGLE = 'ANGLE'
    COLUMN_NAME = '_rotated'

    def __init__(self):
        self._translateUi()
        GeoAlgorithm.__init__(self)

    def _translateUi(self):
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            os.path.dirname(__file__),
            'i18n',
            'pptl_{}.qm'.format(locale)
        )
        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

    def getIcon(self):
        path = os.path.join(os.path.dirname(__file__), "icons", "icon.png")
        return QIcon(path)

    def tr(self, message):
        className = self.__class__.__name__
        return QCoreApplication.translate(className, message)

    def defineCharacteristics(self):
        # The name that the user will see in the toolbox
        self.name = self.tr('Polygons parallel to line')

        # The branch of the toolbox under which the algorithm will appear
        self.group = self.tr('Algorithms for vector layers')

        self.addOutput(
            OutputVector(
                self.OUTPUT_LAYER,
                'Output layer with rotated polygons'
            )
        )
        self.addParameter(
            ParameterVector(
                self.LINE_LAYER,
                self.tr('Select line layer'),
                [ParameterVector.VECTOR_TYPE_LINE]
            )
        )
        self.addParameter(
            ParameterVector(
                self.POLYGON_LAYER,
                self.tr('Select polygon layer'),
                [ParameterVector.VECTOR_TYPE_POLYGON]
            )
        )
        self.addParameter(
            ParameterBoolean(
                self.SELECTED,
                self.tr('Rotate only selected polygons')
            )
        )
        self.addParameter(
            ParameterBoolean(
                self.WRITE_SELECTED,
                self.tr('Save only selected'),
            )
        )
        self.addParameter(
            ParameterBoolean(
                self.LONGEST,
                self.tr("Rotate by longest edge if both angles between "
                        "polygon edges and line segment <= 'Angle value'")
            )
        )
        self.addParameter(
            ParameterBoolean(
                self.MULTI,
                self.tr("Do not rotate multipolygons")
            )
        )
        self.addParameter(
            ParameterNumber(
                self.DISTANCE,
                self.tr("Distance from line")
            )
        )
        self.addParameter(
            ParameterNumber(
                self.ANGLE,
                self.tr("Angle value"),
                maxValue=89.9
            )
        )

    def processAlgorithm(self, progress):
        self._operationCounter = 0
        self._progress = progress
        self._getInputValues()
        self._lineLayer = dataobjects.getObjectFromUri(self._lineLayerName)
        self._polygonLayer = dataobjects.getObjectFromUri(
            self._polygonLayerName
        )
        self._createLineSpatialIndex()
        self._validatePolygonLayer()
        self._addAttribute()
        self._linesDict = {x.id(): x for x in self._lineLayer.getFeatures()}
        self._rotateAndWriteSelectedOrAll(self._getWriter())
        self._deleteAttribute()

    def _getInputValues(self):
        self._lineLayerName = self.getParameterValue(self.LINE_LAYER)
        self._polygonLayerName = self.getParameterValue(self.POLYGON_LAYER)
        self._isSelected = self.getParameterValue(self.SELECTED)
        self._isWriteSelected = self.getParameterValue(self.WRITE_SELECTED)
        self._byLongest = self.getParameterValue(self.LONGEST)
        self._multi = self.getParameterValue(self.MULTI)
        self._distance = self.getParameterValue(self.DISTANCE)
        self._angle = self.getParameterValue(self.ANGLE)
        self._outputLayer = self.getOutputValue(self.OUTPUT_LAYER)

    def _createLineSpatialIndex(self):
        self._index = QgsSpatialIndex()
        for line in self._lineLayer.getFeatures():
            self._index.insertFeature(line)

    def _validatePolygonLayer(self):
        self._totalNumber = self._polygonLayer.featureCount()
        if not self._totalNumber:
            raise GeoAlgorithmExecutionException(
                self.tr("Layer does not have any polygons")
            )
        if self._isWriteSelected and not self._isSelected:
            raise GeoAlgorithmExecutionException(
                self.tr('You have chosen "Save only selected" without '
                        '"Rotate only selected polygons"')
            )
        if self._isSelected:
            self._totalNumber = self._polygonLayer.selectedFeatureCount()
            if not self._totalNumber:
                raise GeoAlgorithmExecutionException(
                    self.tr('You have chosen "Rotate only selected polygons" '
                            'but there are no selected')
                )

    def _addAttribute(self):
        for attr in self._polygonLayer.pendingFields():
            if self.COLUMN_NAME == attr.name():
                if attr.isNumeric():
                    break
                else:
                    self._deleteAttribute()
        else:
            self._polygonLayer.dataProvider().addAttributes(
                [QgsField(self.COLUMN_NAME, QVariant.Int)]
            )
            self._polygonLayer.updateFields()

    def _deleteAttribute(self):
        for i, attr in enumerate(self._polygonLayer.pendingFields()):
            if attr.name() == self.COLUMN_NAME:
                self._polygonLayer.dataProvider().deleteAttributes([i])
                self._polygonLayer.updateFields()

    def _getWriter(self):
        settings = QSettings()
        systemEncoding = settings.value('/UI/encoding', 'System')
        provider = self._polygonLayer.dataProvider()
        return QgsVectorFileWriter(
            self._outputLayer, systemEncoding, provider.fields(),
            provider.geometryType(), provider.crs()
        )

    def _rotateAndWriteSelectedOrAll(self, writer):
        if self._isSelected:
            self._rotateAndWriteSeleced(writer)
        else:
            polygons = self._polygonLayer.getFeatures()
            for polygon in polygons:
                self._rotateAndWritePolygon(polygon, writer)

    def _rotateAndWriteSeleced(self, writer):
        if self._isWriteSelected:
            for polygon in self._polygonLayer.selectedFeatures():
                self._rotateAndWritePolygon(polygon, writer)
        else:
            selectedPolygonsIds = self._polygonLayer.selectedFeaturesIds()
            for p in self._polygonLayer.getFeatures():
                if p.id() in selectedPolygonsIds:
                    self._rotateAndWritePolygon(p, writer)
                else:
                    writer.addFeature(p)

    def _rotateAndWritePolygon(self, polygon, writer):
        self._progressBar()
        self._p = polygon
        self._initiateRotation()
        writer.addFeature(self._p)

    def _progressBar(self):
        self._operationCounter += 1
        currentPercentage = self._operationCounter / self._totalNumber * 100
        self._progress.setPercentage(round(currentPercentage))

    def _initiateRotation(self):
        self._getNearestLine()
        dist = self._nearLine.geometry().distance(self._p.geometry())
        if not self._distance or dist <= self._distance:
            self._simpleOrMultiGeometry()

    def _getNearestLine(self):
        self._center = self._p.geometry().centroid()
        nearId = self._index.nearestNeighbor(self._center.asPoint(), 1)
        self._nearLine = self._linesDict.get(nearId[0])

    def _simpleOrMultiGeometry(self):
        isMulti = self._p.geometry().isMultipart()
        if isMulti and not self._multi:
            dct = {}
            mPolygonVertexes = self._p.geometry().asMultiPolygon()
            for i, part in enumerate(mPolygonVertexes):
                minDistance, vertexIndex = self._getNearestVertex(part[0])
                dct[(i, vertexIndex)] = minDistance
            i, vertexIndex = min(dct, key=dct.get)
            self._nearestEdges(mPolygonVertexes[i][0], vertexIndex)
        elif not isMulti:
            polygonVertexes = self._p.geometry().asPolygon()[0][:-1]
            vertexIndex = self._getNearestVertex(polygonVertexes)[1]
            self._nearestEdges(polygonVertexes, vertexIndex)

    def _getNearestVertex(self, polygonVertexes):
        vertexToSegmentDict = {}
        for vertex in polygonVertexes:
            vertexGeom = QgsGeometry.fromPoint(vertex)
            vertexToSegment = vertexGeom.distance(self._nearLine.geometry())
            vertexToSegmentDict[vertexToSegment] = vertex

        minDistance = min(vertexToSegmentDict.keys())
        self._nearestVertex = vertexToSegmentDict[minDistance]
        vertexIndex = polygonVertexes.index(self._nearestVertex)
        return minDistance, vertexIndex

    def _nearestEdges(self, polygonVertexes, vertexIndex):
        # if vertex is first
        if vertexIndex == 0:
            self._line1 = QgsGeometry.fromPolyline(
                [polygonVertexes[0], polygonVertexes[1]])
            self._line2 = QgsGeometry.fromPolyline(
                [polygonVertexes[0], polygonVertexes[-1]])

        # if vertex is last
        elif vertexIndex == len(polygonVertexes) - 1:
            self._line1 = QgsGeometry.fromPolyline(
                [polygonVertexes[-1], polygonVertexes[0]])
            self._line2 = QgsGeometry.fromPolyline(
                [polygonVertexes[-1], polygonVertexes[-2]])
        else:
            self._line1 = QgsGeometry.fromPolyline(
                [polygonVertexes[vertexIndex],
                 polygonVertexes[vertexIndex + 1]]
            )
            self._line2 = QgsGeometry.fromPolyline(
                [polygonVertexes[vertexIndex],
                 polygonVertexes[vertexIndex - 1]]
            )

        line1Azimuth = self._line1.asPolyline()[0].azimuth(
            self._line1.asPolyline()[1]
        )
        line2Azimuth = self._line2.asPolyline()[0].azimuth(
            self._line2.asPolyline()[1]
        )
        self._segmentAzimuth(line1Azimuth, line2Azimuth)

    def _segmentAzimuth(self, line1Azimuth, line2Azimuth):
        nearLineGeom = self._nearLine.geometry()
        if nearLineGeom.isMultipart():
            dct = {}
            minDists = []

            for line in nearLineGeom.asMultiPolyline():
                l = QgsGeometry.fromPolyline(line)
                closestSegmContext = l.closestSegmentWithContext(
                    self._nearestVertex
                )
                minDists.append(closestSegmContext[0])
                dct[closestSegmContext[0]] = [line, closestSegmContext[-1]]

            minDistance = min(minDists)
            closestSegment = dct[minDistance][0]
            indexSegmEnd = dct[minDistance][1]
            segmEnd = closestSegment[indexSegmEnd]
            segmStart = closestSegment[indexSegmEnd - 1]
        else:
            closestSegmContext = nearLineGeom.closestSegmentWithContext(
                self._nearestVertex
            )
            indexSegmEnd = closestSegmContext[-1]
            segmEnd = nearLineGeom.asPolyline()[indexSegmEnd]
            segmStart = nearLineGeom.asPolyline()[indexSegmEnd - 1]

        segmentAzimuth = segmStart.azimuth(segmEnd)
        self._dltAz1 = self._getDeltaAzimuth(segmentAzimuth, line1Azimuth)
        self._dltAz2 = self._getDeltaAzimuth(segmentAzimuth, line2Azimuth)

        self._azimuth()

    def _getDeltaAzimuth(self, segment, line):
        if (segment >= 0 and line >= 0) or (segment <= 0 and line <= 0):
            delta = segment - line
            if segment > line and abs(delta) > 90:
                delta -= 180
            elif segment < line and abs(delta) > 90:
                delta += 180

        if 90 >= segment >= 0 and line <= 0:
            delta = segment + abs(line)
            if delta > 90:
                delta -= 180
        elif 90 < segment and line <= 0:
            delta = segment - line - 180
            if abs(delta) > 90:
                delta -= 180

        if -90 <= segment <= 0 and line >= 0:
            delta = segment - line
            if abs(delta) > 90:
                delta += 180
        elif -90 > segment and line >= 0:
            delta = segment - line + 180
            if abs(delta) > 90:
                delta += 180

        return delta

    def _azimuth(self):
        delta1 = abs(self._dltAz1)
        delta2 = abs(self._dltAz2)

        if abs(self._dltAz1) > 90:
            delta1 = 180 - delta1
        if abs(self._dltAz2) > 90:
            delta2 = 180 - delta2

        self._rotate(delta1, delta2)

    def _rotate(self, delta1, delta2):
        self._rotationCheck = True
        if delta1 <= self._angle and delta2 <= self._angle:
            if self._byLongest:
                self._rotateByLongest(delta1, delta2)
            else:
                self._rotateNotByLongest(delta1, delta2)
        else:
            self._othersRotations(delta1, delta2)

        self._markAsRotated()

    def _rotateByLongest(self, delta1, delta2):
        if delta1 <= self._angle and delta2 <= self._angle:
            length1 = self._line1.geometry().length()
            length2 = self._line2.geometry().length()

            if length1 >= length2:
                self._p.geometry().rotate(self._dltAz1, self._center.asPoint())
            elif length1 < length2:
                self._p.geometry().rotate(self._dltAz2, self._center.asPoint())
            elif length1 == length2:
                self._rotateNotByLongest(delta1, delta2)
            else:
                self._rotationCheck = False

    def _rotateNotByLongest(self, delta1, delta2):
        if delta1 > delta2:
            self._p.geometry().rotate(self._dltAz2, self._center.asPoint())
        elif delta1 <= delta2:
            self._p.geometry().rotate(self._dltAz1, self._center.asPoint())
        else:
            self._rotationCheck = False

    def _othersRotations(self, delta1, delta2):
        if delta1 <= self._angle:
            self._p.geometry().rotate(self._dltAz1, self._center.asPoint())
        elif delta2 <= self._angle:
            self._p.geometry().rotate(self._dltAz2, self._center.asPoint())
        else:
            self._rotationCheck = False

    def _markAsRotated(self):
        if self._rotationCheck:
            self._p[self.COLUMN_NAME] = 1
Exemple #10
0
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """

        # Retrieve the feature source and sink. The 'dest_id' variable is used
        # to uniquely identify the feature sink, and must be included in the
        # dictionary returned by the processAlgorithm function.
        arrets = self.parameterAsSource(parameters, self.STOPS, context)
        stop_id = self.parameterAsFields(parameters, self.STOP_ID, context)[0]
        noeuds = self.parameterAsSource(parameters, self.NOEUDS, context)
        node_id = self.parameterAsFields(parameters, self.NODE_ID, context)[0]
        mode_i = self.parameterAsString(parameters, self.MODE_ORI, context)
        mode_j = self.parameterAsString(parameters, self.MODE_DES, context)
        texte_i = self.parameterAsString(parameters, self.TEXTE_ORI, context)
        texte_j = self.parameterAsString(parameters, self.TEXTE_DES, context)
        rayon = self.parameterAsDouble(parameters, self.RAYON, context)
        vitesse = self.parameterAsDouble(parameters, self.VITESSE, context)
        nb_max = self.parameterAsInt(parameters, self.MAX_NB, context)

        # Compute the number of steps to display within the progress bar and
        # get features from source
        ##a=fenetre.split(",")
        ##fenetre2=QgsRectangle(float(a[0]),float(a[2]),float(a[1]),float(a[3]))
        arr = [a for a in arrets.getFeatures()]
        nb = len(arr)

        index = QgsSpatialIndex(noeuds.getFeatures())
        champs = QgsFields()
        champs.append(QgsField('i', QVariant.String, len=15))
        champs.append(QgsField('j', QVariant.String, len=15))
        champs.append(QgsField(self.tr('length'), QVariant.Double))
        champs.append(QgsField(self.tr('time'), QVariant.Double))
        champs.append(QgsField(self.tr('mode'), QVariant.String, len=10))

        (table_connecteurs,
         dest_id) = self.parameterAsSink(parameters, self.CONNECTEURS, context,
                                         champs, QgsWkbTypes.LineString,
                                         noeuds.sourceCrs())
        nom_fichier = dest_id
        fichier_connecteurs = os.path.splitext(nom_fichier)[0] + ".txt"
        sortie = codecs.open(fichier_connecteurs, "w", encoding="utf-8")
        nbc = 0
        for i, n in enumerate(arr):
            near = index.nearestNeighbor(n.geometry().centroid().asPoint(),
                                         nb_max)
            feedback.setProgress(i * 100 / nb)
            if len(near) > 0:
                for k, nearest in enumerate(near):
                    if k < nb_max:
                        f = noeuds.getFeatures(
                            request=QgsFeatureRequest(nearest))
                        for j, g in enumerate(f):
                            if j == 0:
                                l = n.geometry().distance(g.geometry())
                                id_node = unicode(g.attribute(node_id))
                                id_stop = unicode(n.attribute(stop_id))
                                if l < rayon:
                                    nbc += 1
                                    gline = QgsGeometry.fromPolylineXY([
                                        QgsPointXY(
                                            n.geometry().centroid().asPoint()),
                                        QgsPointXY(
                                            g.geometry().centroid().asPoint())
                                    ])
                                    hline = QgsGeometry.fromPolylineXY([
                                        QgsPointXY(
                                            g.geometry().centroid().asPoint()),
                                        QgsPointXY(
                                            n.geometry().centroid().asPoint())
                                    ])

                                    fline = QgsFeature()
                                    fline.setGeometry(gline)
                                    ll = gline.length()
                                    moda = unicode(mode_i) + unicode(mode_j)
                                    if vitesse <= 0:
                                        fline.setAttributes([
                                            id_stop, id_node, ll / 1000, 0.0,
                                            moda
                                        ])
                                    else:
                                        fline.setAttributes([
                                            id_stop, id_node, ll / 1000,
                                            ll * 60 / (vitesse * 1000), moda
                                        ])
                                    fline2 = QgsFeature()
                                    fline2.setGeometry(hline)
                                    modb = unicode(mode_j) + unicode(mode_i)
                                    if vitesse <= 0:
                                        fline2.setAttributes([
                                            id_node, id_stop, ll / 1000, 0,
                                            modb
                                        ])
                                    else:
                                        fline2.setAttributes([
                                            id_node, id_stop, ll / 1000,
                                            ll * 60 / (vitesse * 1000), modb
                                        ])
                                    table_connecteurs.addFeature(fline)
                                    table_connecteurs.addFeature(fline2)
                                    if vitesse > 0:
                                        sortie.write(id_node + ';' + id_stop +
                                                     ';' + str((60 / vitesse) *
                                                               (ll / 1000.0)) +
                                                     ';' + str(ll / 1000.0) +
                                                     ';-1;-1;-1;-1;-1;' +
                                                     modb + ';' + modb + '\n')
                                        sortie.write(id_stop + ';' + id_node +
                                                     ';' + str((60 / vitesse) *
                                                               (ll / 1000.0)) +
                                                     ';' + str(ll / 1000.0) +
                                                     ';-1;-1;-1;-1;-1;' +
                                                     moda + ';' + moda + '\n')
                                    else:
                                        sortie.write(id_node + ';' + id_stop +
                                                     ';' + str(0.0) + ';' +
                                                     str(ll / 1000.0) +
                                                     ';-1;-1;-1;-1;-1;' +
                                                     modb + ';' + modb + '\n')
                                        sortie.write(id_stop + ';' + id_node +
                                                     ';' + str(0.0) + ';' +
                                                     str(ll / 1000.0) +
                                                     ';-1;-1;-1;-1;-1;' +
                                                     moda + ';' + moda + '\n')
        feedback.setProgressText(
            unicode(nbc) + "/" + unicode(nb) + self.tr(" connected nodes"))
        sortie.close()
        return {self.OUTPUT: dest_id}
Exemple #11
0
    def linearMatrix(self, parameters, context, source, inField, target_source,
                     targetField, same_source_and_target, matType, nPoints,
                     feedback):

        if same_source_and_target:
            # need to fetch an extra point from the index, since the closest match will always be the same
            # as the input feature
            nPoints += 1

        inIdx = source.fields().lookupField(inField)
        outIdx = target_source.fields().lookupField(targetField)

        fields = QgsFields()
        input_id_field = source.fields()[inIdx]
        input_id_field.setName('InputID')
        fields.append(input_id_field)
        if matType == 0:
            target_id_field = target_source.fields()[outIdx]
            target_id_field.setName('TargetID')
            fields.append(target_id_field)
            fields.append(QgsField('Distance', QVariant.Double))
        else:
            fields.append(QgsField('MEAN', QVariant.Double))
            fields.append(QgsField('STDDEV', QVariant.Double))
            fields.append(QgsField('MIN', QVariant.Double))
            fields.append(QgsField('MAX', QVariant.Double))

        out_wkb = QgsWkbTypes.multiType(
            source.wkbType()) if matType == 0 else source.wkbType()
        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, fields, out_wkb,
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        index = QgsSpatialIndex(
            target_source.getFeatures(
                QgsFeatureRequest().setSubsetOfAttributes(
                    []).setDestinationCrs(source.sourceCrs(),
                                          context.transformContext())),
            feedback)

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

        features = source.getFeatures(
            QgsFeatureRequest().setSubsetOfAttributes([inIdx]))
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        for current, inFeat in enumerate(features):
            if feedback.isCanceled():
                break

            inGeom = inFeat.geometry()
            inID = str(inFeat[inIdx])
            featList = index.nearestNeighbor(inGeom.asPoint(), nPoints)
            distList = []
            vari = 0.0
            request = QgsFeatureRequest().setFilterFids(
                featList).setSubsetOfAttributes([outIdx]).setDestinationCrs(
                    source.sourceCrs(), context.transformContext())
            for outFeat in target_source.getFeatures(request):
                if feedback.isCanceled():
                    break

                if same_source_and_target and inFeat.id() == outFeat.id():
                    continue

                outID = outFeat[outIdx]
                outGeom = outFeat.geometry()
                dist = distArea.measureLine(inGeom.asPoint(),
                                            outGeom.asPoint())

                if matType == 0:
                    out_feature = QgsFeature()
                    out_geom = QgsGeometry.unaryUnion(
                        [inFeat.geometry(),
                         outFeat.geometry()])
                    out_feature.setGeometry(out_geom)
                    out_feature.setAttributes([inID, outID, dist])
                    sink.addFeature(out_feature, QgsFeatureSink.FastInsert)
                else:
                    distList.append(float(dist))

            if matType != 0:
                mean = sum(distList) / len(distList)
                for i in distList:
                    vari += (i - mean) * (i - mean)
                vari = math.sqrt(vari / len(distList))

                out_feature = QgsFeature()
                out_feature.setGeometry(inFeat.geometry())
                out_feature.setAttributes(
                    [inID, mean, vari,
                     min(distList),
                     max(distList)])
                sink.addFeature(out_feature, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
Exemple #12
0
class Worker(QtCore.QObject):
    '''The worker that does the heavy lifting.
    /* QGIS offers spatial indexes to make spatial search more
     * effective.  QgsSpatialIndex will find the nearest index
     * (approximate) geometry (rectangle) for a supplied point.
     * QgsSpatialIndex will give correct results when searching
     * for the nearest neighbour of a point in a point data set.
     * So something has to be done for non-point data sets
     *
     * Non-point join data set:
     * A two pass search is performed.  First the index is used to
     * find the nearest index geometry (approximation - rectangle),
     * and then compute the actual distance to this geometry.
     * Then this rectangle is used to find all features in the join
     * data set that may be the closest feature to the given point.
     * For all the features is this candidate set, the actual
     * distance to the given point is calculated, and the nearest
     * feature is returned.
     *
     * Non-point input data set:
     * First the centroid of the non-point input geometry is
     * calculated.  Then the index is used to find the nearest
     * neighbour to this point (using the approximate index
     * geometry).
     * The distance vector to this feature, combined with the
     * bounding rectangle of the input feature is used to create a
     * search rectangle to find the candidate join geometries.
     * For all the features is this candidate set, the actual
     * distance to the given feature is calculated, and the nearest
     * feature is returned.
     *
     * Joins involving multi-geometry data sets are not supported
     * by a spatial index.
     *
    */
    '''
    # Define the signals used to communicate back to the application
    progress = QtCore.pyqtSignal(float)  # For reporting progress
    status = QtCore.pyqtSignal(str)      # For reporting status
    error = QtCore.pyqtSignal(str)       # For reporting errors
    #killed = QtCore.pyqtSignal()
    # Signal for sending over the result:
    finished = QtCore.pyqtSignal(bool, object)

    def __init__(self, inputvectorlayer, joinvectorlayer,
                 outputlayername, approximateinputgeom, joinprefix,
                 usejoinlayerapproximation, usejoinlayerindex):
        """Initialise.

        Arguments:
        inputvectorlayer -- (QgsVectorLayer) The base vector layer
                            for the join
        joinvectorlayer -- (QgsVectorLayer) the join layer
        outputlayername -- (string) the name of the output memory
                           layer
        approximateinputgeom -- (boolean) should the input geometry
                                be approximated?  Is only be set for
                                non-single-point layers
        joinprefix -- (string) the prefix to use for the join layer
                      attributes in the output layer
        usejoinlayerindexapproximation -- (boolean) should the index
                             geometry approximations be used for the
                             join?
        usejoinlayerindex -- (boolean) should an index for the join
                             layer be used.  Will only use the index
                             geometry approximations for the join
        """

        # Set a variable to control the use of indexes and exact
        # geometries for non-point input geometries
        #self.nonpointexactindex = True
        self.nonpointexactindex = usejoinlayerindex

        QtCore.QObject.__init__(self)  # Essential!
        # Creating instance variables from the parameters
        self.inpvl = inputvectorlayer
        self.joinvl = joinvectorlayer
        self.outputlayername = outputlayername
        self.approximateinputgeom = approximateinputgeom
        self.joinprefix = joinprefix
        self.usejoinlayerapprox = usejoinlayerapproximation
        # Check if the layers are the same (self join)
        self.selfjoin = False
        if self.inpvl is self.joinvl:
            # This is a self join
            self.selfjoin = True
        # Creating instance variables for the progress bar ++
        # Number of elements that have been processed - updated by
        # calculate_progress
        self.processed = 0
        # Current percentage of progress - updated by
        # calculate_progress
        self.percentage = 0
        # Flag set by kill(), checked in the loop
        self.abort = False
        # Number of features in the input layer - used by
        # calculate_progress
        self.feature_count = self.inpvl.featureCount()
        # The number of elements that is needed to increment the
        # progressbar - set early in run()
        self.increment = self.feature_count // 1000

    def run(self):
        try:
            if self.inpvl is None or self.joinvl is None:
                self.status.emit('Layer is missing!')
                self.finished.emit(False, None)
                return
            #self.status.emit('Started!')
            # Check the geometry type and prepare the output layer
            geometryType = self.inpvl.geometryType()
            #self.status.emit('Input layer geometry type: ' +
            #                               str(geometryType))
            geometrytypetext = 'Point'
            if geometryType == QGis.Point:
                geometrytypetext = 'Point'
            elif geometryType == QGis.Line:
                geometrytypetext = 'LineString'
            elif geometryType == QGis.Polygon:
                geometrytypetext = 'Polygon'
            # Does the input vector contain multi-geometries?
            # Try to check the first feature
            # This is not used for anything yet
            self.inputmulti = False
            feats = self.inpvl.getFeatures()
            if feats is not None:
                #self.status.emit('#Input features: ' + str(feats))
                testfeature = feats.next()
                feats.rewind()
                feats.close()
                if testfeature is not None:
                    #self.status.emit('Input feature geometry: ' +
                    #                 str(testfeature.geometry()))
                    if testfeature.geometry() is not None:
                        if testfeature.geometry().isMultipart():
                            self.inputmulti = True
                            geometrytypetext = 'Multi' + geometrytypetext
                        else:
                            pass
                    else:
                        self.status.emit('No geometry!')
                        self.finished.emit(False, None)
                        return
                else:
                    self.status.emit('No input features!')
                    self.finished.emit(False, None)
                    return
            else:
                self.status.emit('getFeatures returns None for input layer!')
                self.finished.emit(False, None)
                return
            geomptext = geometrytypetext
            # Set the coordinate reference system to the input
            # layer's CRS
            if self.inpvl.crs() is not None:
                geomptext = (geomptext + "?crs=" +
                             str(self.inpvl.crs().authid()))
            outfields = self.inpvl.pendingFields().toList()
            #
            if self.joinvl.pendingFields() is not None:
                jfields = self.joinvl.pendingFields().toList()
                for joinfield in jfields:
                    outfields.append(QgsField(self.joinprefix +
                                     str(joinfield.name()),
                                     joinfield.type()))
            else:
                self.status.emit('Unable to get any join layer fields')
                #self.finished.emit(False, None)
                #return

            outfields.append(QgsField("distance", QVariant.Double))
            # Create a memory layer
            self.mem_joinl = QgsVectorLayer(geomptext,
                                            self.outputlayername,
                                            "memory")
            self.mem_joinl.startEditing()
            for field in outfields:
                self.mem_joinl.dataProvider().addAttributes([field])
            # For an index to be used, the input layer has to be a
            # point layer, the input layer geometries have to be
            # approximated to centroids, or the user has to have
            # accepted that a join layer index is used (for
            # non-point input layers).
            # (Could be extended to multipoint)
            if (self.inpvl.wkbType() == QGis.WKBPoint or
                    self.inpvl.wkbType() == QGis.WKBPoint25D or
                    self.approximateinputgeom or
                    self.nonpointexactindex):
                # Create a spatial index to speed up joining
                self.status.emit('Creating join layer index...')
                self.joinlind = QgsSpatialIndex()
                for feat in self.joinvl.getFeatures():
                    # Allow user abort
                    if self.abort is True:
                        break
                    self.joinlind.insertFeature(feat)
                self.status.emit('Join layer index created!')
            # Does the join layer contain multi geometries?
            # Try to check the first feature
            # This is not used for anything yet
            self.joinmulti = False
            feats = self.joinvl.getFeatures()
            if feats is not None:
                testfeature = feats.next()
                feats.rewind()
                feats.close()
                if testfeature is not None:
                    if testfeature.geometry() is not None:
                        if testfeature.geometry().isMultipart():
                            self.joinmulti = True
                    else:
                        self.status.emit('No join geometry!')
                        self.finished.emit(False, None)
                        return
                else:
                    self.status.emit('No join features!')
                    self.finished.emit(False, None)
                    return

            #if feats.next().geometry().isMultipart():
            #    self.joinmulti = True
            #feats.rewind()
            #feats.close()

            # Prepare for the join by fetching the layers into memory
            # Add the input features to a list
            inputfeatures = self.inpvl.getFeatures()
            self.inputf = []
            for f in inputfeatures:
                self.inputf.append(f)
            # Add the join features to a list
            joinfeatures = self.joinvl.getFeatures()
            self.joinf = []
            for f in joinfeatures:
                self.joinf.append(f)
            self.features = []

            # Do the join!
            # Using the original features from the input layer
            for feat in self.inputf:
                # Allow user abort
                if self.abort is True:
                    break
                self.do_indexjoin(feat)
                self.calculate_progress()
            self.mem_joinl.dataProvider().addFeatures(self.features)
            self.status.emit('Join finished')
        except:
            import traceback
            self.error.emit(traceback.format_exc())
            self.finished.emit(False, None)
            if self.mem_joinl is not None:
                self.mem_joinl.rollBack()
        else:
            self.mem_joinl.commitChanges()
            if self.abort:
                self.finished.emit(False, None)
            else:
                self.status.emit('Delivering the memory layer...')
                self.finished.emit(True, self.mem_joinl)

    def calculate_progress(self):
        '''Update progress and emit a signal with the percentage'''
        self.processed = self.processed + 1
        # update the progress bar at certain increments
        if (self.increment == 0 or
                self.processed % self.increment == 0):
            perc_new = (self.processed * 100) / self.feature_count
            if perc_new > self.percentage:
                self.percentage = perc_new
                self.progress.emit(self.percentage)

    def kill(self):
        '''Kill the thread by setting the abort flag'''
        self.abort = True

    def do_indexjoin(self, feat):
        '''Find the nearest neigbour using an index, if possible

        Parameter: feat -- The feature for which a neighbour is
                           sought
        '''
        infeature = feat
        infeatureid = infeature.id()
        inputgeom = QgsGeometry(infeature.geometry())
        # Shall approximate input geometries be used?
        if self.approximateinputgeom:
            # Use the centroid as the input geometry
            inputgeom = QgsGeometry(infeature.geometry()).centroid()
        # Check if the coordinate systems are equal, if not,
        # transform the input feature!
        if (self.inpvl.crs() != self.joinvl.crs()):
            try:
                inputgeom.transform(QgsCoordinateTransform(
                    self.inpvl.crs(), self.joinvl.crs()))
            except:
                import traceback
                self.error.emit(self.tr('CRS Transformation error!') +
                                ' - ' + traceback.format_exc())
                self.abort = True
                return
        nnfeature = None
        mindist = float("inf")
        ## Find the closest feature!
        if (self.approximateinputgeom or
                self.inpvl.wkbType() == QGis.WKBPoint or
                self.inpvl.wkbType() == QGis.WKBPoint25D):
            # The input layer's geometry type is point, or shall be
            # approximated to point (centroid).
            # Then a join index will always be used.
            if (self.usejoinlayerapprox or
                    self.joinvl.wkbType() == QGis.WKBPoint or
                    self.joinvl.wkbType() == QGis.WKBPoint25D):
                # The join layer's geometry type is point, or the
                # user wants approximate join geometries to be used.
                # Then the join index nearest neighbour function can
                # be used without refinement.
                if self.selfjoin:
                    # Self join!
                    # Have to get the two nearest neighbours
                    nearestids = self.joinlind.nearestNeighbor(
                                             inputgeom.asPoint(), 2)
                    if nearestids[0] == infeatureid and len(nearestids) > 1:
                        # The first feature is the same as the input
                        # feature, so choose the second one
                        nnfeature = self.joinvl.getFeatures(
                            QgsFeatureRequest(nearestids[1])).next()
                    else:
                        # The first feature is not the same as the
                        # input feature, so choose it
                        nnfeature = self.joinvl.getFeatures(
                            QgsFeatureRequest(nearestids[0])).next()
                    ## Pick the second closest neighbour!
                    ## (the first is supposed to be the point itself)
                    ## Should we check for coinciding points?
                    #nearestid = self.joinlind.nearestNeighbor(
                    #    inputgeom.asPoint(), 2)[1]
                    #nnfeature = self.joinvl.getFeatures(
                    #    QgsFeatureRequest(nearestid)).next()
                else:
                    # Not a self join, so we can search for only the
                    # nearest neighbour (1)
                    nearestid = self.joinlind.nearestNeighbor(
                                           inputgeom.asPoint(), 1)[0]
                    nnfeature = self.joinvl.getFeatures(
                                 QgsFeatureRequest(nearestid)).next()
                mindist = inputgeom.distance(nnfeature.geometry())
            elif (self.joinvl.wkbType() == QGis.WKBPolygon or
                  self.joinvl.wkbType() == QGis.WKBPolygon25D or
                  self.joinvl.wkbType() == QGis.WKBLineString or
                  self.joinvl.wkbType() == QGis.WKBLineString25D):
                # Use the join layer index to speed up the join when
                # the join layer geometry type is polygon or line
                # and the input layer geometry type is point or an
                # approximation (point)
                nearestindexid = self.joinlind.nearestNeighbor(
                    inputgeom.asPoint(), 1)[0]
                # Check for self join
                if self.selfjoin and nearestindexid == infeatureid:
                    # Self join and same feature, so get the two
                    # first two neighbours
                    nearestindexes = self.joinlind.nearestNeighbor(
                                             inputgeom.asPoint(), 2)
                    nearestindexid = nearestindexes[0]
                    if (nearestindexid == infeatureid and
                                  len(nearestindexes) > 1):
                        nearestindexid = nearestindexes[1]
                nnfeature = self.joinvl.getFeatures(
                    QgsFeatureRequest(nearestindexid)).next()
                mindist = inputgeom.distance(nnfeature.geometry())
                px = inputgeom.asPoint().x()
                py = inputgeom.asPoint().y()
                closefids = self.joinlind.intersects(QgsRectangle(
                    px - mindist,
                    py - mindist,
                    px + mindist,
                    py + mindist))
                for closefid in closefids:
                    if self.abort is True:
                        break
                    # Check for self join and same feature
                    if self.selfjoin and closefid == infeatureid:
                        continue
                    closef = self.joinvl.getFeatures(
                        QgsFeatureRequest(closefid)).next()
                    thisdistance = inputgeom.distance(closef.geometry())
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = closef
                    if mindist == 0:
                        break
            else:
                # Join with no index use
                # Go through all the features from the join layer!
                for inFeatJoin in self.joinf:
                    if self.abort is True:
                        break
                    joingeom = QgsGeometry(inFeatJoin.geometry())
                    thisdistance = inputgeom.distance(joingeom)
                    # If the distance is 0, check for equality of the
                    # features (in case it is a self join)
                    if (thisdistance == 0 and self.selfjoin and
                            infeatureid == inFeatJoin.id()):
                        continue
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = inFeatJoin
                    # For 0 distance, settle with the first feature
                    if mindist == 0:
                        break
        else:
            # non-simple point input geometries (could be multipoint)
            if (self.nonpointexactindex):
                # Use the spatial index on the join layer (default).
                # First we do an approximate search
                # Get the input geometry centroid
                centroid = QgsGeometry(infeature.geometry()).centroid()
                centroidgeom = centroid.asPoint()
                # Find the nearest neighbour (index geometries only)
                nearestid = self.joinlind.nearestNeighbor(centroidgeom, 1)[0]
                # Check for self join
                if self.selfjoin and nearestid == infeatureid:
                    # Self join and same feature, so get the two
                    # first two neighbours
                    nearestindexes = self.joinlind.nearestNeighbor(
                        centroidgeom, 2)
                    nearestid = nearestindexes[0]
                    if nearestid == infeatureid and len(nearestindexes) > 1:
                        nearestid = nearestindexes[1]
                nnfeature = self.joinvl.getFeatures(
                    QgsFeatureRequest(nearestid)).next()
                mindist = inputgeom.distance(nnfeature.geometry())
                # Calculate the search rectangle (inputgeom BBOX
                inpbbox = infeature.geometry().boundingBox()
                minx = inpbbox.xMinimum() - mindist
                maxx = inpbbox.xMaximum() + mindist
                miny = inpbbox.yMinimum() - mindist
                maxy = inpbbox.yMaximum() + mindist
                #minx = min(inpbbox.xMinimum(), centroidgeom.x() - mindist)
                #maxx = max(inpbbox.xMaximum(), centroidgeom.x() + mindist)
                #miny = min(inpbbox.yMinimum(), centroidgeom.y() - mindist)
                #maxy = max(inpbbox.yMaximum(), centroidgeom.y() + mindist)
                searchrectangle = QgsRectangle(minx, miny, maxx, maxy)
                # Fetch the candidate join geometries
                closefids = self.joinlind.intersects(searchrectangle)
                # Loop through the geometries and choose the closest
                # one
                for closefid in closefids:
                    if self.abort is True:
                        break
                    # Check for self join and identical feature
                    if self.selfjoin and closefid == infeatureid:
                        continue
                    closef = self.joinvl.getFeatures(
                        QgsFeatureRequest(closefid)).next()
                    thisdistance = inputgeom.distance(closef.geometry())
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = closef
                    if mindist == 0:
                        break
            else:
                # Join with no index use
                # Check all the features of the join layer!
                mindist = float("inf")  # should not be necessary
                for inFeatJoin in self.joinf:
                    if self.abort is True:
                        break
                    joingeom = QgsGeometry(inFeatJoin.geometry())
                    thisdistance = inputgeom.distance(joingeom)
                    # If the distance is 0, check for equality of the
                    # features (in case it is a self join)
                    if (thisdistance == 0 and self.selfjoin and
                            infeatureid == inFeatJoin.id()):
                        continue
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = inFeatJoin
                    # For 0 distance, settle with the first feature
                    if mindist == 0:
                        break
        if not self.abort:
            atMapA = infeature.attributes()
            atMapB = nnfeature.attributes()
            attrs = []
            attrs.extend(atMapA)
            attrs.extend(atMapB)
            attrs.append(mindist)

            outFeat = QgsFeature()
            # Use the original input layer geometry!:
            outFeat.setGeometry(QgsGeometry(infeature.geometry()))
            # Use the modified input layer geometry (could be
            # centroid)
            #outFeat.setGeometry(QgsGeometry(inputgeom))
            outFeat.setAttributes(attrs)
            self.calculate_progress()
            self.features.append(outFeat)
            #self.mem_joinl.dataProvider().addFeatures([outFeat])

    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('NNJoinEngine', message)
Exemple #13
0
    def processAlgorithm(self, parameters, context, feedback):

        layer = self.parameterAsLayer(parameters, self.Network, context)

        distance = parameters[self.Distance]
        angle = parameters[self.Angle] #Orientation Threshold

        Graph = {} #Store all node connections

        ## Preprocessing requirements

        params  = {'INPUT':layer,'OUTPUT':'memory:'}
        explode = st.run("native:explodelines",params,context=context,feedback=feedback)

        features = explode['OUTPUT'].getFeatures(QgsFeatureRequest())

        index = QgsSpatialIndex(explode['OUTPUT'].getFeatures())
        data = {feature.id():feature for feature in explode['OUTPUT'].getFeatures()}
        total = 0

        iN = 15#parameters[self.iters] #Number of neighbour points to search
        dI = parameters[self.dIters] #Number of distance operations to perform

        n = parameters[self.nodeCount]

        fields = QgsFields()
        for field in explode['OUTPUT'].fields():
            fields.append(QgsField(field.name(),field.type()))

        (writer, dest_id) = self.parameterAsSink(parameters, self.Repaired, context,
                                               fields, QgsWkbTypes.LineString, explode['OUTPUT'].sourceCrs())

        total = explode['OUTPUT'].featureCount()
        total = 100.0/total
        for enum,feature in enumerate(features):
            try:
                if total > 0:
                    feedback.setProgress(int(enum*total))
                try:
                    geom = feature.geometry().asPolyline()
                except Exception:
                    feedback.reportError(QCoreApplication.translate('Nodes','Only Linestring Layers Supported'))
                    return {}
                start,end = geom[0],geom[-1]
                branch = [start,end]
                for b in branch:
                    if b in Graph: #node count
                        Graph[b] += 1
                    else:
                        Graph[b] = 1

            except Exception as e:
                feedback.reportError(QCoreApplication.translate('Node Error','%s'%(e)))

        skip = []

        def calcDist(start,end):
            startxC,startyC = start
            endxC,endyC = end
            dx,dy = endxC-startxC,endyC-startyC
            dist = math.sqrt((dx**2)+(dy**2))
            angle = math.degrees(math.atan2(dy,dx))
            bearing = (90.0 - angle) % 360
            return dist,bearing

        feedback.pushInfo(QCoreApplication.translate('Create Lines','Snapping Nodes'))

        fet = QgsFeature()
        addDist = distance/dI
        total = 100.0/dI

        for i in range(dI): #TO DO - remove unnecessary distance search iteration
            if total != -1:
                feedback.setProgress(int(i*total))
            distance += addDist
            for enum,feature in enumerate(explode['OUTPUT'].getFeatures(QgsFeatureRequest())):
                try:
                    geomFeat = feature.geometry()
                    try:
                        geom = geomFeat.asPolyline()
                    except Exception:
                        feedback.reportError(QCoreApplication.translate('Nodes','Only Linestring Layers Supported'))
                        return {}
                    start,end = geom[0],geom[-1]
                    branch = [start,end]
                    startx,starty = start
                    endx,endy = end
                    fID = feature.id()

                    vertices = [Graph[branch[0]],Graph[branch[1]]]
                    rows = []
                    for field in explode['OUTPUT'].fields():
                        rows.append(feature[field.name()])
                    dist, dist2 = 1e10,1e10

                    if vertices[0] == 1:
                        near = index.nearestNeighbor(QgsPointXY(startx,starty), iN) #look for x closest lines
                        dist = 1e10
                        for nid in near:
                            nearFeat = data[nid]
                            inFID = nearFeat.id()
                            if inFID != fID: ##TO DO SIMPLIFY##
                                try:
                                    nearGeom = nearFeat.geometry().asPolyline()
                                except Exception:
                                    nearGeom = nearFeat.geometry().asMultiPolyline()[0]
                                start2,end2 = nearGeom[0],nearGeom[-1]

                                featDist,featOrient = calcDist(end,start)
                                curDist,curOrient = calcDist(start,start2)
                                curDist2,curOrient2 = calcDist(start2,end2)
                                curDiff = 180 - abs(abs(featOrient - curOrient) - 180)
                                curDiff2 = 180 - abs(abs(curOrient - curOrient2) - 180)

                                v = Graph[start2]
                                if curDist < dist and curDiff < angle and curDiff2 < angle:

                                    if v <= n:
                                        dist = curDist
                                        outB = start2
                                        outFID = inFID

                                curDist,curOrient = calcDist(start,end2)
                                curDist2,curOrient2 = calcDist(end2,start2)
                                curDiff = 180 - abs(abs(featOrient - curOrient) - 180)
                                curDiff2 = 180 - abs(abs(curOrient - curOrient2) - 180)
                                v = Graph[end2]

                                if curDist < dist and curDiff < angle and curDiff2 < angle:
                                    if v <= n:
                                        dist = curDist
                                        outB = end2
                                        outFID = inFID

                        if dist < distance:
                            if outB in skip or start in skip:
                                continue
                            else:
                                points = [QgsPointXY(outB[0],outB[1]),QgsPointXY(startx,starty)]
                                outGeom = QgsGeometry.fromPolylineXY(points)

                                for nid in near:
                                    nearFeat = data[nid]
                                    inFID = nearFeat.id()
                                    if inFID == fID or inFID == outFID:
                                        continue
                                    else:
                                        nearGeom = nearFeat.geometry()
                                        geom = nearGeom.asPolyline()
                                        if geom[0] == outB or geom[1] == outB:
                                            continue
                                        elif outGeom.intersects(nearGeom):
                                            outB = None
                                            break
                                if outB:
                                    skip.extend([outB,start])
                                    fet.setGeometry(outGeom)
                                    fet.setAttributes(rows)
                                    writer.addFeature(fet,QgsFeatureSink.FastInsert)
                                    v = Graph[outB]
                                    Graph[outB] = v + 1


                    if vertices[1] == 1:
                        near = index.nearestNeighbor(QgsPointXY(endx,endy), iN) #look for x closest lines
                        dist2 = 1e10
                        for nid in near:
                            nearFeat = data[nid]
                            inFID = nearFeat.id()
                            if inFID != fID:
                                try:
                                    nearGeom = nearFeat.geometry().asPolyline()
                                except Exception:
                                    nearGeom = nearFeat.geometry().asMultiPolyline()[0]
                                start2,end2 = nearGeom[0],nearGeom[-1]

                                if ((start2,end)) in skip:
                                    continue

                                featDist,featOrient = calcDist(start,end)
                                curDist,curOrient = calcDist(end,start2)
                                curDist2,curOrient2 = calcDist(start2,end2)
                                curDiff = 180 - abs(abs(featOrient - curOrient) - 180)
                                curDiff2 = 180 - abs(abs(curOrient - curOrient2) - 180)

                                v = Graph[start2]
                                if curDist < dist2 and curDiff < angle and curDiff2 < angle:
                                    if v <= n:
                                        dist2 = curDist
                                        outB = start2
                                        outFID = inFID

                                curDist,curOrient = calcDist(end,end2)
                                curDist2,curOrient2 = calcDist(end2,start2)
                                curDiff = 180 - abs(abs(featOrient - curOrient) - 180)
                                curDiff2 = 180 - abs(abs(curOrient - curOrient2) - 180)

                                v = Graph[end2]
                                if curDist < dist2 and curDiff < angle and curDiff2 < angle:
                                    if v <= n:
                                        dist2 = curDist
                                        outB = end2
                                        outFID = inFID

                        if dist2 < distance:

                            if outB in skip or end in skip:
                                continue
                            else:
                                points = [QgsPointXY(outB[0],outB[1]),QgsPointXY(endx,endy)]
                                outGeom = QgsGeometry.fromPolylineXY(points)

                                for nid in near:
                                    nearFeat = data[nid]
                                    inFID = nearFeat.id()
                                    if inFID == fID or inFID == outFID:
                                        continue
                                    else:
                                        nearGeom = nearFeat.geometry()
                                        geom = nearGeom.asPolyline()
                                        if geom[0] == outB or geom[1] == outB:
                                            continue
                                        elif outGeom.intersects(nearGeom):
                                            outB = None
                                            break

                                if outB:
                                    skip.extend([outB,end])
                                    outGeom = QgsGeometry.fromPolylineXY(points)
                                    fet.setGeometry(outGeom)
                                    fet.setAttributes(rows)
                                    writer.addFeature(fet,QgsFeatureSink.FastInsert)
                                    Graph[outB] = v + 1

                except Exception as e:
                    feedback.reportError(QCoreApplication.translate('Node Error','%s'%(e)))
                    continue

        total = 100.0/enum
        for enum,feature in enumerate(explode['OUTPUT'].getFeatures(QgsFeatureRequest())):
            try:
                if total != -1:
                    feedback.setProgress(int(enum*total))
                geomFeat = feature.geometry()

                rows = []
                for field in explode['OUTPUT'].fields():
                    rows.append(feature[field.name()])

                fet.setGeometry(geomFeat)
                fet.setAttributes(rows)
                writer.addFeature(fet,QgsFeatureSink.FastInsert)

            except Exception as e:
                feedback.reportError(QCoreApplication.translate('Node Error','%s'%(e)))
                continue
        return {self.Repaired:dest_id}
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        name_field_name = self.parameterAsString(parameters, self.NAME_FIELD,
                                                 context)
        elevation_field_name = self.parameterAsString(parameters,
                                                      self.ELEVATION_FIELD,
                                                      context)
        name_field_index = source.fields().lookupField(name_field_name)
        elevation_field_index = source.fields().lookupField(
            elevation_field_name)

        request = QgsFeatureRequest()
        request.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'),
                                  context.transformContext())
        expression_string = self.parameterAsString(parameters,
                                                   self.ORDERBY_EXPRESSION,
                                                   context)
        if expression_string:
            expression = QgsExpression(expression_string)
            if expression.hasParserError():
                raise QgsProcessingException(expression.parserErrorString())
            request.addOrderBy(expression_string)

        departure_point = QgsPointXY()
        destination_point = QgsPointXY()
        user_points = ''

        total = 100.0 / source.featureCount() if source.featureCount() else 0
        features = source.getFeatures(request)
        for current, feature in enumerate(features):
            if feedback.isCanceled():
                break
            if not feature.hasGeometry():
                continue
            point = feature.geometry().asPoint()

            if departure_point.isEmpty():
                departure_point = point
            destination_point = point

            name = 'UNTITLED'
            if name_field_index >= 0:
                name = feature[name_field_index]
                name = re.sub('[^0-9A-Za-z_]', '', re.sub('\s+', '_', name))

            elevation = 1000.0
            if elevation_field_index >= 0:
                elevation = feature[elevation_field_index]
            user_points += '        <ATCWaypoint id="{}">\n            <ATCWaypointType>User</ATCWaypointType>\n            <WorldPosition>{}</WorldPosition>\n        </ATCWaypoint>\n'.format(
                name, self.formattedCoordinateElevation(point, elevation))
            feedback.setProgress(int(current * total))

        if departure_point.isEmpty():
            raise QgsProcessingException('Error: departure point is missing')
        if destination_point.isEmpty():
            raise QgsProcessingException('Error: destination point is missing')

        strips = QgsVectorLayer(
            os.path.join(os.path.dirname(__file__), 'data', 'strips.gpkg'),
            'strips')
        index = QgsSpatialIndex(strips.getFeatures())

        icao_index = strips.fields().lookupField('icao')
        name_short_index = strips.fields().lookupField('nameshort')

        departure_airport = self.parameterAsString(parameters,
                                                   self.DEPARTURE_AIRPORT,
                                                   context)
        if departure_airport:
            feature = QgsFeature()
            expression = QgsExpression(
                "icao ILIKE '{}'".format(departure_airport))
            strips.getFeatures(
                QgsFeatureRequest(expression)).nextFeature(feature)
            if feature:
                departure_point = feature.geometry().asPoint()
            else:
                raise QgsProcessingException(
                    'Error: custom departure airport ICAO ID not found')

        destination_airport = self.parameterAsString(parameters,
                                                     self.DESTINATION_AIRPORT,
                                                     context)
        if destination_airport:
            feature = QgsFeature()
            expression = QgsExpression(
                "icao ILIKE '{}'".format(destination_airport))
            strips.getFeatures(
                QgsFeatureRequest(expression)).nextFeature(feature)
            if feature:
                destination_point = feature.geometry().asPoint()
            else:
                raise QgsProcessingException(
                    'Error: custom destination airport ICAO ID not found')

        departure = ''
        departure_header = ''
        destination = ''
        destination_header = ''
        if not departure_point.isEmpty():
            departure_id = index.nearestNeighbor(departure_point)
            feature = strips.getFeature(departure_id[0])
            point = feature.geometry().asPoint()
            elevation = 50.0
            departure = '        <ATCWaypoint id="{}">\n            <ATCWaypointType>Airport</ATCWaypointType>\n            <WorldPosition>{}</WorldPosition>\n            <RunwayNumberFP>1</RunwayNumberFP>\n            <ICAO>\n                <ICAOIdent>{}</ICAOIdent>\n            </ICAO>\n        </ATCWaypoint>\n'.format(
                feature[icao_index],
                self.formattedCoordinateElevation(point, elevation),
                feature[icao_index])
            departure_header = '        <DepartureID>{}</DepartureID>\n        <DepartureLLA>{}</DepartureLLA>\n        <DepartureName>{}</DepartureName>\n'.format(
                feature[icao_index],
                self.formattedCoordinateElevation(point, elevation),
                feature[name_short_index])

        if not destination_point.isEmpty():
            destination_id = index.nearestNeighbor(destination_point)
            feature = strips.getFeature(destination_id[0])
            point = feature.geometry().asPoint()
            elevation = 50.0
            destination = '        <ATCWaypoint id="{}">\n            <ATCWaypointType>Airport</ATCWaypointType>\n            <WorldPosition>{}</WorldPosition>\n            <RunwayNumberFP>1</RunwayNumberFP>\n            <ICAO>\n                <ICAOIdent>{}</ICAOIdent>\n            </ICAO>\n        </ATCWaypoint>\n'.format(
                feature[icao_index],
                self.formattedCoordinateElevation(point, elevation),
                feature[icao_index])
            destination_header = '        <DestinationID>{}</DestinationID>\n        <DestinationLLA>{}</DestinationLLA>\n        <DestinationName>{}</DestinationName>\n'.format(
                feature[icao_index],
                self.formattedCoordinateElevation(point, elevation),
                feature[name_short_index])

        title = self.parameterAsString(parameters, self.TITLE, context)

        plan_file_path = self.parameterAsString(parameters, self.OUTPUT,
                                                context)
        plan_file = open(plan_file_path, 'w')
        plan_file.write(
            '<?xml version="1.0" encoding="UTF-8"?>\n\n<SimBase.Document Type="AceXML" version="1,0">\n    <Descr>AceXML Document</Descr>\n    <FlightPlan.FlightPlan>\n        <Title>{}</Title>\n        <FPType>IFR</FPType>\n        <RouteType>LowAlt</RouteType>\n        <CruisingAlt>11000.000</CruisingAlt>\n'
            .format(title))
        plan_file.write(departure_header + destination_header)
        plan_file.write(
            '        <Descr>{}</Descr>\n        <AppVersion>\n            <AppVersionMajor>11</AppVersionMajor>\n            <AppVersionBuild>282174</AppVersionBuild>\n        </AppVersion>\n'
            .format(title))
        plan_file.write(departure + user_points + destination)
        plan_file.write('    </FlightPlan.FlightPlan>\n</SimBase.Document>\n')
        plan_file.close()

        return {self.OUTPUT: plan_file_path}
    def processAlgorithm(self, parameters, context, feedback):

        try:
            import math, random, string
            import pandas as pd
            import processing as st
            import networkx as nx
            import numpy as np
        except Exception as e:
            feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))
            feedback.reportError(QCoreApplication.translate('Error',' '))
            feedback.reportError(QCoreApplication.translate('Error','Error loading modules - please install the necessary dependencies'))
            return {}

        Network = self.parameterAsLayer(parameters, self.Centerline, context)
        Sources = self.parameterAsLayer(parameters, self.Sources, context)
        Targets = self.parameterAsLayer(parameters, self.Targets, context)

        Precision = 6

        explode = st.run("native:explodelines",{'INPUT':Network,'OUTPUT':'memory:'},context=context,feedback=feedback)

        wF = parameters[self.Weight]

        fet = QgsFeature()
        fs = QgsFields()

        fs = QgsFields()
        fs.append(QgsField("ID", QVariant.Int))
        field_names = ['Distance', 'SP_Dist']

        for name in field_names:
            fs.append(QgsField(name, QVariant.Double))

        (writer, dest_id) = self.parameterAsSink(parameters, self.Tortuosity, context,
                                            fs, QgsWkbTypes.LineString, Network.sourceCrs())

        index = QgsSpatialIndex(explode['OUTPUT'].getFeatures())
        orig_data = {feature.id():feature for feature in explode['OUTPUT'].getFeatures()}

        srcs,tgts = {},{}

        field_check = Sources.fields().indexFromName('ID')

        if field_check == -1:
            feedback.reportError(QCoreApplication.translate('Error','No ID attribute in Source layer'))
            return {}
        if Targets:
            field_check2 = Targets.fields().indexFromName('ID')
            if field_check2 == -1:
                feedback.reportError(QCoreApplication.translate('Error','No ID attribute in Targets layer'))
                return {}

        feedback.pushInfo(QCoreApplication.translate('Model','Defining Source Nodes'))
        total = 100.0/Sources.featureCount()
        c = 0
        for enum,feature in enumerate(Sources.getFeatures()): #Find source node
            try:
                if total > 0:
                    feedback.setProgress(int(enum*total))
                pnt = feature.geometry().asPoint()
                startx,starty = (round(pnt.x(),Precision),round(pnt.y(),Precision))
                featFIDs = index.nearestNeighbor(QgsPointXY(startx,starty), 2)
                d = 1e10
                ID = None
                for FID in featFIDs:
                    feature2 = orig_data[FID]
                    testGeom = QgsGeometry.fromPointXY(QgsPointXY(startx,starty))
                    dist = QgsGeometry.distance(testGeom,feature2.geometry())

                    if dist < d: #Find closest vertex in graph to source point

                        ID = feature['ID']
                        d = dist
                        geom = feature2.geometry()

                        start,end = geom.asPolyline()

                        startx2,starty2 = (round(start.x(),Precision),round(start.y(),Precision))
                        endx2,endy2 = (round(end.x(),Precision),round(end.y(),Precision))

                        testGeom2 = QgsGeometry.fromPointXY(QgsPointXY(startx2,starty2))
                        testGeom3 = QgsGeometry.fromPointXY(QgsPointXY(endx2,endy2))
                        near = QgsGeometry.distance(testGeom2,testGeom)
                        near2 = QgsGeometry.distance(testGeom3,testGeom)
                        if near < near2:
                            x,y = startx2,starty2
                        else:
                            x,y = endx2,endy2
                if ID:
                    if ID in srcs:
                        srcs[ID].append((x,y))
                    else:
                        srcs[ID] = [(x,y)]
                    c+=1

            except Exception as e:
                feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))

        if Targets:
            total = 100.0/Targets.featureCount()

            feedback.pushInfo(QCoreApplication.translate('Model','Defining Target Nodes'))
            for enum,feature in enumerate(Targets.getFeatures()): #Find source node
                try:
                    if total > 0:
                        feedback.setProgress(int(enum*total))
                    pnt = feature.geometry().asPoint()

                    startx,starty = (round(pnt.x(),Precision),round(pnt.y(),Precision))

                    featFIDs = index.nearestNeighbor(QgsPointXY(startx,starty), 2)

                    d = 1e10
                    ID = None
                    for FID in featFIDs:
                        feature2 = orig_data[FID]
                        testGeom = QgsGeometry.fromPointXY(QgsPointXY(startx,starty))
                        dist = QgsGeometry.distance(testGeom,feature2.geometry())

                        if dist < d: #Find closest vertex in graph to source point
                            ID = feature['ID']
                            d = dist
                            geom = feature2.geometry()

                            start,end = geom.asPolyline()

                            startx2,starty2 = (round(start.x(),Precision),round(start.y(),Precision))
                            endx2,endy2 = (round(end.x(),Precision),round(end.y(),Precision))

                            testGeom2 = QgsGeometry.fromPointXY(QgsPointXY(startx2,starty2))
                            testGeom3 = QgsGeometry.fromPointXY(QgsPointXY(endx2,endy2))
                            near = QgsGeometry.distance(testGeom2,testGeom)
                            near2 = QgsGeometry.distance(testGeom3,testGeom)
                            if near < near2:
                                x,y = startx2,starty2
                            else:
                                x,y = endx2,endy2
                    if ID:
                        if ID in tgts:
                            tgts[ID].append((x,y))
                        else:
                            tgts[ID] = [(x,y)]
                        c+=1

                except Exception as e:
                    feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))

        total = 100.0/Network.featureCount()

        G = nx.Graph()

        feedback.pushInfo(QCoreApplication.translate('Model','Building Graph'))
        for enum,feature in enumerate(explode['OUTPUT'].getFeatures()): #Build Graph
            try:
                if total > 0:
                    feedback.setProgress(int(enum*total))

                geom = feature.geometry()
                if geom.isMultipart():
                    start,end = geom.asMultiPolyline()[0]
                else:
                    start,end = geom.asPolyline()

                startx,starty = (round(start.x(),Precision),round(start.y(),Precision))
                endx,endy = (round(end.x(),Precision),round(end.y(),Precision))

                if wF:
                    w = feature[wF]
                    if type(w) == int or type(w) == float:
                        pass
                    else:
                        feedback.reportError(QCoreApplication.translate('Error','Weight field contains non numeric values - check for null values'))
                        return {}
                    w = float(W)*feature.geometry().length()
                else:
                    w = feature.geometry().length()

                G.add_edge((startx,starty),(endx,endy),weight=w)
                #G.add_edge((endx,endy),(startx,starty),weight=w)

            except Exception as e:
                feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))

        feedback.pushInfo(QCoreApplication.translate('Model','Creating Fracture Network'))
        try:
            for FID in srcs:
                sources = srcs[FID]
                for source in sources:
                    if G.has_node(source):
                        if FID in tgts:
                            targets = tgts[FID]
                            for target in targets:
                                try:
                                    path = nx.single_source_dijkstra(G, source, target, weight='weight')
                                except Exception:
                                    feedback.reportError(QCoreApplication.translate('Error','No connection found between source and target of ID %s'%(FID)))
                                    continue

                                length = nx.single_source_dijkstra_path_length(G,source)
                                sx = None

                                Index = 1
                                for p in path[Index]:
                                    if sx == None:
                                        sx, sy = p[0], p[1]
                                        continue
                                    ex, ey = p[0], p[1]
                                    D = max([length[(sx, sy)], length[(ex, ey)]])
                                    dx = path[Index][0][0] - ex
                                    dy = path[Index][0][1] - ey
                                    dx2 = path[Index][0][0] - sx
                                    dy2 = path[Index][0][1] - sy
                                    SP = max([math.sqrt((dx ** 2) + (dy ** 2)), math.sqrt((dx2 ** 2) + (dy2 ** 2))])

                                    points = [QgsPointXY(sx, sy), QgsPointXY(ex, ey)]
                                    fet.setGeometry(QgsGeometry.fromPolylineXY(points))
                                    fet.setAttributes([FID, D, SP])
                                    writer.addFeature(fet)
                                    sx, sy = ex, ey

                        else:
                            length, path = nx.single_source_dijkstra(G, source, weight='weight')
                            sx = None
                            Index = max(length, key=length.get)
                            source = path[Index][-1]
                            for p in path[Index]:
                                if sx == None:
                                    sx, sy = p[0], p[1]
                                    continue
                                ex, ey = p[0], p[1]
                                D = max([length[(sx, sy)], length[(ex, ey)]])
                                dx = path[Index][0][0] - ex
                                dy = path[Index][0][1] - ey
                                dx2 = path[Index][0][0] - sx
                                dy2 = path[Index][0][1] - sy
                                SP = max([math.sqrt((dx ** 2) + (dy ** 2)), math.sqrt((dx2 ** 2) + (dy2 ** 2))])

                                points = [QgsPointXY(sx, sy), QgsPointXY(ex, ey)]
                                fet.setGeometry(QgsGeometry.fromPolylineXY(points))
                                fet.setAttributes([FID, D, SP])
                                writer.addFeature(fet)
                                sx, sy = ex, ey

        except Exception as e:
            feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))

        return {self.Tortuosity:dest_id}
class Worker(QtCore.QObject):
    '''The worker that does the heavy lifting.
    /* QGIS offers spatial indexes to make spatial search more
     * effective.  QgsSpatialIndex will find the nearest index
     * (approximate) geometry (rectangle) for a supplied point.
     * QgsSpatialIndex will only give correct results when searching
     * for the nearest neighbour of a point in a point data set.
     * So something has to be done for non-point data sets
     *
     * Non-point join data set:
     * A two pass search is performed.  First the index is used to
     * find the nearest index geometry (approximation - rectangle),
     * and then compute the distance to the actual indexed geometry.
     * A rectangle is constructed from this (maximum minimum)
     * distance, and this rectangle is used to find all features in
     * the join data set that may be the closest feature to the given
     * point.
     * For all the features is this candidate set, the actual
     * distance to the given point is calculated, and the nearest
     * feature is returned.
     *
     * Non-point input data set:
     * First the centroid of the non-point input geometry is
     * calculated.  Then the index is used to find the nearest
     * neighbour to this point (using the approximate index
     * geometry).
     * The distance vector to this feature, combined with the
     * bounding rectangle of the input feature is used to create a
     * search rectangle to find the candidate join geometries.
     * For all the features is this candidate set, the actual
     * distance to the given feature is calculated, and the nearest
     * feature is returned.
     *
     * Joins involving multi-geometry data sets are not supported
     * by a spatial index.
     *
    */
    '''
    # Define the signals used to communicate back to the application
    progress = QtCore.pyqtSignal(float)  # For reporting progress
    status = QtCore.pyqtSignal(str)  # For reporting status
    error = QtCore.pyqtSignal(str)  # For reporting errors
    # Signal for sending over the result:
    finished = QtCore.pyqtSignal(bool, object)

    def __init__(self,
                 inputvectorlayer,
                 joinvectorlayer,
                 outputlayername,
                 joinprefix,
                 distancefieldname="distance",
                 approximateinputgeom=False,
                 usejoinlayerapproximation=False,
                 usejoinlayerindex=True,
                 selectedinputonly=True,
                 selectedjoinonly=True):
        """Initialise.

        Arguments:
        inputvectorlayer -- (QgsVectorLayer) The base vector layer
                            for the join
        joinvectorlayer -- (QgsVectorLayer) the join layer
        outputlayername -- (string) the name of the output memory
                           layer
        joinprefix -- (string) the prefix to use for the join layer
                      attributes in the output layer
        distancefieldname -- name of the (new) field where neighbour
                             distance is stored
        approximateinputgeom -- (boolean) should the input geometry
                                be approximated?  Is only be set for
                                non-single-point layers
        usejoinlayerindexapproximation -- (boolean) should the index
                             geometry approximations be used for the
                             join?
        usejoinlayerindex -- (boolean) should an index for the join
                             layer be used.
        """

        QtCore.QObject.__init__(self)  # Essential!
        # Set a variable to control the use of indexes and exact
        # geometries for non-point input geometries
        self.nonpointexactindex = usejoinlayerindex
        # Creating instance variables from the parameters
        self.inpvl = inputvectorlayer
        self.joinvl = joinvectorlayer
        self.outputlayername = outputlayername
        self.joinprefix = joinprefix
        self.approximateinputgeom = approximateinputgeom
        self.usejoinlayerapprox = usejoinlayerapproximation
        self.selectedinonly = selectedinputonly
        self.selectedjoonly = selectedjoinonly
        # Check if the layers are the same (self join)
        self.selfjoin = False
        if self.inpvl is self.joinvl:
            # This is a self join
            self.selfjoin = True
        # The name of the attribute for the calculated distance
        self.distancename = distancefieldname
        # Creating instance variables for the progress bar ++
        # Number of elements that have been processed - updated by
        # calculate_progress
        self.processed = 0
        # Current percentage of progress - updated by
        # calculate_progress
        self.percentage = 0
        # Flag set by kill(), checked in the loop
        self.abort = False
        # Number of features in the input layer - used by
        # calculate_progress (set when needed)
        self.feature_count = 1
        # The number of elements that is needed to increment the
        # progressbar (set when needed)
        self.increment = 0

    def run(self):
        try:
            # Check if the layers look OK
            if self.inpvl is None or self.joinvl is None:
                self.status.emit('Layer is missing!')
                self.finished.emit(False, None)
                return
            # Check if there are features in the layers
            incount = 0
            if self.selectedinonly:
                incount = self.inpvl.selectedFeatureCount()
            else:
                incount = self.inpvl.featureCount()
            joincount = 0
            if self.selectedjoonly:
                joincount = self.joinvl.selectedFeatureCount()
            else:
                joincount = self.joinvl.featureCount()
            if incount == 0 or joincount == 0:
                self.status.emit('Layer without features!')
                self.finished.emit(False, None)
                return
            # Check the geometry type and prepare the output layer
            geometryType = self.inpvl.geometryType()
            geometrytypetext = 'Point'
            if geometryType == QgsWkbTypes.PointGeometry:
                geometrytypetext = 'Point'
            elif geometryType == QgsWkbTypes.LineGeometry:
                geometrytypetext = 'LineString'
            elif geometryType == QgsWkbTypes.PolygonGeometry:
                geometrytypetext = 'Polygon'
            # Does the input vector contain multi-geometries?
            # Try to check the first feature
            # This is not used for anything yet
            self.inputmulti = False
            if self.selectedinonly:
                #feats = self.inpvl.selectedFeaturesIterator()
                feats = self.inpvl.getSelectedFeatures()
            else:
                feats = self.inpvl.getFeatures()
            if feats is not None:
                testfeature = next(feats)
                feats.rewind()
                feats.close()
                if testfeature is not None:
                    if testfeature.hasGeometry():
                        if testfeature.geometry().isMultipart():
                            self.inputmulti = True
                            geometrytypetext = 'Multi' + geometrytypetext
                        else:
                            pass
                    else:
                        self.status.emit('No geometry!')
                        self.finished.emit(False, None)
                        return
                else:
                    self.status.emit('No input features!')
                    self.finished.emit(False, None)
                    return
            else:
                self.status.emit('getFeatures returns None for input layer!')
                self.finished.emit(False, None)
                return
            geomttext = geometrytypetext
            # Set the coordinate reference system to the input
            # layer's CRS using authid (proj4 may be more robust)
            if self.inpvl.crs() is not None:
                geomttext = (geomttext + "?crs=" +
                             str(self.inpvl.crs().authid()))
            # Retrieve the fields from the input layer
            outfields = self.inpvl.pendingFields().toList()
            # Retrieve the fields from the join layer
            if self.joinvl.pendingFields() is not None:
                jfields = self.joinvl.pendingFields().toList()
                for joinfield in jfields:
                    outfields.append(
                        QgsField(self.joinprefix + str(joinfield.name()),
                                 joinfield.type()))
            else:
                self.status.emit('Unable to get any join layer fields')
            # Add the nearest neighbour distance field
            # Check if there is already a "distance" field
            # (should be avoided in the user interface)
            # Try a new name if there is a collission
            collission = True
            while collission:  # Iterate until there are no collissions
                collission = False
                for field in outfields:
                    if field.name() == self.distancename:
                        self.status.emit(
                            'Distance field already exists - renaming!')
                        #self.abort = True
                        #self.finished.emit(False, None)
                        #break
                        collission = True
                        self.distancename = self.distancename + '1'
            outfields.append(QgsField(self.distancename, QVariant.Double))
            # Create a memory layer using a CRS description
            self.mem_joinl = QgsVectorLayer(geomttext, self.outputlayername,
                                            "memory")
            # Set the CRS to the inputlayer's CRS
            self.mem_joinl.setCrs(self.inpvl.crs())
            self.mem_joinl.startEditing()
            # Add the fields
            for field in outfields:
                self.mem_joinl.dataProvider().addAttributes([field])
            # For an index to be used, the input layer has to be a
            # point layer, the input layer geometries have to be
            # approximated to centroids, or the user has to have
            # accepted that a join layer index is used (for
            # non-point input layers).
            # (Could be extended to multipoint)
            if (self.inpvl.wkbType() == QgsWkbTypes.Point
                    or self.inpvl.wkbType() == QgsWkbTypes.Point25D
                    or self.approximateinputgeom or self.nonpointexactindex):
                # Create a spatial index to speed up joining
                self.status.emit('Creating join layer index...')
                # Number of features in the input layer - used by
                # calculate_progress
                if self.selectedjoonly:
                    self.feature_count = self.joinvl.selectedFeatureCount()
                else:
                    self.feature_count = self.joinvl.featureCount()
                # The number of elements that is needed to increment the
                # progressbar - set early in run()
                self.increment = self.feature_count // 1000
                self.joinlind = QgsSpatialIndex()
                if self.selectedjoonly:
                    #for feat in self.joinvl.selectedFeaturesIterator():
                    for feat in self.joinvl.getSelectedFeatures():
                        # Allow user abort
                        if self.abort is True:
                            break
                        self.joinlind.insertFeature(feat)
                        self.calculate_progress()
                else:
                    for feat in self.joinvl.getFeatures():
                        # Allow user abort
                        if self.abort is True:
                            break
                        self.joinlind.insertFeature(feat)
                        self.calculate_progress()
                self.status.emit('Join layer index created!')
                self.processed = 0
                self.percentage = 0
                #self.calculate_progress()
            # Does the join layer contain multi geometries?
            # Try to check the first feature
            # This is not used for anything yet
            self.joinmulti = False
            if self.selectedjoonly:
                feats = self.joinvl.getSelectedFeatures()
            else:
                feats = self.joinvl.getFeatures()
            if feats is not None:
                testfeature = next(feats)
                feats.rewind()
                feats.close()
                if testfeature is not None:
                    if testfeature.hasGeometry():
                        if testfeature.geometry().isMultipart():
                            self.joinmulti = True
                    else:
                        self.status.emit('No join geometry!')
                        self.finished.emit(False, None)
                        return
                else:
                    self.status.emit('No join features!')
                    self.finished.emit(False, None)
                    return
            # Prepare for the join by fetching the layers into memory
            # Add the input features to a list
            self.inputf = []
            if self.selectedinonly:
                for f in self.inpvl.getSelectedFeatures():
                    self.inputf.append(f)
            else:
                for f in self.inpvl.getFeatures():
                    self.inputf.append(f)
            # Add the join features to a list
            self.joinf = []
            if self.selectedjoonly:
                for f in self.joinvl.getSelectedFeatures():
                    self.joinf.append(f)
            else:
                for f in self.joinvl.getFeatures():
                    self.joinf.append(f)
            self.features = []
            # Do the join!
            # Number of features in the input layer - used by
            # calculate_progress
            if self.selectedinonly:
                self.feature_count = self.inpvl.selectedFeatureCount()
            else:
                self.feature_count = self.inpvl.featureCount()
            # The number of elements that is needed to increment the
            # progressbar - set early in run()
            self.increment = self.feature_count // 1000
            # Using the original features from the input layer
            for feat in self.inputf:
                # Allow user abort
                if self.abort is True:
                    break
                self.do_indexjoin(feat)
                self.calculate_progress()
            self.mem_joinl.dataProvider().addFeatures(self.features)
            self.status.emit('Join finished')
        except:
            import traceback
            self.error.emit(traceback.format_exc())
            self.finished.emit(False, None)
            if self.mem_joinl is not None:
                self.mem_joinl.rollBack()
        else:
            self.mem_joinl.commitChanges()
            if self.abort:
                self.finished.emit(False, None)
            else:
                self.status.emit('Delivering the memory layer...')
                self.finished.emit(True, self.mem_joinl)

    def calculate_progress(self):
        '''Update progress and emit a signal with the percentage'''
        self.processed = self.processed + 1
        # update the progress bar at certain increments
        if (self.increment == 0 or self.processed % self.increment == 0):
            # Calculate percentage as integer
            perc_new = (self.processed * 100) / self.feature_count
            if perc_new > self.percentage:
                self.percentage = perc_new
                self.progress.emit(self.percentage)

    def kill(self):
        '''Kill the thread by setting the abort flag'''
        self.abort = True

    def do_indexjoin(self, feat):
        '''Find the nearest neigbour using an index, if possible

        Parameter: feat -- The feature for which a neighbour is
                           sought
        '''
        infeature = feat
        # Get the feature ID
        infeatureid = infeature.id()
        # Get the feature geometry
        inputgeom = infeature.geometry()
        # Shall approximate input geometries be used?
        if self.approximateinputgeom:
            # Use the centroid as the input geometry
            inputgeom = infeature.geometry().centroid()
        # Check if the coordinate systems are equal, if not,
        # transform the input feature!
        if (self.inpvl.crs() != self.joinvl.crs()):
            try:
                inputgeom.transform(
                    QgsCoordinateTransform(self.inpvl.crs(),
                                           self.joinvl.crs()))
            except:
                import traceback
                self.error.emit(
                    self.tr('CRS Transformation error!') + ' - ' +
                    traceback.format_exc())
                self.abort = True
                return
        ## Find the closest feature!
        nnfeature = None
        mindist = float("inf")
        if (self.approximateinputgeom
                or self.inpvl.wkbType() == QgsWkbTypes.Point
                or self.inpvl.wkbType() == QgsWkbTypes.Point25D):
            # The input layer's geometry type is point, or has been
            # approximated to point (centroid).
            # Then a join index will always be used.
            if (self.usejoinlayerapprox
                    or self.joinvl.wkbType() == QgsWkbTypes.Point
                    or self.joinvl.wkbType() == QgsWkbTypes.Point25D):
                # The join index nearest neighbour function can
                # be used without refinement.
                if self.selfjoin:
                    # Self join!
                    # Have to get the two nearest neighbours
                    nearestids = self.joinlind.nearestNeighbor(
                        inputgeom.asPoint(), 2)
                    if nearestids[0] == infeatureid and len(nearestids) > 1:
                        # The first feature is the same as the input
                        # feature, so choose the second one
                        if self.selectedjoonly:
                            nnfeature = next(
                                self.joinvl.getSelectedFeatures(
                                    QgsFeatureRequest(nearestids[1])))
                        else:
                            nnfeature = next(
                                self.joinvl.getFeatures(
                                    QgsFeatureRequest(nearestids[1])))
                    else:
                        # The first feature is not the same as the
                        # input feature, so choose it
                        if self.selectedjoonly:
                            nnfeature = next(
                                self.joinvl.getSelectedFeatures(
                                    QgsFeatureRequest(nearestids[0])))
                        else:
                            nnfeature = next(
                                self.joinvl.getFeatures(
                                    QgsFeatureRequest(nearestids[0])))
                else:
                    # Not a self join, so we can search for only the
                    # nearest neighbour (1)
                    nearestid = self.joinlind.nearestNeighbor(
                        inputgeom.asPoint(), 1)[0]
                    if self.selectedjoonly:
                        nnfeature = next(
                            self.joinvl.getSelectedFeatures(
                                QgsFeatureRequest(nearestid)))
                    else:
                        nnfeature = next(
                            self.joinvl.getFeatures(
                                QgsFeatureRequest(nearestid)))
                mindist = inputgeom.distance(nnfeature.geometry())
            elif (self.joinvl.wkbType() == QgsWkbTypes.Polygon
                  or self.joinvl.wkbType() == QgsWkbTypes.Polygon25D
                  or self.joinvl.wkbType() == QgsWkbTypes.LineString
                  or self.joinvl.wkbType() == QgsWkbTypes.LineString25D):
                # Use the join layer index to speed up the join when
                # the join layer geometry type is polygon or line
                # and the input layer geometry type is point or an
                # approximation (point)
                nearestindexid = self.joinlind.nearestNeighbor(
                    inputgeom.asPoint(), 1)[0]
                # Check for self join
                if self.selfjoin and nearestindexid == infeatureid:
                    # Self join and same feature, so get the
                    # first two neighbours
                    nearestindexes = self.joinlind.nearestNeighbor(
                        inputgeom.asPoint(), 2)
                    nearestindexid = nearestindexes[0]
                    if (nearestindexid == infeatureid
                            and len(nearestindexes) > 1):
                        nearestindexid = nearestindexes[1]
                if self.selectedjoonly:
                    nnfeature = next(
                        self.joinvl.getSelectedFeatures(
                            QgsFeatureRequest(nearestindexid)))
                else:
                    nnfeature = next(
                        self.joinvl.getFeatures(
                            QgsFeatureRequest(nearestindexid)))
                mindist = inputgeom.distance(nnfeature.geometry())
                px = inputgeom.asPoint().x()
                py = inputgeom.asPoint().y()
                closefids = self.joinlind.intersects(
                    QgsRectangle(px - mindist, py - mindist, px + mindist,
                                 py + mindist))
                for closefid in closefids:
                    if self.abort is True:
                        break
                    # Check for self join and same feature
                    if self.selfjoin and closefid == infeatureid:
                        continue
                    if self.selectedjoonly:
                        closef = next(
                            self.joinvl.getSelectedFeatures(
                                QgsFeatureRequest(closefid)))
                    else:
                        closef = next(
                            self.joinvl.getFeatures(
                                QgsFeatureRequest(closefid)))
                    thisdistance = inputgeom.distance(closef.geometry())
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = closef
                    if mindist == 0:
                        break
            else:
                # Join with no index use
                # Go through all the features from the join layer!
                for inFeatJoin in self.joinf:
                    if self.abort is True:
                        break
                    joingeom = inFeatJoin.geometry()
                    thisdistance = inputgeom.distance(joingeom)
                    # If the distance is 0, check for equality of the
                    # features (in case it is a self join)
                    if (thisdistance == 0 and self.selfjoin
                            and infeatureid == inFeatJoin.id()):
                        continue
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = inFeatJoin
                    # For 0 distance, settle with the first feature
                    if mindist == 0:
                        break
        else:
            # non-simple point input geometries (could be multipoint)
            if (self.nonpointexactindex):
                # Use the spatial index on the join layer (default).
                # First we do an approximate search
                # Get the input geometry centroid
                centroid = infeature.geometry().centroid()
                centroidgeom = centroid.asPoint()
                # Find the nearest neighbour (index geometries only)
                nearestid = self.joinlind.nearestNeighbor(centroidgeom, 1)[0]
                # Check for self join
                if self.selfjoin and nearestid == infeatureid:
                    # Self join and same feature, so get the two
                    # first two neighbours
                    nearestindexes = self.joinlind.nearestNeighbor(
                        centroidgeom, 2)
                    nearestid = nearestindexes[0]
                    if nearestid == infeatureid and len(nearestindexes) > 1:
                        nearestid = nearestindexes[1]
                if self.selectedjoonly:
                    nnfeature = next(
                        self.joinvl.getSelectedFeatures(
                            QgsFeatureRequest(nearestid)))
                else:
                    nnfeature = next(
                        self.joinvl.getFeatures(QgsFeatureRequest(nearestid)))
                mindist = inputgeom.distance(nnfeature.geometry())
                # Calculate the search rectangle (inputgeom BBOX
                inpbbox = infeature.geometry().boundingBox()
                minx = inpbbox.xMinimum() - mindist
                maxx = inpbbox.xMaximum() + mindist
                miny = inpbbox.yMinimum() - mindist
                maxy = inpbbox.yMaximum() + mindist
                #minx = min(inpbbox.xMinimum(), centroidgeom.x() - mindist)
                #maxx = max(inpbbox.xMaximum(), centroidgeom.x() + mindist)
                #miny = min(inpbbox.yMinimum(), centroidgeom.y() - mindist)
                #maxy = max(inpbbox.yMaximum(), centroidgeom.y() + mindist)
                searchrectangle = QgsRectangle(minx, miny, maxx, maxy)
                # Fetch the candidate join geometries
                closefids = self.joinlind.intersects(searchrectangle)
                # Loop through the geometries and choose the closest
                # one
                for closefid in closefids:
                    if self.abort is True:
                        break
                    # Check for self join and identical feature
                    if self.selfjoin and closefid == infeatureid:
                        continue
                    if self.selectedjoonly:
                        closef = next(
                            self.joinvl.getSelectedFeatures(
                                QgsFeatureRequest(closefid)))
                    else:
                        closef = next(
                            self.joinvl.getFeatures(
                                QgsFeatureRequest(closefid)))
                    thisdistance = inputgeom.distance(closef.geometry())
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = closef
                    if mindist == 0:
                        break
            else:
                # Join with no index use
                # Check all the features of the join layer!
                mindist = float("inf")  # should not be necessary
                for inFeatJoin in self.joinf:
                    if self.abort is True:
                        break
                    joingeom = inFeatJoin.geometry()
                    thisdistance = inputgeom.distance(joingeom)
                    # If the distance is 0, check for equality of the
                    # features (in case it is a self join)
                    if (thisdistance == 0 and self.selfjoin
                            and infeatureid == inFeatJoin.id()):
                        continue
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = inFeatJoin
                    # For 0 distance, settle with the first feature
                    if mindist == 0:
                        break
        if not self.abort:
            # Collect the attribute
            atMapA = infeature.attributes()
            atMapB = nnfeature.attributes()
            attrs = []
            attrs.extend(atMapA)
            attrs.extend(atMapB)
            attrs.append(mindist)
            # Create the feature
            outFeat = QgsFeature()
            # Use the original input layer geometry!:
            outFeat.setGeometry(infeature.geometry())
            # Use the modified input layer geometry (could be
            # centroid)
            #outFeat.setGeometry(inputgeom)
            # Add the attributes
            outFeat.setAttributes(attrs)
            self.calculate_progress()
            self.features.append(outFeat)
            #self.mem_joinl.dataProvider().addFeatures([outFeat])

    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('NNJoinEngine', message)
Exemple #17
0
class CatchmentAnalysis(QObject):
    # Setup signals
    finished = pyqtSignal(object)
    error = pyqtSignal(Exception, str)
    progress = pyqtSignal(float)
    warning = pyqtSignal(str)

    def __init__(self, iface, settings):
        QObject.__init__(self)
        self.concave_hull = ct.ConcaveHull()
        self.iface = iface
        self.settings = settings
        self.killed = False

    def analysis(self):
        if has_pydevd and is_debug:
            pydevd.settrace('localhost',
                            port=53100,
                            stdoutToServer=True,
                            stderrToServer=True,
                            suspend=False)
        if self.settings:
            try:
                # Prepare the origins
                origins = self.origin_preparation(self.settings['origins'],
                                                  self.settings['name'])
                self.progress.emit(10)
                if self.killed: return

                # Build the graph
                graph, tied_origins = self.graph_builder(
                    self.settings['network'], self.settings['cost'], origins,
                    self.settings['network tolerance'], self.settings['crs'],
                    self.settings['epsg'])
                self.progress.emit(20)
                if self.killed: return

                # Run the analysis
                catchment_network, catchment_points = self.graph_analysis(
                    graph, tied_origins, self.settings['distances'])
                self.progress.emit(40)
                if self.killed: return

                # Create output signal
                output = {
                    'output network features': None,
                    'output polygon features': None,
                    'distances': self.settings['distances']
                }

                network = self.settings['network']

                # Write and render the catchment polygons

                if self.settings['output polygon check']:
                    new_fields = QgsFields()
                    new_fields.append(QgsField('id', QVariant.Int))
                    new_fields.append(QgsField('origin', QVariant.String))
                    new_fields.append(QgsField('distance', QVariant.Int))

                    output_polygon_features = self.polygon_writer(
                        catchment_points,
                        self.settings['distances'],
                        new_fields,
                        self.settings['polygon tolerance'],
                    )
                    output['output polygon features'] = output_polygon_features

                self.progress.emit(70)
                if self.killed: return

                # get fields

                new_fields = self.get_fields(origins, self.settings['name'])

                # Write and render the catchment network
                output_network_features = self.network_writer(
                    catchment_network, new_fields, self.settings['name'])

                output['output network features'] = output_network_features

                if self.killed is False:
                    self.progress.emit(100)
                    self.finished.emit(output)

            except Exception as e:
                self.error.emit(e, traceback.format_exc())

    def origin_preparation(self, origin_vector, origin_name_field):

        # Create a dictionary of origin point dictionaries containing name and geometry
        origins = []

        # Loop through origin and get or create points
        for i, f in enumerate(origin_vector.getFeatures()):

            # Get origin name
            if origin_name_field:
                origin_name = f[origin_name_field]
            else:
                origin_name = i  # "origin_" + "%s" % (i+1)

            origins.append({
                'name': origin_name,
                'geom': f.geometry().centroid()
            })

        return origins

    def graph_builder(self, network, cost_field, origins, tolerance, crs,
                      epsg):

        # Settings
        otf = False

        # Get index of cost field
        network_fields = network.fields()
        network_cost_index = network_fields.indexFromName(cost_field)

        # Setting up graph build director
        director = QgsVectorLayerDirector(network, -1, '', '', '',
                                          QgsVectorLayerDirector.DirectionBoth)

        # Determining cost calculation
        if cost_field != 'length':
            strategy = ct.CustomCost(network_cost_index, 0.01)
        else:
            strategy = QgsNetworkDistanceStrategy()

        # Creating graph builder
        director.addStrategy(strategy)
        builder = QgsGraphBuilder(crs, otf, tolerance, epsg)

        # Reading origins and making list of coordinates
        graph_origin_points = []

        # Loop through the origin points and add graph vertex indices
        for index, origin in enumerate(origins):
            graph_origin_points.append(origins[index]['geom'].asPoint())

        # Get origin graph vertex index
        tied_origin_vertices = director.makeGraph(builder, graph_origin_points)

        # Build the graph
        graph = builder.graph()

        # Create dictionary of origin names and tied origins
        tied_origins = {}

        # Combine origin names and tied point vertices
        for index, tied_origin in enumerate(tied_origin_vertices):
            tied_origins[index] = {
                'name': origins[index]['name'],
                'vertex': tied_origin
            }

        self.spIndex = QgsSpatialIndex()
        self.indices = {}
        self.attributes_dict = {}
        self.centroids = {}
        i = 0
        for f in network.getFeatures():
            if f.geometry().type() == QgsWkbTypes.LineGeometry:
                if not f.geometry().isMultipart():
                    self.attributes_dict[f.id()] = f.attributes()
                    polyline = f.geometry().asPolyline()
                    for idx, p in enumerate(polyline[1:]):
                        ml = QgsGeometry.fromPolylineXY([polyline[idx], p])
                        new_f = QgsFeature()
                        new_f.setGeometry(ml.centroid())
                        new_f.setAttributes([f.id()])
                        new_f.setId(i)
                        self.spIndex.addFeature(new_f)
                        self.centroids[i] = f.id()
                        i += 1
                else:
                    self.attributes_dict[f.id()] = f.attributes()
                    for pl in f.geometry().asMultiPolyline():
                        for idx, p in enumerate(pl[1:]):
                            ml = QgsGeometry.fromPolylineXY([pl[idx], p])
                            new_f = QgsFeature()
                            new_f.setGeometry(ml.centroid())
                            new_f.setAttributes([f.id()])
                            new_f.setId(i)
                            self.spIndex.addFeature(new_f)
                            self.centroids[i] = f.id()
                            i += 1

        self.network_fields = network_fields
        return graph, tied_origins

    def graph_analysis(self, graph, tied_origins, distances):
        # Settings
        catchment_threshold = max(distances)

        # Variables
        catchment_network = {}
        catchment_points = {}

        # Loop through graph and get geometry and write to catchment network
        for index in range(graph.edgeCount()):
            inVertexId = graph.edge(index).fromVertex()
            outVertexId = graph.edge(index).toVertex()
            inVertexGeom = graph.vertex(inVertexId).point()
            outVertexGeom = graph.vertex(outVertexId).point()
            # only include one of the two possible arcs
            if inVertexId < outVertexId:
                arcGeom = QgsGeometry.fromPolylineXY(
                    [inVertexGeom, outVertexGeom])
                catchment_network[index] = {
                    'geom': arcGeom,
                    'start': inVertexId,
                    'end': outVertexId,
                    'cost': {}
                }

        # Loop through tied origins and write origin names
        for tied_point, origin in enumerate(tied_origins):
            origin_name = tied_origins[tied_point]['name']
            catchment_points[tied_point] = {'name': origin_name}
            catchment_points[tied_point].update(
                {distance: []
                 for distance in distances})

        # Loop through tied origins and write costs and polygon points
        i = 1
        for tied_point, origin in enumerate(tied_origins):
            self.progress.emit(20 + int(20 * i / len(tied_origins)))
            origin_name = tied_origins[tied_point]['name']
            originVertexId = graph.findVertex(
                tied_origins[tied_point]['vertex'])

            # Run dijkstra and get tree and cost
            (tree, cost) = QgsGraphAnalyzer.dijkstra(graph, originVertexId, 0)

            # Loop through graph arcs
            for index in catchment_network.keys():
                if self.killed: break
                # Define the arc properties
                inVertexId = catchment_network[index]['start']
                outVertexId = catchment_network[index]['end']
                inVertexCost = cost[inVertexId]
                outVertexCost = cost[outVertexId]
                # this is the permissive option gives cost to the arc based on the closest point,
                # it just needs to be reach by one node
                arcCost = min(inVertexCost, outVertexCost)
                # this is the restrictive option, gives cost to the arc based on the furtherst point,
                # it needs to be entirely within distance
                # arcCost = max(inVertexCost, outVertexCost)

                # If arc is the origin set cost to 0
                if outVertexId == originVertexId or inVertexId == originVertexId:
                    catchment_network[index]['cost'][origin_name] = 0

                # If arc is connected and within the maximum radius set cost
                elif arcCost <= catchment_threshold and tree[inVertexId] != -1:
                    if origin_name in catchment_network[index]['cost']:
                        if catchment_network[index]['cost'][origin_name] > int(
                                arcCost):
                            catchment_network[index]['cost'][
                                origin_name] = int(arcCost)
                    else:
                        catchment_network[index]['cost'][origin_name] = int(
                            arcCost)

                    # Add catchment points for each given radius
                    inVertexGeom = graph.vertex(inVertexId).point()
                    outVertexGeom = graph.vertex(outVertexId).point()
                    seg_length = catchment_network[index]['geom'].length(
                    )  # math.sqrt(inVertexGeom.sqrDist(outVertexGeom))
                    for distance in distances:
                        if self.killed: break
                        # this option includes both nodes as long as arc is within distance
                        # the polygon is the same as the network output
                        # if arcCost <= distance:
                        #    catchment_points[tied_point][distance].extend([inVertexGeom, outVertexGeom])
                        # this option only includes nodes within distance
                        # it does linear interpolation for extra points
                        if inVertexCost <= distance:
                            catchment_points[tied_point][distance].append(
                                inVertexGeom)
                            # add an extra point with linear referencing
                            if outVertexCost > distance:
                                target_dist = distance - inVertexCost
                                midVertexGeom = catchment_network[index][
                                    'geom'].interpolate(target_dist).asPoint()
                                catchment_points[tied_point][distance].append(
                                    midVertexGeom)
                        if outVertexCost <= distance:
                            catchment_points[tied_point][distance].append(
                                outVertexGeom)
                            # add an extra point with linear referencing
                            if inVertexCost > distance:
                                target_dist = distance - outVertexCost
                                midVertexGeom = catchment_network[index][
                                    'geom'].interpolate(seg_length -
                                                        target_dist).asPoint()
                                catchment_points[tied_point][distance].append(
                                    midVertexGeom)

            i += 1
        return catchment_network, catchment_points

    def get_fields(self, origins, use_name):
        # fields: self.network_fields
        # Setup all unique origin columns and minimum origin distance column

        # add origin field names
        if use_name:
            self.names = set([str(origin['name']) for origin in origins])
            for n in self.names:
                self.network_fields.append(QgsField(n, QVariant.Int))
        else:
            self.names = list(range(0, len(origins)))

        self.network_fields.append(QgsField('min_dist', QVariant.Int))

        return self.network_fields

    def network_writer(self, catchment_network, new_fields, use_name):

        # Loop through arcs in catchment network and write geometry and costs
        i = 0
        features = []

        for k, v in catchment_network.items():

            self.progress.emit(70 + int(30 * i / len(catchment_network)))

            if self.killed is True:
                break

            # Get arc properties
            arc_geom = v['geom']
            arc_cost_dict = {
                str(key): value
                for key, value in list(v['cost'].items())
            }

            i += 1
            # Ignore arc if not connected or outside of catchment
            if len(arc_cost_dict) > 0:
                # Create feature and write id and geom
                f = QgsFeature()
                # get original feature attributes
                centroid_match = self.spIndex.nearestNeighbor(
                    arc_geom.centroid().asPoint(), 1).pop()
                original_feature_id = self.centroids[centroid_match]
                f_attrs = self.attributes_dict[original_feature_id]
                arc_cost_list = []
                non_null_arc_cost_list = []
                for name in self.names:
                    try:
                        dist = arc_cost_dict[str(name)]
                        arc_cost_list.append(dist)
                        non_null_arc_cost_list.append(dist)
                    except KeyError:
                        arc_cost_list.append(NULL)

                f.setFields(new_fields)
                if use_name:
                    f.setAttributes(f_attrs + arc_cost_list +
                                    [min(non_null_arc_cost_list)])
                else:
                    f.setAttributes(f_attrs + [min(non_null_arc_cost_list)])

                f.setGeometry(arc_geom)

                # Write feature to output network layer
                features.append(f)

            i += 1

        return features

    def polygon_writer(self, catchment_points, distances, new_fields,
                       polygon_tolerance):

        # Setup unique origin dictionary containing all distances
        unique_origins_list = []
        polygon_dict = {}
        output_polygon_features = []
        i = 1
        for tied_point in catchment_points:
            if self.killed: break
            self.progress.emit(40 + int(30 * i / len(catchment_points)))
            name = catchment_points[tied_point]['name']
            if name not in unique_origins_list:
                polygon_dict[name] = {distance: [] for distance in distances}
                unique_origins_list.append(name)
            # Creating hull for each distance and if applicable in a list
            for distance in distances:
                if self.killed: break
                points = catchment_points[tied_point][distance]
                if len(points) > 2:  # Only three points can create a polygon
                    hull = self.concave_hull.concave_hull(
                        points, polygon_tolerance)
                    polygon_dict[name][distance].append(hull)
            i += 1
        # Generate the polygons
        index = 1
        hull_validity = True
        for name in polygon_dict:
            for distance in distances:
                for hull in polygon_dict[name][
                        distance]:  # Later add combine functionality
                    if self.killed: break
                    # Check if hull is a actual polygon
                    try:
                        polygon_geom = QgsGeometry.fromPolygonXY([
                            hull,
                        ])
                    except TypeError:
                        hull_validity = False
                        continue
                    if polygon_geom:
                        p = QgsFeature()
                        p.setFields(new_fields)
                        p.setAttribute('id', index)
                        p.setAttribute('origin', name)
                        p.setAttribute('distance', distance)
                        p.setGeometry(polygon_geom)
                        output_polygon_features.append(p)
                        index += 1
        if not hull_validity:
            self.warning.emit(
                'Polygon tolerance too high for small cost bands.')
        return output_polygon_features

    def kill(self):
        self.killed = True
    def run(self):

        # Converter escala textual para numérica (denominador)
        escala = self.escalaAvaliacao.currentText() # '1:100.000'
        escalaAval = escala.split(':')[1].replace('.','')    
        escalaAval = int(escalaAval)
        
        self.canvas.zoomScale(escalaAval)

        listaHomologosXY = {} # dicionario yx.
        listaHomologosZ = {} # dicionario z.    
       
        # Raio definido
        raio = self.spinBox.value()

        # Layers selecionados
        layer1 = self.referenciaComboBox.currentLayer()
        layer2 = self.avaliacaoComboBox.currentLayer()

        # Teste para identificar se esta setado.
        XY = False 
        Z = False

        if self.xy.isChecked():
            XY = True 
        if self.z.isChecked(): 
            Z = True

        # Troca de referência CRS para uma que utiliza medição em metros (3857).
        source1 = layer1.crs()
        source2 = layer2.crs()
        dest = QgsCoordinateReferenceSystem(3857)

        tr1 = QgsCoordinateTransform(source1, dest)
        tr2 = QgsCoordinateTransform(source2, dest)

        lista1 = [feat1 for feat1 in layer1.getFeatures()]
        lista2 = [feat2 for feat2 in layer2.getFeatures()]
        lista3 = []
         
        spIndex1 = QgsSpatialIndex()
        neighbour = QgsFeature()
        
        for feat1 in lista1: 

            spIndex1.insertFeature(feat1)  # transforma features em indices espaciais
            geom1 = QgsGeometry(feat1.geometry()) 
            geom1.transform(tr1)        
            raioTeste = raio 
            neighbour = None
            encontrado =  False
                        
            for feat2 in lista2:

                pt = feat2.geometry().asPoint()
                nearestid = spIndex1.nearestNeighbor(pt, 2)[0] # realiza a analise do vizinho mais próximo, numero de vizinhos é definido no segundo argumento.
                #print nearestid
                request = QgsFeatureRequest().setFilterFid(nearestid)
                geom2 = QgsGeometry(feat2.geometry()) 
                geom2.transform(tr2) 

                if geom1.buffer (raioTeste, 20 ) .contains (geom2):
                     raioTeste = sqrt (geom1.asPoint (). sqrDist (geom2.asPoint ()))
                     neighbour = feat2  
                     encontrado = True
                  
            # apenas pra descobrir se não foi encontrado.
            if encontrado == False: 
                #print u'\nHouve pontos nao encontrados dentro do raio definido.'
                frase = ("<span style='color:purple'>HOUVE PONTOS NAO ENCONTRADOS.</span>")
                lista3.append(frase)
            
            else:
                if XY:
                    listaHomologosXY[int(feat1.id()), int(neighbour.id())] = (raioTeste)
                
                if Z:
                    listaHomologosZ[int(feat1.id()), int(neighbour.id())]  = feat1.geometry().geometry().z() - neighbour.geometry().geometry().z()
                lista2.remove(neighbour)     

        if XY or Z:   
            #print '\nHomologos: \n', listaHomologosXY.keys()
            distAcum = 0
             
            resultado2 = "<span style='color:orange'>DISTANCIA ENTRE PONTOS: </span>",[ round(v, 2) for v in listaHomologosXY.values()]
            resultado1 = "<span style='color:orange'>PONTOS HOMOLOGOS: </span>",listaHomologosXY.keys()
            #print '\nDistancia entre pontos Homologados:\n',resultado2
            lista3.append(resultado1)
            lista3.append(resultado2)      
        
        if XY: 
            distAcum = 0
            for valorXY in listaHomologosXY.values():
                distAcum += valorXY    

            resultado = int(distAcum / len(listaHomologosXY))
            #print '\nDistancia media:\n', round(resultado,2)  
            b = "<span style='color:orange'>DISTANCIA MEDIA: </span>", round(resultado,2)
            lista3.append(b)
        
        if Z:
            zAcum = 0     
            for valorZ in listaHomologosZ.values(): 
                zAcum += valorZ

            resultado = int(zAcum / len(listaHomologosZ)) 
            #print '\nDiferenca media de elevacao: \n', round(resultado,3)
            a = "<span style='color:orange'>DISTANCIA MEDIA DE ELEVACAO: </span>", round(resultado,3)
            lista3.append(a)

        # Formatação para apresentar QmessageBox de forma bem distribuida.
        message = u""
        one_data = u"<p>{0}</p>"
        two_data = u"<p>{0} {1}</p>"
        for data in lista3:
            message += one_data.format(data) if type(data) == str else two_data.format(data[0], data[1])
  
        QMessageBox.about(self, "RESULTADO: ", message )  
Exemple #19
0
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """

        # Retrieve the feature source and sink. The 'dest_id' variable is used
        # to uniquely identify the feature sink, and must be included in the
        # dictionary returned by the processAlgorithm function.
        arrets = self.parameterAsSource(parameters, self.STOPS, context)
        stop_id=self.parameterAsFields(parameters,self.STOP_ID,context)[0]
        noeuds = self.parameterAsSource(parameters, self.NOEUDS, context)
        node_id=self.parameterAsFields(parameters,self.NODE_ID,context)[0]
        mode_i=self.parameterAsString(parameters,self.MODE_ORI,context)
        mode_j=self.parameterAsString(parameters,self.MODE_DES,context)
        texte_i=self.parameterAsString(parameters,self.TEXTE_ORI,context)
        texte_j=self.parameterAsString(parameters,self.TEXTE_DES,context)
        rayon=self.parameterAsDouble(parameters,self.RAYON,context)
        vitesse=self.parameterAsDouble(parameters,self.VITESSE,context)
        nb_max=self.parameterAsInt(parameters,self.MAX_NB,context)

        # Compute the number of steps to display within the progress bar and
        # get features from source
        ##a=fenetre.split(",")
        ##fenetre2=QgsRectangle(float(a[0]),float(a[2]),float(a[1]),float(a[3]))
        arr=[a for a in arrets.getFeatures()]
        nb=len(arr)
        
        index=QgsSpatialIndex(noeuds.getFeatures())
        champs=QgsFields()
        champs.append(QgsField('i',QVariant.String,len=15))
        champs.append(QgsField('j',QVariant.String,len=15))
        champs.append(QgsField(self.tr('length'),QVariant.Double))
        champs.append(QgsField(self.tr('time'),QVariant.Double))
        champs.append(QgsField(self.tr('mode'),QVariant.String,len=10))

        (table_connecteurs,dest_id) = self.parameterAsSink(parameters, self.CONNECTEURS,context,champs, QgsWkbTypes.LineString, noeuds.sourceCrs())
        nom_fichier=dest_id
        fichier_connecteurs=os.path.splitext(nom_fichier)[0]+".txt"
        sortie=codecs.open(fichier_connecteurs,"w",encoding="utf-8")
        nbc=0
        for i,n in enumerate(arr):
            near=index.nearestNeighbor(n.geometry().centroid().asPoint(),nb_max)
            feedback.setProgress(i*100/nb)
            if len(near)>0:
                for k,nearest in enumerate(near):
                    if k<nb_max:
                        f=noeuds.getFeatures(request=QgsFeatureRequest(nearest))
                        for j, g in enumerate(f):
                            if j==0:
                                l=n.geometry().distance(g.geometry())
                                id_node=unicode(g.attribute(node_id))
                                id_stop=unicode(n.attribute(stop_id))
                                if l<rayon:
                                    nbc+=1
                                    gline=QgsGeometry.fromPolylineXY([QgsPointXY(n.geometry().centroid().asPoint()),QgsPointXY(g.geometry().centroid().asPoint())])
                                    hline=QgsGeometry.fromPolylineXY([QgsPointXY(g.geometry().centroid().asPoint()),QgsPointXY(n.geometry().centroid().asPoint())])

                                    fline=QgsFeature()
                                    fline.setGeometry(gline)
                                    ll=gline.length()
                                    moda=unicode(mode_i)+unicode(mode_j)
                                    if vitesse<=0:
                                        fline.setAttributes([id_stop,id_node, ll/1000,0.0,moda])
                                    else:
                                        fline.setAttributes([id_stop,id_node, ll/1000,ll*60/(vitesse*1000),moda])
                                    fline2=QgsFeature()
                                    fline2.setGeometry(hline)
                                    modb=unicode(mode_j)+unicode(mode_i)
                                    if vitesse<=0:
                                        fline2.setAttributes([id_node,id_stop, ll/1000,0,modb])
                                    else:
                                        fline2.setAttributes([id_node,id_stop, ll/1000,ll*60/(vitesse*1000),modb])
                                    table_connecteurs.addFeature(fline)
                                    table_connecteurs.addFeature(fline2)
                                    if vitesse>0:
                                        sortie.write(id_node+';'+id_stop+';'+str((60/vitesse)*(ll/1000.0))+';'+str(ll/1000.0)+';-1;-1;-1;-1;-1;'+modb+';'+modb+'\n')
                                        sortie.write(id_stop+';'+id_node+';'+str((60/vitesse)*(ll/1000.0))+';'+str(ll/1000.0)+';-1;-1;-1;-1;-1;'+moda+';'+moda+'\n')
                                    else:
                                        sortie.write(id_node+';'+id_stop+';'+str(0.0)+';'+str(ll/1000.0)+';-1;-1;-1;-1;-1;'+modb +';'+modb+'\n')
                                        sortie.write(id_stop+';'+id_node+';'+str(0.0)+';'+str(ll/1000.0)+';-1;-1;-1;-1;-1;'+moda+';'+moda+'\n')
        feedback.setProgressText(unicode(nbc)+"/"+unicode(nb)+self.tr(" connected nodes"))
        sortie.close()
        return {self.OUTPUT: dest_id}
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        A = source.sourceExtent()
        A = float(A.width() * A.height())

        extent = self.parameterAsExtent(parameters, self.EXTENT, context,
                                        source.sourceCrs())
        # feedback.pushInfo('Extent: {}'.format(extent))
        if extent.area() != 0:
            A = float(extent.width() * extent.height())

        k = self.parameterAsInt(parameters, self.K, context)

        output_file = self.parameterAsFileOutput(parameters,
                                                 self.OUTPUT_HTML_FILE,
                                                 context)
        self.path = output_file

        spatialIndex = QgsSpatialIndex(source, feedback)

        count = source.featureCount()
        features = source.getFeatures()
        total = 100.0 / count if count else 1

        #initialize nnDistances dict
        nnDistances = {}  #key = k , value = list of distances
        x_values = []
        for i in range(0, k):
            nnDistances[i + 1] = []
            x_values.append(i + 1)
        #compute distances
        distance = QgsDistanceArea()
        distance.setSourceCrs(source.sourceCrs(), context.transformContext())
        distance.setEllipsoid(context.project().ellipsoid())
        for current, feat in enumerate(features):
            if feedback.isCanceled():
                break

            neighbours = spatialIndex.nearestNeighbor(
                feat.geometry().asPoint(), k + 1)
            for i in range(0, k):
                # request = QgsFeatureRequest().setFilterFid(i).setSubsetOfAttributes([])
                # neighbour = next(source.getFeatures(request))
                neighbour = QgsFeature()

                if i + 1 < len(neighbours):  # index 0 = point him self !
                    source.getFeatures(QgsFeatureRequest(
                        neighbours[i + 1])).nextFeature(neighbour)
                    dist = distance.measureLine(neighbour.geometry().asPoint(),
                                                feat.geometry().asPoint())
                    nnDistances[i + 1].append(dist)
            feedback.setProgress(int(current * total))

        results = {}
        do_values = []
        de_values = []
        de_max_values = []
        de_min_values = []
        nni_values = []
        #TODO : add edges correction
        #From Crimestat Manual: If the observed nearest neighbor distance
        #for point i is equal to or less than the distance
        #to the nearest borderr, it is retanined. (retain or exclude?)
        for k, distances in nnDistances.items():
            if len(distances) == 0:
                x_values = x_values[:k - 1]
                break
            do = float(sum(distances)) / count
            if k == 0:
                de = float(0.5 / math.sqrt(count / A))
            else:
                de = (k * math.factorial(2 * k)) / (math.pow(
                    (math.pow(2, k) * math.factorial(k)), 2) *
                                                    math.sqrt(count / A))
            nni = float(do / de)
            SE = float(0.26136 /
                       math.sqrt(count**2 / A))  #TODO : correct for k > 1
            do_values.append(round(do))
            de_values.append(round(de))
            de_max_values.append(round(de + SE))
            de_min_values.append(round(de - SE))
            nni_values.append(round(nni, 3))
            result = {}
            result['K'] = k
            result['OBSERVED_MD'] = do
            result['EXPECTED_MD'] = de
            result['NN_INDEX'] = nni
            result['POINT_COUNT'] = count
            if k == 0:
                result['Z_SCORE'] = float((do - de) / SE)
            results[k] = result

        feedback.pushInfo('Results: {}'.format(results))

        if not feedback.isCanceled():
            do_data = go.Scatter(x=x_values,
                                 y=do_values,
                                 mode='lines+markers',
                                 name=self.tr('Observed mean distance'))
            de_data = go.Scatter(x=x_values,
                                 y=de_values,
                                 mode='lines+markers',
                                 marker=dict(size=5,
                                             color='rgba(120, 120, 120, .8)',
                                             line=dict(width=0,
                                                       color='rgb(0, 0, 0)')),
                                 name=self.tr('Expected distance (if random)'))
            x_rev = x_values[::-1]
            de_min_values = de_min_values[::-1]
            se_data = go.Scatter(
                x=x_values + x_rev,
                y=de_max_values + de_min_values,
                fill='tozerox',
                fillcolor='rgba(120, 120, 120, .3)',
                line=dict(color='rgba(255,255,255,0)'),
                showlegend=False,
                name=self.tr('Expected distance (SE)'),
            )
            nni_data = go.Scatter(
                x=x_values,
                y=nni_values,
                mode='lines+markers',
                name=self.tr('Observed mean distance'),
                showlegend=False,
            )

            fig = tools.make_subplots(rows=2,
                                      cols=1,
                                      shared_xaxes=True,
                                      vertical_spacing=0.1)
            fig.append_trace(do_data, 1, 1)
            fig.append_trace(de_data, 1, 1)
            fig.append_trace(se_data, 1, 1)
            fig.append_trace(nni_data, 2, 1)

            fig['layout'].update(
                title=self.tr('K-Nearest neighbours'),
                hovermode='x',
                xaxis=dict(title=self.tr('K-Order'),
                           ticklen=5,
                           zeroline=False,
                           range=[0.9, len(x_values) + 0.1]),
                yaxis=dict(
                    title=self.tr('Distance [m]'),
                    ticklen=5,
                    gridwidth=1,
                ),
                yaxis2=dict(
                    title=self.tr('NN Index'),
                    ticklen=5,
                    gridwidth=1,
                    range=[
                        0, 1 if max(nni_values) < 1 else max(nni_values) + 0.1
                    ]),
                showlegend=True,
                legend=dict(orientation="h", y=1))
            # fig = go.Figure(data=[do_data, de_data, se_data], layout=layout)
            plt.offline.plot(fig, filename=output_file, auto_open=False)

        self.output = {self.OUTPUT_HTML_FILE: output_file}

        return self.output
Exemple #21
0
class TracingPipelines(QgsTask):
    def __init__(self,
                 pipelines,
                 valves,
                 description='TracingCAJ',
                 user_distance=0.001,
                 onfinish=None,
                 debug=False):
        super().__init__(description, QgsTask.CanCancel)

        self.onfinish = onfinish
        self.debug = debug
        self.__user_distance = user_distance
        self._pipelines_features = pipelines[0]
        self._valves_features = valves[0]
        self.__list_valves = []
        self.__list_visited_pipelines = []
        self.__list_visited_pipelines_ids = []
        self.__q_list_pipelines = []
        self.__q_list_pipelines_ids = []
        self.__iterations = 0
        self.__exception = None

        # Cria os índices espaciais
        self.__idx_pipelines = None
        self.__idx_valves = None
        if self.__idx_valves is None or self.__idx_pipelines is None:
            self.__create_spatial_index()

    def run(self):
        QgsMessageLog.logMessage(f'Started task {self.description()}',
                                 'TracingCAJ', Qgis.Info)

        # Busca por redes selecionadas (necessário ser apenas uma)
        if self.debug:
            self._pipelines_features.selectByIds([4343])
            # self._pipelines_features.getFeatures(16)

        selected_pipeline = self._pipelines_features.selectedFeatures()

        if len(selected_pipeline) != 1:
            QgsMessageLog.logMessage('Selecione apenas UMA rede', 'TracingCAJ',
                                     Qgis.Info)
            return False
        else:

            self.__q_list_pipelines.append(selected_pipeline[0].geometry())
            self.__q_list_pipelines_ids.append(selected_pipeline[0].id())

            while len(self.__q_list_pipelines) > 0:
                self.__iterations += 1

                # check isCanceled() to handle cancellation
                if self.isCanceled():
                    return False

                pipeline = self.__q_list_pipelines.pop(0)
                pipeline_id = self.__q_list_pipelines_ids.pop(0)
                if pipeline_id not in self.__list_visited_pipelines_ids:
                    self.__list_visited_pipelines.append(pipeline)
                    self.__list_visited_pipelines_ids.append(pipeline_id)

                    v1 = pipeline.vertexAt(0)
                    if self.debug:
                        v2 = pipeline.vertexAt(pipeline.get()[0].childCount() -
                                               1)
                    else:
                        v2 = pipeline.vertexAt(len(pipeline.get()) - 1)

                    try:
                        self.__find_neighbors(v1)
                        self.__find_neighbors(v2)
                    except Exception as e:
                        self.__exception = e
                        return False

        return True

    def finished(self, result):
        if result:
            self._valves_features.selectByIds(self.__list_valves)
            self._pipelines_features.selectByIds(
                self.__list_visited_pipelines_ids)

            if self.onfinish:
                self.onfinish()

            QgsMessageLog.logMessage(
                f"Task {self.description()} has been executed correctly"
                f"Iterations: {self.__iterations}"
                f"Pipelines: {self.__list_visited_pipelines_ids}"
                f"Valves: {self.__list_valves}",
                level=Qgis.Success)
        else:
            if self.__exception is None:
                QgsMessageLog.logMessage(
                    f"Tracing {self.description()} not successful "
                    f"but without exception "
                    f"(probably the task was manually canceled by the user)",
                    level=Qgis.Warning)
            else:
                QgsMessageLog.logMessage(
                    f"Task {self.description()}"
                    f"Exception: {self.__exception}",
                    level=Qgis.Critical)
                raise self.__exception

    def cancel(self):
        QgsMessageLog.logMessage(
            f'TracingTrask {self.description()} was canceled', level=Qgis.Info)
        super().cancel()

    def __create_spatial_index(self):
        self.__idx_pipelines = QgsSpatialIndex(
            self._pipelines_features.getFeatures(),
            flags=QgsSpatialIndex.FlagStoreFeatureGeometries)
        self.__idx_valves = QgsSpatialIndex(
            self._valves_features.getFeatures(),
            flags=QgsSpatialIndex.FlagStoreFeatureGeometries)

    def __find_neighbors(self, point_vertex):
        reg_isvisivel = None
        reg_status = None

        # Busca pelo registro mais próximo, dentro do raio maxDistance=user_distance
        reg_nearest = self.__idx_valves.nearestNeighbor(
            point=QgsPointXY(point_vertex),
            neighbors=1,
            maxDistance=self.__user_distance)

        if len(reg_nearest) > 0:
            # visivel = 'sim' = registro visível | visivel = 'não' = registro não visível
            reg_isvisivel = str(
                list(self._valves_features.getFeatures(reg_nearest))[0]
                ['visivel'])
            # status = 0 = 'Aberto' | status = 1 = 'Fechado'
            reg_status = str(
                list(self._valves_features.getFeatures(reg_nearest))[0]
                ['status'])

        if len(reg_nearest) > 0:
            if reg_isvisivel.upper() != 'NÃO' and reg_status == '0':
                self.__list_valves.append(reg_nearest[0])
        else:
            # Busca pelas 4 redes mais próximas dentro do raio maxDistance=user_distance
            pipelines_nearest = self.__idx_pipelines.nearestNeighbor(
                point=QgsPointXY(point_vertex),
                neighbors=4,
                maxDistance=self.__user_distance)
            if len(pipelines_nearest) > 0:
                for pipeline_id in pipelines_nearest:
                    pipeline_geometry = self.__idx_pipelines.geometry(
                        pipeline_id)
                    if pipeline_id not in self.__list_visited_pipelines_ids:
                        self.__q_list_pipelines_ids.append(pipeline_id)
                        self.__q_list_pipelines.append(pipeline_geometry)
    def processAlgorithm(self, parameters, context, feedback):

        layer = self.parameterAsSource(parameters, self.Centerline, context)
        layer2 = self.parameterAsVectorLayer(parameters, self.Polygons, context)
        samples = parameters[self.Samples]
        distance = parameters[self.Distance]
        FC = parameters[self.FC]

        context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck)

        if layer.sourceCrs() != layer2.sourceCrs():
            feedback.reportError(QCoreApplication.translate('Error','WARNING: Centerline and Polygon input do not have the same projection'))

        Precision=5
        if FC:
            field_names = ['Distance','SP_Dist','Width','Deviation','DWidthL','DWidthR']
        else:
            field_names = ['Distance','SP_Dist','Width','Deviation','DWidthL','DWidthR','Diff']

        fields = QgsFields()
        fields.append( QgsField('ID', QVariant.Int ))

        for name in field_names:
            fields.append( QgsField(name, QVariant.Double ))

        (writer, dest_id) = self.parameterAsSink(parameters, self.Output, context,
                                               fields, QgsWkbTypes.LineString, layer.sourceCrs())

        fet = QgsFeature()

        field_check =layer.fields().indexFromName('ID')
        field_check2 =layer2.fields().indexFromName('ID')
        if field_check == -1 or field_check2 == -1:
            feedback.reportError(QCoreApplication.translate('Error','Centerline and Polygon input feature require a matching ID field!'))
            return {}

        total = 0
        counts = {}
        if FC:
            vertices = st.run("native:extractvertices", {'INPUT':layer2,'OUTPUT':'memory:'})
            index = QgsSpatialIndex(vertices['OUTPUT'].getFeatures())
            data = {feature.id():feature for feature in vertices['OUTPUT'].getFeatures()}
        SPS = {}
        SPE = {}
        values = {}
        values2 = {}
        feats = {f["ID"]:f for f in layer2.getFeatures()}
        feedback.pushInfo(QCoreApplication.translate('Update','Defining Centerline Paths'))
        for enum,feature in enumerate(layer.getFeatures()):
            total += 1
            try:
                pnt = feature.geometry()
                if pnt.isMultipart():
                    pnt = pnt.asMultiPolyline()[0]
                else:
                    pnt = pnt.asPolyline()

                startx,starty = round(pnt[0][0],Precision),round(pnt[0][1],Precision)
                endx,endy = round(pnt[-1][0],Precision),round(pnt[-1][1],Precision)
                ID = feature['ID']
                c =  feature['Distance']
                if ID in SPS: #Get start and endpoint of each centerline
                    v = values[ID]
                    v2 = values2[ID]
                    v3 = counts[ID] + 1
                    if c > v:
                        SPS[ID] = [(startx,starty),(endx,endy)]
                        values[ID] = c
                    if c < v2:
                        SPE[ID] = [(startx,starty),(endx,endy)]
                        values2[ID] = c
                    counts[ID] = v3
                else:
                    SPS[ID] = [(startx,starty),(endx,endy)]
                    values[ID] = c
                    SPE[ID] = [(startx,starty),(endx,endy)]
                    values2[ID] = c
                    counts[ID] = 1

            except Exception as e:
                feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))
                continue ##Possible Collapsed Polyline?

        del values,values2
        total = 100.0/float(total)
        ID = None
        feedback.pushInfo(QCoreApplication.translate('Update','Creating Width Measurements'))
        report = True
        for enum,feature in enumerate(layer.getFeatures()):
            try:
                if total != -1:
                    feedback.setProgress(int(enum*total))
                pnt = feature.geometry()
                L = pnt.length()
                if pnt.isMultipart():
                    pnt = pnt.asMultiPolyline()[0]
                else:
                    pnt = pnt.asPolyline()

                curID = feature["ID"]
                if ID != curID:
                    startx,starty = round(pnt[0][0],Precision),round(pnt[0][1],Precision)
                    midx,midy = round(pnt[-1][0],Precision),round(pnt[-1][1],Precision)

                    ID = curID
                    if samples > 0:
                        if distance:
                            Counter = L
                            Limit = float(samples)
                        else:
                            Counter = 1
                            Limit = round((counts[ID]/float(samples)),0)

                    continue

                endx,endy = round(pnt[-1][0],Precision),round(pnt[-1][1],Precision)

                if samples > 0:
                    if distance:
                        Counter += L
                    else:
                        Counter += 1
                    if Counter < Limit:
                        startx,starty = midx,midy
                        midx,midy = endx,endy
                        continue
                if FC:
                    startx,starty = round(pnt[0][0],Precision),round(pnt[0][1],Precision)
                    near = index.nearestNeighbor(QgsPointXY(startx,starty), 1)
                    SPv = 1e12

                    midx,midy = data[near[0]].geometry().asPoint()

                    dx,dy = startx-midx,starty-midy
                    shortestPath = sqrt((dx**2)+(dy**2))
                    if shortestPath < SPv:
                        SPv = shortestPath

                    near = index.nearestNeighbor(QgsPointXY(endx,endy), 1)
                    midx,midy = data[near[0]].geometry().asPoint()
                    dx,dy = endx-midx,endy-midy

                    shortestPath = sqrt((dx**2)+(dy**2))
                    if shortestPath < SPv:
                        SP = shortestPath

                else:

                    m = ((starty - endy)/(startx - endx)) #Slope
                    inter = feats[curID]
                    Distance = inter.geometry().boundingBox().width()/2

                    if startx==endx: #if vertical
                        x1,y1 = midx+Distance,midy
                        x2,y2 = midx - Distance,midy
                    else:
                        m = ((starty - endy)/(startx - endx)) #Slope
                        angle = degrees(atan(m)) + 90

                        m = tan(radians(angle)) #Angle to Slope
                        c,s = (1/sqrt(1+m**2),m/sqrt(1+m**2)) #cosine and sin
                        x1,y1 = (midx + Distance*(c),midy + Distance*(s))
                        x2,y2 = (midx - Distance*(c),midy - Distance*(s))

                    geom = QgsGeometry.fromPolylineXY([QgsPointXY(x1,y1),QgsPointXY(midx,midy),QgsPointXY(x2,y2)])

                    geom = geom.intersection(inter.geometry())

                    if geom.isMultipart():
                        polyline = geom.asMultiPolyline()
                        if len(polyline) == 0:
                            startx,starty = midx,midy
                            midx,midy = endx,endy
                            continue

                        for line in polyline:
                            if len(line)==3:
                                t=1
                                start,mid,end = line
                                geom1 = QgsGeometry.fromPolylineXY([QgsPointXY(start[0],start[1]),QgsPointXY(mid[0],mid[1])])
                                geom2 = QgsGeometry.fromPolylineXY([QgsPointXY(mid[0],mid[1]),QgsPointXY(end[0],end[1])])
                                geom = QgsGeometry.fromPolylineXY([QgsPointXY(start[0],start[1]),QgsPointXY(mid[0],mid[1]),QgsPointXY(end[0],end[1])])
                                break

                    else:
                        try:
                            line = geom.asPolyline()
                        except Exception as e:
                            startx,starty = midx,midy
                            midx,midy = endx,endy
                            if report:
                                report = False
                                feedback.reportError(QCoreApplication.translate('Error','Width measurement along centerline does not intersect with input polygons. Check 1. ID fields corresponds between centerline and polygons 2. Geometry of centerline and polygon inputs by using the "Fix Geometries" tool'))
                            continue
                        geom1 = QgsGeometry.fromPolylineXY([QgsPointXY(line[0][0],line[0][1]),QgsPointXY(line[1][0],line[1][1])])
                        geom2 = QgsGeometry.fromPolylineXY([QgsPointXY(line[1][0],line[1][1]),QgsPointXY(line[2][0],line[2][1])])
                    Widths = [geom1.length(),geom2.length()]


                SP = list(SPS[curID])
                SP.extend(list(SPE[curID]))
                D = 0

                for start,end in combinations(SP,2):
                    dx = start[0] - end[0]
                    dy =  start[1] - end[1]
                    shortestPath = sqrt((dx**2)+(dy**2))
                    if shortestPath > D:
                        D = shortestPath
                        s = QgsPointXY(start[0],start[1])
                        e = QgsPointXY(end[0],end[1])

                m = s.sqrDist(e)

                u = ((midx - s.x()) * (e.x() - s.x()) + (midy - s.y()) * (e.y() - s.y()))/(m)
                x = s.x() + u * (e.x() - s.x())
                y = s.y() + u * (e.y() - s.y())
                d = ((e.x()-s.x())*(midy-s.y()) - (e.y() - s.y())*(midx - s.x())) #Determine which side of the SP the symmetry occurs

                dx = s.x() - e.x()
                dy =  s.y() - e.y()
                shortestPath = sqrt((dx**2)+(dy**2))

                dx = s.x() - x
                dy =  s.y() - y
                shortestPath1 = sqrt((dx**2)+(dy**2))

                if shortestPath < shortestPath1:
                    sym = QgsGeometry.fromPolylineXY([QgsPointXY(e.x(),e.y()),QgsPointXY(midx,midy)])
                else:
                    sym = QgsGeometry.fromPolylineXY([QgsPointXY(x,y),QgsPointXY(midx,midy)])

                if d < 0:
                    DW = -(sym.length())
                else:
                    DW = sym.length()

                if FC:
                    W = SPv*2
                    rows = [curID,feature['Distance'],feature['SP_Dist'],W,DW,(W/2)+DW,-(W/2)+DW]
                    geom = feature.geometry()
                else:
                    W = geom.length()

                    rows = [curID,feature['Distance'],feature['SP_Dist'],W,DW,(W/2)+DW,-(W/2)+DW,(min(Widths)/max(Widths))*100]

                startx,starty = midx,midy
                midx,midy = endx,endy

                fet.setGeometry(geom)
                fet.setAttributes(rows)
                writer.addFeature(fet)

                if distance:
                    Counter -= samples
                else:
                    Counter = 0

            except Exception as e:
                #feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))
                startx,starty = midx,midy
                midx,midy = endx,endy
                continue
        del writer
        if FC:
            del data

        del SPS,SPE

        return {self.Output:dest_id}
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        output_file = self.parameterAsFileOutput(parameters,
                                                 self.OUTPUT_HTML_FILE,
                                                 context)

        spatialIndex = QgsSpatialIndex(source, feedback)

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

        sumDist = 0.00
        A = source.sourceExtent()
        A = float(A.width() * A.height())

        features = source.getFeatures()
        count = source.featureCount()
        total = 100.0 / count if count else 1
        for current, feat in enumerate(features):
            if feedback.isCanceled():
                break

            neighbourID = spatialIndex.nearestNeighbor(
                feat.geometry().asPoint(), 2)[1]
            request = QgsFeatureRequest().setFilterFid(
                neighbourID).setSubsetOfAttributes([])
            neighbour = next(source.getFeatures(request))
            sumDist += distance.measureLine(neighbour.geometry().asPoint(),
                                            feat.geometry().asPoint())

            feedback.setProgress(int(current * total))

        do = float(sumDist) / count
        de = float(0.5 / math.sqrt(count / A))
        d = float(do / de)
        SE = float(0.26136 / math.sqrt(count**2 / A))
        zscore = float((do - de) / SE)

        results = {}
        results[self.OBSERVED_MD] = do
        results[self.EXPECTED_MD] = de
        results[self.NN_INDEX] = d
        results[self.POINT_COUNT] = count
        results[self.Z_SCORE] = zscore

        if output_file:
            data = []
            data.append('Observed mean distance: ' + str(do))
            data.append('Expected mean distance: ' + str(de))
            data.append('Nearest neighbour index: ' + str(d))
            data.append('Number of points: ' + str(count))
            data.append('Z-Score: ' + str(zscore))
            self.createHTML(output_file, data)
            results[self.OUTPUT_HTML_FILE] = output_file

        return results
Exemple #24
0
def generate_od_routes(
    network_layer: QgsVectorLayer,
    origin_layer: QgsVectorLayer,
    poi_layer: QgsVectorLayer,
    size_field: str,
    class_field: str,
    work_layer: QgsVectorLayer = None,
    work_size_field: str = None,
    school_layer: QgsVectorLayer = None,
    school_size_field: str = None,
    origin_weight_field: str = None,
    socio_data=None,
    health_data=None,
    diversity_data=None,
    join_on: str = None,
    max_distance: int = 25000,
    return_layer: bool = True,
    return_raw: bool = False,
    feedback: QgsProcessingFeedback = None,
) -> QgsVectorLayer:
    """
    Shortest path algorithm based on Dijkstra's algorithm

    :param network_layer: road network
    :param points_layer: combined from to points
    :param relations_data: tabular from to id data
    :param origin_field: name of from field
    :param destination_field: name of to field
    :param max_distance: maximum distance/cost
    :param crs: output layer crs
    """

    if not network_layer.wkbType() & QgsWkbTypes.LineString:
        raise Exception('Network layer must be of type LineString')
    crs = network_layer.crs()

    ## prepare graph
    director = QgsVectorLayerDirector(
        source=network_layer,
        directionFieldId=-1,
        directDirectionValue='',
        reverseDirectionValue='',
        bothDirectionValue='',
        defaultDirection=QgsVectorLayerDirector.DirectionBoth,
    )
    # First strategy is for actual shortest distance calculation
    director.addStrategy(QgsNetworkDistanceStrategy())  # 0
    # Second strategy is a hack to be able to recover the edge id
    director.addStrategy(SaveFidStrategy())  # 1
    builder = QgsGraphBuilder(crs)

    ## spatial index

    poi_sidx = QgsSpatialIndex(poi_layer)
    work_sidx = QgsSpatialIndex(work_layer)
    school_sidx = QgsSpatialIndex(school_layer)

    ## prepare points
    orig_n = len(origin_layer)
    poi_n = len(poi_layer)
    work_n = len(work_layer) if work_layer else 0
    school_n = len(school_layer) if school_layer else 0
    dest_n = poi_n + work_n + school_n

    orig_points = [None] * orig_n
    orig_sizes = np.zeros(orig_n, dtype=float)
    if socio_data:
        orig_socio = np.zeros(orig_n, dtype=float)
    dest_points = [None] * dest_n
    dest_sizes = [None] * dest_n
    dest_fids = [None] * dest_n
    dest_cats = [None] * dest_n

    orig_id_field = 'deso'
    for i, feat in enumerate(origin_layer.getFeatures()):
        orig_points[i] = feat.geometry().asPoint()
        orig_sizes[i] = feat[size_field] * (
            feat[origin_weight_field] if origin_weight_field else 1
        )

        if socio_data:
            orig_socio[i] = socio_data[
                feat[orig_id_field]
            ]  # FIXME: check if all origins have data

    if socio_data:
        orig_socio = orig_socio / np.mean(orig_socio)
        orig_sizes *= orig_socio

    for i, feat in enumerate(poi_layer.getFeatures()):
        dest_points[i] = feat.geometry().asPoint()
        dest_fids[i] = feat.id()
        dest_sizes[i] = 1  # TODO: dest size
        dest_cats[i] = poi_class_map.get(feat[class_field])

    if work_layer:
        for i, feat in enumerate(work_layer.getFeatures(), start=poi_n):
            dest_points[i] = feat.geometry().asPoint()
            dest_fids[i] = feat.id()
            dest_sizes[i] = feat[work_size_field]  # TODO: dest size
            dest_cats[i] = 'work'

    if school_layer:
        for i, feat in enumerate(school_layer.getFeatures(), start=(poi_n + work_n)):
            dest_points[i] = feat.geometry().asPoint()
            dest_fids[i] = feat.id()
            dest_sizes[i] = feat[school_size_field]  # TODO: dest size
            dest_cats[i] = 'school'

    # points = [origin.point for origin in origins_data] + [
    #    dest.point for dest in dests_data
    # ]

    if feedback is None:
        feedback = QgsProcessingFeedback()

        def progress(p):
            if int(10 * p % 100) == 0:
                print(f'{int(p):#3d}%')

        feedback.progressChanged.connect(progress)

    with timing('build network graph'):
        tied_points = director.makeGraph(
            builder, orig_points + dest_points, feedback=feedback
        )
        graph = builder.graph()

    orig_tied_points = tied_points[:orig_n]
    dest_tied_points = tied_points[orig_n:]

    poi_tied_points = dest_tied_points[:poi_n]
    work_tied_points = dest_tied_points[poi_n : poi_n + work_n]
    school_tied_points = dest_tied_points[poi_n + work_n :]

    dest_fid_to_tied_points = dict(zip(dest_fids, enumerate(dest_tied_points)))

    poi_fid_to_tied_points = dict(zip(dest_fids[:poi_n], enumerate(poi_tied_points)))
    work_fid_to_tied_points = dict(
        zip(dest_fids[poi_n : poi_n + work_n], enumerate(work_tied_points, start=poi_n))
    )
    school_fid_to_tied_points = dict(
        zip(
            dest_fids[poi_n + work_n :],
            enumerate(school_tied_points, start=poi_n + work_n),
        )
    )

    orig_dests = [None] * orig_n
    for i, point in enumerate(orig_points):
        orig_dests[i] = (
            [
                poi_fid_to_tied_points[fid]
                for fid in poi_sidx.nearestNeighbor(
                    point, neighbors=MAX_NEIGHBORS, maxDistance=max_distance
                )
            ]
            + [
                work_fid_to_tied_points[fid]
                for fid in work_sidx.nearestNeighbor(
                    point, neighbors=MAX_NEIGHBORS, maxDistance=max_distance
                )
            ]
            + [
                school_fid_to_tied_points[fid]
                for fid in school_sidx.nearestNeighbor(
                    point, neighbors=MAX_NEIGHBORS, maxDistance=max_distance
                )
            ]
        )

    step = 100.0 / orig_n
    time_dijkstra = 0.0
    time_find = 0.0
    time_route = 0.0
    with timing('calculate connecting routes'):
        routes = []
        # for i, (origin_fid, dest_fids) in enumerate(od_data):
        for i, (orig_point, dests) in enumerate(zip(orig_tied_points, orig_dests)):
            origin_vertex_id = graph.findVertex(orig_point)

            # Calculate the tree and cost using the distance strategy (#0)
            ts = time()
            (tree, cost) = QgsGraphAnalyzer.dijkstra(graph, origin_vertex_id, 0)
            time_dijkstra += time() - ts

            for j, dest_point in dests:
                if feedback.isCanceled():
                    return
                if dest_sizes[j] <= 0:
                    continue
                category = dest_cats[j]
                if category is None:
                    continue
                ts = time()
                dest_vertex_id = graph.findVertex(dest_point)
                time_find += time() - ts
                if tree[dest_vertex_id] != -1 and (
                    cost[dest_vertex_id] <= MAX_DISTANCE_M
                    or MAX_DISTANCE_M <= 0  # TODO: enable skipping max distance
                ):
                    route_distance = cost[dest_vertex_id]
                    # route_points = [graph.vertex(dest_vertex_id).point()]
                    cur_vertex_id = dest_vertex_id
                    route_fids = []
                    # Iterate the graph from dest to origin saving the edges
                    ts = time()
                    while cur_vertex_id != origin_vertex_id:
                        cur_edge = graph.edge(tree[cur_vertex_id])
                        # Here we recover the edge id through strategy #1
                        route_fids.append(cur_edge.cost(1))
                        cur_vertex_id = cur_edge.fromVertex()
                        # route_points.append(graph.vertex(cur_vertex_id).point())
                    time_route += time() - ts

                    # route_points.reverse()
                    # route_geom = QgsGeometry.fromPolylineXY(route_points))

                    # Hack to remove duplicate fids
                    route_fids = list(
                        dict.fromkeys(route_fids)
                    )  # NOTE: requires python >= 3.7 for ordered dict FIXME: add python version check
                    route_fids.reverse()

                    # Calc
                    # TODO: Move to matrix and vectorize calculation using numpy
                    gravity_value = poi_gravity_values[category]
                    bike_params = mode_params_bike[category]
                    ebike_params = mode_params_ebike[category]

                    # NOTE: we include dest size in decay here
                    decay = dest_sizes[j] * math.exp(
                        gravity_value * route_distance / 1000.0
                    )
                    p_bike = sigmoid(*bike_params, route_distance)
                    p_ebike = sigmoid(*ebike_params, route_distance)

                    # TODO: use namedtuple or dataclass
                    routes.append(
                        Route(
                            i,
                            j,
                            category,
                            route_distance,
                            decay,
                            p_bike,
                            p_ebike,
                            route_fids,
                        )
                    )
            feedback.setProgress(i * step)

        print(f'dijkstra took: {time_dijkstra:#1.2f} sec')
        print(f'find vertex took: {time_find:#1.2f} sec')
        print(f'route took: {time_route:#1.2f} sec')

    with timing('post process routes'):
        alpha_bike = 0.8
        alpha_ebke = 0.2

        decay_sums = {cat: defaultdict(float) for cat in poi_categories}
        bike_values = {cat: defaultdict(float) for cat in poi_categories}
        ebike_values = {cat: defaultdict(float) for cat in poi_categories}

        for route in routes:
            # NOTE: dest size is included in decay
            decay_sums[route.cat][route.i] += route.decay
        for route in routes:
            decay_sum = decay_sums[route.cat][route.i]
            # TODO: add T_p and alpha_m
            T_p = trip_generation[route.cat]
            bike_value = (
                T_p
                * alpha_bike
                * orig_sizes[route.i]
                * route.p_bike
                * route.decay
                / decay_sum
            )
            ebike_value = (
                T_p
                * alpha_ebke
                * orig_sizes[route.i]
                * route.p_ebike
                * route.decay
                / decay_sum
            )
            for fid in route.net_fids:
                bike_values[route.cat][fid] += float(bike_value)
                ebike_values[route.cat][fid] += float(ebike_value)

    # FIXME: Un-kludge this
    with timing('create result features'):
        fields = get_fields()

        segments = []
        for feature in network_layer.getFeatures():
            fid = feature.id()
            segment = QgsFeature(fields)
            segment.setGeometry(QgsGeometry(feature.geometry()))

            segment['network_fid'] = fid
            flow = 0.0
            for cat in poi_categories:
                bike_field = f'{cat}_bike_value'
                ebike_field = f'{cat}_ebike_value'

                flow_bike = segment[bike_field] = bike_values[cat].get(fid)
                flow_ebke = segment[ebike_field] = ebike_values[cat].get(fid)

                if flow_bike is not None:
                    flow += flow_bike
                if flow_ebke is not None:
                    flow += flow_ebke

            segment['flow'] = flow
            segment['lts'] = feature['lts']
            segment['vgu'] = feature['vgu']
            segment['R'] = flow * feature['ratio']
            segments.append(segment)

    if not return_layer:
        if return_raw:
            return segments, bike_values, ebike_values
        return segments

    with timing('create result layer'):
        output_layer = QgsVectorLayer(
            f'LineString?crs={crs.toWkt()}', 'segments', 'memory'
        )
        with edit(output_layer):
            for field in fields:
                output_layer.addAttribute(field)
            output_layer.addFeatures(segments, flags=QgsFeatureSink.FastInsert)

    return output_layer
Exemple #25
0
    def regularMatrix(self, parameters, context, source, inField,
                      target_source, targetField, nPoints, feedback):

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

        inIdx = source.fields().lookupField(inField)
        targetIdx = target_source.fields().lookupField(targetField)

        index = QgsSpatialIndex(
            target_source.getFeatures(
                QgsFeatureRequest().setSubsetOfAttributes(
                    []).setDestinationCrs(source.sourceCrs(),
                                          context.transformContext())),
            feedback)

        first = True
        sink = None
        dest_id = None
        features = source.getFeatures(
            QgsFeatureRequest().setSubsetOfAttributes([inIdx]))
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        for current, inFeat in enumerate(features):
            if feedback.isCanceled():
                break

            inGeom = inFeat.geometry()
            if first:
                featList = index.nearestNeighbor(inGeom.asPoint(), nPoints)
                first = False
                fields = QgsFields()
                input_id_field = source.fields()[inIdx]
                input_id_field.setName('ID')
                fields.append(input_id_field)
                for f in target_source.getFeatures(
                        QgsFeatureRequest().setFilterFids(
                            featList).setSubsetOfAttributes([
                                targetIdx
                            ]).setDestinationCrs(source.sourceCrs(),
                                                 context.transformContext())):
                    fields.append(
                        QgsField(str(f[targetField]), QVariant.Double))

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

            data = [inFeat[inField]]
            for target in target_source.getFeatures(
                    QgsFeatureRequest().setSubsetOfAttributes(
                        []).setFilterFids(featList).setDestinationCrs(
                            source.sourceCrs(), context.transformContext())):
                if feedback.isCanceled():
                    break
                outGeom = target.geometry()
                dist = distArea.measureLine(inGeom.asPoint(),
                                            outGeom.asPoint())
                data.append(dist)

            out_feature = QgsFeature()
            out_feature.setGeometry(inGeom)
            out_feature.setAttributes(data)
            sink.addFeature(out_feature, QgsFeatureSink.FastInsert)
            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
Exemple #26
0
    def processAlgorithm(self, parameters, context, feedback):
        # Get Parameters and assign to variable to work with
        layer = self.parameterAsLayer(parameters, self.SOURCE_LYR, context)
        idfield = self.parameterAsString(parameters, self.SOURCE_FIELD,
                                         context)
        idfield_index = layer.fields().indexFromName(
            idfield)  # get the fieldindex of the id field
        idfield_type = layer.fields()[idfield_index].type(
        )  # get the fieldtype of this field
        attrfield = self.parameterAsString(parameters, self.ATTRIBUTE_FIELD,
                                           context)
        attrfield_index = layer.fields().indexFromName(
            attrfield)  # get the fieldindex of the attribute field
        attrfield_type = layer.fields()[attrfield_index].type(
        )  # get the fieldtype of this field
        maxneighbors = self.parameterAsDouble(parameters, self.MAX_NEIGHBORS,
                                              context)
        maxdistance = self.parameterAsDouble(parameters, self.MAX_DISTANCE,
                                             context)
        donotcomparevalue = self.parameterAsDouble(parameters,
                                                   self.DONOT_COMPARE_VALUE,
                                                   context)
        donotcomparebool = self.parameterAsBool(parameters,
                                                self.DONOT_COMPARE_BOOL,
                                                context)
        op = self.parameterAsString(parameters, self.OPERATOR, context)
        op = int(op[0])  # get the index of the chosen operator
        #import operator
        ops = {  # get the operator by this index
            0: operator.lt,
            1: operator.le,
            2: operator.eq,
            3: operator.ne,
            4: operator.ge,
            5: operator.gt
        }
        op_func = ops[op]  # create the operator function

        total = 100.0 / layer.featureCount() if layer.featureCount(
        ) else 0  # Initialize progress for progressbar

        # if -1 has been chosen for maximum features to compare, use the amount of features of the layer, else use the given input
        if maxneighbors == -1:
            maxneighbors = layer.featureCount()

        fields = layer.fields()  # get all fields of the inputlayer
        fields.append(QgsField(
            "near_id",
            idfield_type))  # create new field with same type as the inputfield
        fields.append(QgsField(
            "near_attr", attrfield_type))  # same here for the attribute field
        fields.append(QgsField("near_dist", QVariant.Double, len=20,
                               prec=5))  # add a new field of type double

        idx = QgsSpatialIndex(layer.getFeatures())  # create a spatial index

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

        for current, feat in enumerate(
                layer.getFeatures()):  # iterate over source
            new_feat = QgsFeature(fields)  # copy source fields + appended
            attridx = 0  # reset attribute fieldindex
            for attr in feat.attributes(
            ):  # iterate over attributes of source layer for the current feature
                new_feat[
                    attridx] = attr  # copy attribute values over to the new layer
                attridx += 1  # go to the next field
            new_feat.setGeometry(feat.geometry(
            ))  # copy over the geometry of the source feature
            if (
                (not (op_func(feat[attrfield], donotcomparevalue)))
                    or (not donotcomparebool)
            ):  # only search for matches if not beeing told to not do to so
                nearestneighbors = idx.nearestNeighbor(
                    feat.geometry(),
                    neighbors=maxneighbors,
                    maxDistance=maxdistance
                )  # get the featureids of the maximum specified number of near neighbors within a maximum distance
                try:
                    nearestneighbors.remove(
                        feat.id()
                    )  # remove the current feature from this list (otherwise the nearest feature by == operator would always be itself...)
                except:
                    pass  # ignore on error
                for near in nearestneighbors:  # for each feature iterate over the nearest ones (the index is already sorted by distance, so the first match will be the nearest match)
                    if op_func(
                            layer.getFeature(near)[attrfield], feat[attrfield]
                    ):  # if the current nearest attribute is (chosen operator here) than the current feature ones, then
                        new_feat['near_id'] = layer.getFeature(
                            near
                        )[idfield]  # get the near matchs's id value and fill the current feature with its value
                        new_feat['near_attr'] = layer.getFeature(
                            near
                        )[attrfield]  # also get the attribute value of this near feature
                        new_feat['near_dist'] = feat.geometry().distance(
                            layer.getFeature(near).geometry()
                        )  # and finally calculate the distance between the current feature and the nearest matching feature
                        break  # break the for loop of near features and continue with the next feat
            else:  # do not search for near neighbor matches if given value is (operator here) than x
                pass  # do nothing and continue adding the feature

            sink.addFeature(
                new_feat,
                QgsFeatureSink.FastInsert)  # add feature to the output
            feedback.setProgress(int(current *
                                     total))  # Set Progress in Progressbar

            if feedback.isCanceled():  # Cancel algorithm if button is pressed
                break

        return {self.OUTPUT: dest_id}  # Return result of algorithm
def spaced(bar,buildings_layer_path,receiver_points_layer_path,spaced_pts_distance):
    
    distance_from_facades = 0.1

    buildings_layer_name = os.path.splitext(os.path.basename(buildings_layer_path))[0]
    buildings_layer = QgsVectorLayer(buildings_layer_path,buildings_layer_name,"ogr")
    
    
    # cp building layer to delete all fields
    buildings_memory_layer = QgsVectorLayer("Polygon?crs=" + str(buildings_layer.crs().authid()), "polygon_memory_layer", "memory")
    buildings_memory_layer.dataProvider().addAttributes([])
    
    buildings_feat_all = buildings_layer.dataProvider().getFeatures()    
    buildings_feat_list = []
    for buildings_feat in buildings_feat_all:
        buildings_feat_list.append(buildings_feat)

    buildings_memory_layer.dataProvider().addFeatures(buildings_feat_list)   
    buildings_memory_layer.updateExtents()

    # this is crazy: I had to addd this line otherwise the first processing doesn't work...
    QgsProject.instance().addMapLayers([buildings_memory_layer])
    
    bar.setValue(1)

    # this processing alg has as output['OUTPUT'] the layer
    output = processing.run("native:buffer", {'INPUT': buildings_memory_layer,
                                             'DISTANCE': distance_from_facades,
                                             'DISSOLVE': False,
                                             'OUTPUT': 'memory:'})

    # I can now remove the layer from map...
    QgsProject.instance().removeMapLayers( [buildings_memory_layer.id()] )

    bar.setValue(25)

    # this processing alg has as output['OUTPUT'] the layer
    output = processing.run("qgis:polygonstolines", {'INPUT': output['OUTPUT'],
                                                     'OUTPUT': 'memory:'})
    bar.setValue(50)    

    # this processing alg has as output['output'] the layer path...
    poly_to_lines = output['OUTPUT']
    output = processing.run("qgis:pointsalonglines", {'INPUT': poly_to_lines,
                                                      'DISTANCE': spaced_pts_distance,
                                                      'START_OFFSET': 0,
                                                      'END_OFFSET': 0,
                                                      'OUTPUT': 'memory:'})


    bar.setValue(75)

    receiver_points_memory_layer = output['OUTPUT']


    del output
    
    ## Delete pts in buildings
    # creates SpatialIndex
    buildings_feat_all = buildings_layer.dataProvider().getFeatures()    
    buildings_spIndex = QgsSpatialIndex()
    buildings_feat_all_dict = {}
    for buildings_feat in buildings_feat_all:
        buildings_spIndex.insertFeature(buildings_feat)
        buildings_feat_all_dict[buildings_feat.id()] = buildings_feat

    receiver_points_memory_layer_all = receiver_points_memory_layer.dataProvider().getFeatures()

    receiver_points_layer_fields = QgsFields()
    receiver_points_layer_fields.append(QgsField("id_pt", QVariant.Int))
    receiver_points_layer_fields.append(QgsField("id_bui", QVariant.Int))

    receiver_points_layer_writer = QgsVectorFileWriter(receiver_points_layer_path, "System",
                                                       receiver_points_layer_fields, QgsWkbTypes.Point,
                                                       buildings_layer.crs(), "ESRI Shapefile")

    receiver_points_feat_id = 0

    receiver_memory_feat_total = receiver_points_memory_layer.dataProvider().featureCount()
    receiver_memory_feat_number = 0

    for receiver_memory_feat in receiver_points_memory_layer_all:

        receiver_memory_feat_number = receiver_memory_feat_number + 1
        barValue = receiver_memory_feat_number/float(receiver_memory_feat_total)*25 + 75
        bar.setValue(barValue)

        rect = QgsRectangle()
        rect.setXMinimum(receiver_memory_feat.geometry().asPoint().x() - distance_from_facades)
        rect.setXMaximum(receiver_memory_feat.geometry().asPoint().x() + distance_from_facades)
        rect.setYMinimum(receiver_memory_feat.geometry().asPoint().y() - distance_from_facades)
        rect.setYMaximum(receiver_memory_feat.geometry().asPoint().y() + distance_from_facades)
        buildings_selection = buildings_spIndex.intersects(rect)

        to_add = True

        receiver_geom = receiver_memory_feat.geometry()
        building_id_correct = None

        for buildings_id in buildings_selection:
            building_geom = buildings_feat_all_dict[buildings_id].geometry()
            intersectBuilding = QgsGeometry.intersects(receiver_geom, building_geom)
            building_id_correct = buildings_id
            if intersectBuilding:
                to_add = False
                building_id_correct = None
                break

        # picking the nearest building to the receiver point analysed
        nearestIds = buildings_spIndex.nearestNeighbor(receiver_geom.asPoint(), 1)
        building_fid = []
        for featureId in nearestIds:
            request = QgsFeatureRequest().setFilterFid(featureId)
            for feature in buildings_layer.getFeatures(request):
                dist = receiver_geom.distance(feature.geometry())
                building_fid.append((dist, feature.id()))
        building_fid_correct = min(building_fid, key=lambda x: x[0])[-1]



        if to_add:
            attributes = [receiver_points_feat_id, building_fid_correct]
            fet = QgsFeature()
            fet.setGeometry(receiver_memory_feat.geometry())
            fet.setAttributes(attributes)
            receiver_points_layer_writer.addFeature(fet)
            receiver_points_feat_id = receiver_points_feat_id + 1


    del receiver_points_layer_writer
    
    receiver_points_layer_name = os.path.splitext(os.path.basename(receiver_points_layer_path))[0]
    receiver_points_layer = QgsVectorLayer(receiver_points_layer_path, str(receiver_points_layer_name), "ogr")

    QgsProject.instance().addMapLayers([receiver_points_layer])

    QgsProject.instance().reloadAllLayers()
Exemple #28
0
def spaced(bar, buildings_layer_path, receiver_points_layer_path,
           spaced_pts_distance):

    distance_from_facades = 0.1

    buildings_layer_name = os.path.splitext(
        os.path.basename(buildings_layer_path))[0]
    buildings_layer = QgsVectorLayer(buildings_layer_path,
                                     buildings_layer_name, "ogr")

    # cp building layer to delete all fields
    buildings_memory_layer = QgsVectorLayer(
        "Polygon?crs=" + str(buildings_layer.crs().authid()),
        "polygon_memory_layer", "memory")
    buildings_memory_layer.dataProvider().addAttributes([])

    buildings_feat_all = buildings_layer.dataProvider().getFeatures()
    buildings_feat_list = []
    for buildings_feat in buildings_feat_all:
        buildings_feat_list.append(buildings_feat)

    buildings_memory_layer.dataProvider().addFeatures(buildings_feat_list)
    buildings_memory_layer.updateExtents()

    # this is crazy: I had to addd this line otherwise the first processing doesn't work...
    QgsProject.instance().addMapLayers([buildings_memory_layer])

    bar.setValue(1)

    # this processing alg has as output['OUTPUT'] the layer
    output = processing.run(
        "native:buffer", {
            'INPUT': buildings_memory_layer,
            'DISTANCE': distance_from_facades,
            'DISSOLVE': False,
            'OUTPUT': 'memory:'
        })

    # I can now remove the layer from map...
    QgsProject.instance().removeMapLayers([buildings_memory_layer.id()])

    bar.setValue(25)

    # this processing alg has as output['OUTPUT'] the layer
    output = processing.run("qgis:polygonstolines", {
        'INPUT': output['OUTPUT'],
        'OUTPUT': 'memory:'
    })
    bar.setValue(50)

    # this processing alg has as output['output'] the layer path...
    poly_to_lines = output['OUTPUT']
    output = processing.run(
        "qgis:pointsalonglines", {
            'INPUT': poly_to_lines,
            'DISTANCE': spaced_pts_distance,
            'START_OFFSET': 0,
            'END_OFFSET': 0,
            'OUTPUT': 'memory:'
        })

    bar.setValue(75)

    receiver_points_memory_layer = output['OUTPUT']

    del output

    ## Delete pts in buildings
    # creates SpatialIndex
    buildings_feat_all = buildings_layer.dataProvider().getFeatures()
    buildings_spIndex = QgsSpatialIndex()
    buildings_feat_all_dict = {}
    for buildings_feat in buildings_feat_all:
        buildings_spIndex.insertFeature(buildings_feat)
        buildings_feat_all_dict[buildings_feat.id()] = buildings_feat

    receiver_points_memory_layer_all = receiver_points_memory_layer.dataProvider(
    ).getFeatures()

    receiver_points_layer_fields = QgsFields()
    receiver_points_layer_fields.append(QgsField("id_pt", QVariant.Int))
    receiver_points_layer_fields.append(QgsField("id_bui", QVariant.Int))

    receiver_points_layer_writer = QgsVectorFileWriter(
        receiver_points_layer_path, "System", receiver_points_layer_fields,
        QgsWkbTypes.Point, buildings_layer.crs(), "ESRI Shapefile")

    receiver_points_feat_id = 0

    receiver_memory_feat_total = receiver_points_memory_layer.dataProvider(
    ).featureCount()
    receiver_memory_feat_number = 0

    for receiver_memory_feat in receiver_points_memory_layer_all:

        receiver_memory_feat_number = receiver_memory_feat_number + 1
        barValue = receiver_memory_feat_number / float(
            receiver_memory_feat_total) * 25 + 75
        bar.setValue(barValue)

        rect = QgsRectangle()
        rect.setXMinimum(receiver_memory_feat.geometry().asPoint().x() -
                         distance_from_facades)
        rect.setXMaximum(receiver_memory_feat.geometry().asPoint().x() +
                         distance_from_facades)
        rect.setYMinimum(receiver_memory_feat.geometry().asPoint().y() -
                         distance_from_facades)
        rect.setYMaximum(receiver_memory_feat.geometry().asPoint().y() +
                         distance_from_facades)
        buildings_selection = buildings_spIndex.intersects(rect)

        to_add = True

        receiver_geom = receiver_memory_feat.geometry()
        building_id_correct = None

        for buildings_id in buildings_selection:
            building_geom = buildings_feat_all_dict[buildings_id].geometry()
            intersectBuilding = QgsGeometry.intersects(receiver_geom,
                                                       building_geom)
            building_id_correct = buildings_id
            if intersectBuilding:
                to_add = False
                building_id_correct = None
                break

        # picking the nearest building to the receiver point analysed
        nearestIds = buildings_spIndex.nearestNeighbor(receiver_geom.asPoint(),
                                                       1)
        building_fid = []
        for featureId in nearestIds:
            request = QgsFeatureRequest().setFilterFid(featureId)
            for feature in buildings_layer.getFeatures(request):
                dist = receiver_geom.distance(feature.geometry())
                building_fid.append((dist, feature.id()))
        building_fid_correct = min(building_fid, key=lambda x: x[0])[-1]

        if to_add:
            attributes = [receiver_points_feat_id, building_fid_correct]
            fet = QgsFeature()
            fet.setGeometry(receiver_memory_feat.geometry())
            fet.setAttributes(attributes)
            receiver_points_layer_writer.addFeature(fet)
            receiver_points_feat_id = receiver_points_feat_id + 1

    del receiver_points_layer_writer

    receiver_points_layer_name = os.path.splitext(
        os.path.basename(receiver_points_layer_path))[0]
    receiver_points_layer = QgsVectorLayer(receiver_points_layer_path,
                                           str(receiver_points_layer_name),
                                           "ogr")

    QgsProject.instance().addMapLayers([receiver_points_layer])

    QgsProject.instance().reloadAllLayers()
Exemple #29
0
class FindPoints(QgsTask):
    def __init__(self, qpipelines, description='FindHds', debug=False):
        super().__init__(description, QgsTask.CanCancel)

        self.debug = debug

        if self.debug:
            self.hds_feature = QgsVectorLayer(
                'C:/Users/jeferson.machado/Desktop/QGIS/shapes/hds_tracing.shp',
                "hds_tracing", "ogr")
        else:
            self.hds_feature = QgsProject.instance().mapLayersByName(
                'hds_tracing')[0]
        self.idx_hds = QgsSpatialIndex(
            self.hds_feature.getFeatures(),
            flags=QgsSpatialIndex.FlagStoreFeatureGeometries)

        self.__exception = None
        self.q_list_pipelines = qpipelines
        self.list_hds = []

    def find_hds_by_nearest_neighbor(self, points_vertex):
        hds_nearest = self.idx_hds.nearestNeighbor(
            point=QgsPointXY(points_vertex), neighbors=10, maxDistance=25)

        if len(hds_nearest) > 0:
            for hd in hds_nearest:
                if hd not in self.list_hds:
                    self.list_hds.append(hd)

    def split_line(self, p1, p2, pos, pipeline):
        geo = QgsGeometry.fromPolyline([p1, p2])
        center = geo.centroid()
        pipeline.insert(pos, center.asPoint())

    def get_points(self, pipeline):
        pipe = [
            QgsPointXY(pipeline.vertexAt(i))
            for i in range(pipeline.get().childCount())
        ]
        distances = []
        breakForce = 0
        while True:
            if breakForce == 1000:
                break
            # check isCanceled() to handle cancellation
            if self.isCanceled():
                return False
            increment = 0
            pipeline = QgsGeometry.fromMultiPointXY(pipe)

            for i in range(len(pipe) - 1):
                p1 = pipeline.vertexAt(i)
                p2 = pipeline.vertexAt(i + 1)
                d = QgsDistanceArea()
                distance = d.measureLine(QgsPointXY(p1), QgsPointXY(p2))
                distances.append(distance)
                if distance > 10:
                    self.split_line(p1, p2, i + 1 + increment, pipe)
                    increment += 1

            breakForce += 1
            if distances:
                if max(distances) <= 10:
                    break
            distances.clear()
        return pipeline

    def run(self):

        try:
            while len(self.q_list_pipelines) > 0:
                pipeline = self.q_list_pipelines.pop(0)
                pipeline_geo = self.get_points(pipeline.geometry())

                for i in range(0, len(pipeline_geo.get()) - 1):
                    self.find_hds_by_nearest_neighbor(pipeline_geo.vertexAt(i))

        except Exception as e:
            self.__exception = e
            return False

        return True

    def finished(self, result):
        if result:
            self.hds_feature.selectByIds(self.list_hds)

            QgsMessageLog.logMessage(
                f"Task {self.description()} has been executed correctly\n"
                f"HDS: {self.list_hds}",
                level=Qgis.Success)
        else:
            if self.__exception is None:
                QgsMessageLog.logMessage(
                    f"Tracing {self.description()} not successful "
                    f"but without exception "
                    f"(probably the task was manually canceled by the user)",
                    level=Qgis.Warning)
            else:
                QgsMessageLog.logMessage(
                    f"Task {self.description()}"
                    f"Exception: {self.__exception}",
                    level=Qgis.Critical)
                raise self.__exception

    def cancel(self):
        QgsMessageLog.logMessage(
            f'TracingTrask {self.description()} was canceled', level=Qgis.Info)
        super().cancel()
class Worker(QtCore.QObject):
    '''The worker that does the heavy lifting.
    /* QGIS offers spatial indexes to make spatial search more
     * effective.  QgsSpatialIndex will find the nearest index
     * (approximate) geometry (rectangle) for a supplied point.
     * QgsSpatialIndex will only give correct results when searching
     * for the nearest neighbour of a point in a point data set.
     * So something has to be done for non-point data sets
     *
     * Non-point join data set:
     * A two pass search is performed.  First the index is used to
     * find the nearest index geometry (approximation - rectangle),
     * and then compute the distance to the actual indexed geometry.
     * A rectangle is constructed from this (maximum minimum)
     * distance, and this rectangle is used to find all features in
     * the join data set that may be the closest feature to the given
     * point.
     * For all the features is this candidate set, the actual
     * distance to the given point is calculated, and the nearest
     * feature is returned.
     *
     * Non-point input data set:
     * First the centroid of the non-point input geometry is
     * calculated.  Then the index is used to find the nearest
     * neighbour to this point (using the approximate index
     * geometry).
     * The distance vector to this feature, combined with the
     * bounding rectangle of the input feature is used to create a
     * search rectangle to find the candidate join geometries.
     * For all the features is this candidate set, the actual
     * distance to the given feature is calculated, and the nearest
     * feature is returned.
     *
     * Joins involving multi-geometry datasets are not supported
     * by a spatial index.
     *
    */
    '''
    # Define the signals used to communicate back to the application
    progress = QtCore.pyqtSignal(float)  # For reporting progress
    status = QtCore.pyqtSignal(str)      # For reporting status
    error = QtCore.pyqtSignal(str)       # For reporting errors
    # Signal for sending over the result:
    finished = QtCore.pyqtSignal(bool, object)

    def __init__(self, inputvectorlayer, joinvectorlayer,
                 outputlayername, joinprefix,
                 distancefieldname="distance",
                 approximateinputgeom=False,
                 usejoinlayerapproximation=False,
                 usejoinlayerindex=True,
                 selectedinputonly=True,
                 selectedjoinonly=True,
                 excludecontaining=True):
        """Initialise.

        Arguments:
        inputvectorlayer -- (QgsVectorLayer) The base vector layer
                            for the join
        joinvectorlayer -- (QgsVectorLayer) the join layer
        outputlayername -- (string) the name of the output memory
                           layer
        joinprefix -- (string) the prefix to use for the join layer
                      attributes in the output layer
        distancefieldname -- name of the (new) field where neighbour
                             distance is stored
        approximateinputgeom -- (boolean) should the input geometry
                                be approximated?  Is only be set for
                                non-single-point layers
        usejoinlayerindexapproximation -- (boolean) should the index
                             geometry approximations be used for the
                             join?
        usejoinlayerindex -- (boolean) should an index for the join
                             layer be used.
        selectedinputonly -- Only selected features from the input
                             layer
        selectedjoinonly -- Only selected features from the join
                            layer
        excludecontaining -- exclude the containing polygon for points
        """

        QtCore.QObject.__init__(self)  # Essential!
        # Set a variable to control the use of indexes and exact
        # geometries for non-point input geometries
        self.nonpointexactindex = usejoinlayerindex
        # Creating instance variables from the parameters
        self.inpvl = inputvectorlayer
        self.joinvl = joinvectorlayer
        self.outputlayername = outputlayername
        self.joinprefix = joinprefix
        self.approximateinputgeom = approximateinputgeom
        self.usejoinlayerapprox = usejoinlayerapproximation
        self.selectedinonly = selectedinputonly
        self.selectedjoonly = selectedjoinonly
        self.excludecontaining = excludecontaining

        # Check if the layers are the same (self join)
        self.selfjoin = False
        if self.inpvl is self.joinvl:
            # This is a self join
            self.selfjoin = True
        # The name of the attribute for the calculated distance
        self.distancename = distancefieldname
        # Creating instance variables for the progress bar ++
        # Number of elements that have been processed - updated by
        # calculate_progress
        self.processed = 0
        # Current percentage of progress - updated by
        # calculate_progress
        self.percentage = 0
        # Flag set by kill(), checked in the loop
        self.abort = False
        # Number of features in the input layer - used by
        # calculate_progress (set when needed)
        self.feature_count = 1
        # The number of elements that is needed to increment the
        # progressbar (set when needed)
        self.increment = 0

    def run(self):
        try:
            # Check if the layers look OK
            if self.inpvl is None or self.joinvl is None:
                self.status.emit('Layer is missing!')
                self.finished.emit(False, None)
                return
            # Check if there are features in the layers
            incount = 0
            if self.selectedinonly:
                incount = self.inpvl.selectedFeatureCount()
            else:
                incount = self.inpvl.featureCount()
            if incount == 0:
                self.status.emit('Input layer has no features!')
                self.finished.emit(False, None)
                return
            joincount = 0
            if self.selectedjoonly:
                joincount = self.joinvl.selectedFeatureCount()
            else:
                joincount = self.joinvl.featureCount()
            if joincount == 0:
                self.status.emit('Join layer has no features!')
                self.finished.emit(False, None)
                return
            # Get the wkbtype of the layers
            self.inpWkbType = self.inpvl.wkbType()
            self.joinWkbType = self.joinvl.wkbType()

            # Check if the input layer does not have geometries
            if (self.inpvl.geometryType() == QgsWkbTypes.NullGeometry):
                self.status.emit('No geometries in the input layer!')
                self.finished.emit(False, None)
                return
            # Check if the join layer does not have geometries
            if (self.joinvl.geometryType() == QgsWkbTypes.NullGeometry):
                self.status.emit('No geometries in the join layer!')
                self.finished.emit(False, None)
                return
            # Set the geometry type and prepare the output layer
            inpWkbTypetext = QgsWkbTypes.displayString(int(self.inpWkbType))
            # self.inputmulti = QgsWkbTypes.isMultiType(self.inpWkbType)
            # self.status.emit('wkbtype: ' + inpWkbTypetext)
            # geometryType = self.inpvl.geometryType()
            # geometrytypetext = 'Point'
            # if geometryType == QgsWkbTypes.PointGeometry:
            #     geometrytypetext = 'Point'
            # elif geometryType == QgsWkbTypes.LineGeometry:
            #     geometrytypetext = 'LineString'
            # elif geometryType == QgsWkbTypes.PolygonGeometry:
            #     geometrytypetext = 'Polygon'
            # if self.inputmulti:
            #     geometrytypetext = 'Multi' + geometrytypetext
            # geomttext = geometrytypetext

            geomttext = inpWkbTypetext
            # Set the coordinate reference system to the input
            # layer's CRS using authid (proj4 may be more robust)
            if self.inpvl.crs() is not None:
                geomttext = (geomttext + "?crs=" +
                             str(self.inpvl.crs().authid()))
            # Retrieve the fields from the input layer
            outfields = self.inpvl.fields().toList()
            # Retrieve the fields from the join layer
            if self.joinvl.fields() is not None:
                jfields = self.joinvl.fields().toList()
                for joinfield in jfields:
                    outfields.append(QgsField(self.joinprefix +
                                     str(joinfield.name()),
                                     joinfield.type()))
            else:
                self.status.emit('Unable to get any join layer fields')
            # Add the nearest neighbour distance field
            # Check if there is already a "distance" field
            # (should be avoided in the user interface)
            # Try a new name if there is a collission
            collission = True
            trynumber = 1
            distnameorg = self.distancename
            while collission:   # Iterate until there are no collissions
                collission = False
                for field in outfields:
                    # This check should not be necessary - handled in the UI
                    if field.name() == self.distancename:
                        self.status.emit(
                              'Distance field already exists - renaming!')
                        # self.abort = True
                        # self.finished.emit(False, None)
                        # break
                        collission = True
                        self.distancename = distnameorg + str(trynumber)
                        trynumber = trynumber + 1
            outfields.append(QgsField(self.distancename, QVariant.Double))
            # Create a memory layer using a CRS description
            self.mem_joinl = QgsVectorLayer(geomttext,
                                            self.outputlayername,
                                            "memory")
            # Set the CRS to the inputlayer's CRS
            self.mem_joinl.setCrs(self.inpvl.crs())
            self.mem_joinl.startEditing()
            # Add the fields
            for field in outfields:
                self.mem_joinl.dataProvider().addAttributes([field])
            # For an index to be used, the input layer has to be a
            # point layer, or the input layer geometries have to be
            # approximated to centroids, or the user has to have
            # accepted that a join layer index is used (for
            # non-point input layers).
            # (Could be extended to multipoint)
            if (self.inpWkbType == QgsWkbTypes.Point or
                    self.inpWkbType == QgsWkbTypes.Point25D or
                    self.approximateinputgeom or
                    self.nonpointexactindex):
                # Number of features in the join layer - used by
                # calculate_progress for the index creation
                if self.selectedjoonly:
                    self.feature_count = self.joinvl.selectedFeatureCount()
                else:
                    self.feature_count = self.joinvl.featureCount()
                # Create a spatial index to speed up joining
                self.status.emit('Creating join layer index...')
                # The number of elements that is needed to increment the
                # progressbar - set early in run()
                self.increment = self.feature_count // 1000
                self.joinlind = QgsSpatialIndex()
                # Include geometries to enable exact distance calculations
                # self.joinlind = QgsSpatialIndex(flags=[QgsSpatialIndex.FlagStoreFeatureGeometries])

                if self.selectedjoonly:
                    for feat in self.joinvl.getSelectedFeatures():
                        # Allow user abort
                        if self.abort is True:
                            break
                        self.joinlind.insertFeature(feat)
                        self.calculate_progress()
                else:
                    for feat in self.joinvl.getFeatures():
                        # Allow user abort
                        if self.abort is True:
                            break
                        self.joinlind.insertFeature(feat)
                        self.calculate_progress()
                self.status.emit('Join layer index created!')
                self.processed = 0
                self.percentage = 0
                # self.calculate_progress()

            # Is the join layer a multi-geometry layer?
            # self.joinmulti = QgsWkbTypes.isMultiType(self.joinWkbType)
            # Does the join layer contain multi geometries?
            # Try to check the first feature
            # This is not used for anything yet
            self.joinmulti = False
            if self.selectedjoonly:
                feats = self.joinvl.getSelectedFeatures()
            else:
                feats = self.joinvl.getFeatures()
            if feats is not None:
                testfeature = next(feats)
                feats.rewind()
                feats.close()
                if testfeature is not None:
                    if testfeature.hasGeometry():
                        if testfeature.geometry().isMultipart():
                            self.joinmulti = True
            # Prepare for the join by fetching the layers into memory
            # Add the input features to a list
            self.inputf = []
            if self.selectedinonly:
                for f in self.inpvl.getSelectedFeatures():
                    self.inputf.append(f)
            else:
                for f in self.inpvl.getFeatures():
                    self.inputf.append(f)
            # Add the join features to a list (used in the join)
            self.joinf = []
            if self.selectedjoonly:
                for f in self.joinvl.getSelectedFeatures():
                    self.joinf.append(f)
            else:
                for f in self.joinvl.getFeatures():
                    self.joinf.append(f)
            # Initialise the global variable that will contain the
            # result of the nearest neighbour spatial join (list of
            # features)
            self.features = []
            # Do the join!
            # Number of features in the input layer - used by
            # calculate_progress for the join operation
            if self.selectedinonly:
                self.feature_count = self.inpvl.selectedFeatureCount()
            else:
                self.feature_count = self.inpvl.featureCount()
            # The number of elements that is needed to increment the
            # progressbar - set early in run()
            self.increment = self.feature_count // 1000
            # Using the original features from the input layer
            for feat in self.inputf:
                # Allow user abort
                if self.abort is True:
                    break
                self.do_indexjoin(feat)
                self.calculate_progress()
            self.mem_joinl.dataProvider().addFeatures(self.features)
            self.status.emit('Join finished')
        except:
            import traceback
            self.error.emit(traceback.format_exc())
            self.finished.emit(False, None)
            if self.mem_joinl is not None:
                self.mem_joinl.rollBack()
        else:
            self.mem_joinl.commitChanges()
            if self.abort:
                self.finished.emit(False, None)
            else:
                self.status.emit('Delivering the memory layer...')
                self.finished.emit(True, self.mem_joinl)

    def calculate_progress(self):
        '''Update progress and emit a signal with the percentage'''
        self.processed = self.processed + 1
        # update the progress bar at certain increments
        if (self.increment == 0 or
                self.processed % self.increment == 0):
            # Calculate percentage as integer
            perc_new = (self.processed * 100) / self.feature_count
            if perc_new > self.percentage:
                self.percentage = perc_new
                self.progress.emit(self.percentage)

    def kill(self):
        '''Kill the thread by setting the abort flag'''
        self.abort = True

    def do_indexjoin(self, feat):
        '''Find the nearest neigbour of a feature.  Using an index,
           if possible

        Parameter: feat -- The feature for which a neighbour is
                           sought
        '''
        infeature = feat
        # Get the feature ID
        infeatureid = infeature.id()
        # self.status.emit('**infeatureid: ' + str(infeatureid))
        # Get the feature geometry
        inputgeom = infeature.geometry()
        # Check for missing input geometry
        if inputgeom.isEmpty():
            # Prepare the result feature
            atMapA = infeature.attributes()
            atMapB = []
            for thefield in self.joinvl.fields():
                atMapB.extend([None])
            attrs = []
            attrs.extend(atMapA)
            attrs.extend(atMapB)
            attrs.append(0 - float("inf"))
            # Create the feature
            outFeat = QgsFeature()
            # Use the original input layer geometry!:
            outFeat.setGeometry(infeature.geometry())
            # Use the modified input layer geometry (could be
            # centroid)
            # outFeat.setGeometry(inputgeom)
            # Add the attributes
            outFeat.setAttributes(attrs)
            # self.calculate_progress()
            self.features.append(outFeat)
            # self.mem_joinl.dataProvider().addFeatures([outFeat])
            self.status.emit("Warning: Input feature with "
                                         "missing geometry: " +
                                         str(infeature.id()))
            return
        # Shall approximate input geometries be used?
        if self.approximateinputgeom:
            # Use the centroid as the input geometry
            inputgeom = infeature.geometry().centroid()
        # Check if the coordinate systems are equal, if not,
        # transform the input feature!
        if (self.inpvl.crs() != self.joinvl.crs()):
            try:
                # inputgeom.transform(QgsCoordinateTransform(
                #     self.inpvl.crs(), self.joinvl.crs(), None))
                # transcontext = QgsCoordinateTransformContext()
                # inputgeom.transform(QgsCoordinateTransform(
                #     self.inpvl.crs(), self.joinvl.crs(), transcontext))
                inputgeom.transform(QgsCoordinateTransform(
                    self.inpvl.crs(), self.joinvl.crs(),
                    QgsProject.instance()))
            except:
                import traceback
                self.error.emit(self.tr('CRS Transformation error!') +
                                ' - ' + traceback.format_exc())
                self.abort = True
                return
        # Find the closest feature!
        nnfeature = None
        minfound = False
        mindist = float("inf")

        # If the input layer's geometry type is point, or has been
        # approximated to point (centroid), then a join index will
        # be used.
        # if ((QgsWkbTypes.geometryType(self.inpWkbType) == QgsWkbTypes.PointGeometry and
        #     not QgsWkbTypes.isMultiType(self.inpWkbType)) or self.approximateinputgeom):
        if (self.approximateinputgeom or
                self.inpWkbType == QgsWkbTypes.Point or
                self.inpWkbType == QgsWkbTypes.Point25D):
            # Are there points on the join side?
            # Then the index nearest neighbour function is sufficient
            # if ((QgsWkbTypes.geometryType(self.joinWkbType) == QgsWkbTypes.PointGeometry and
            #     not QgsWkbTypes.isMultiType(self.joinWkbType)) or self.usejoinlayerapprox):
            if (self.usejoinlayerapprox or
                    self.joinWkbType == QgsWkbTypes.Point or
                    self.joinWkbType == QgsWkbTypes.Point25D):
                # Is it a self join?
                if self.selfjoin:
                    # Have to consider the two nearest neighbours
                    nearestids = self.joinlind.nearestNeighbor(
                                             inputgeom.asPoint(), 2)
                    fch = 0  # Which of the two features to choose
                    if (nearestids[0] == infeatureid and
                                               len(nearestids) > 1):
                        # The first feature is the same as the input
                        # feature, so choose the second one
                        fch = 1
                    # Get the feature!
                    if False:
                    #if self.selectedjoonly:
                        # This caused problems (wrong results) in QGIS 3.0.1
                        nnfeature = next(
                            self.joinvl.getSelectedFeatures(
                                QgsFeatureRequest(nearestids[fch])))
                    else:
                        nnfeature = next(self.joinvl.getFeatures(
                            QgsFeatureRequest(nearestids[fch])))
                # Not a self join
                else:
                    # Not a self join, so we search for only the
                    # nearest neighbour (1)
                    nearestids = self.joinlind.nearestNeighbor(
                                           inputgeom.asPoint(), 1)
                    # Get the feature!
                    if len(nearestids) > 0:
                        nearestid = nearestids[0]
                        nnfeature = next(self.joinvl.getFeatures(
                                 QgsFeatureRequest(nearestid)))
                    #else:
                    #if self.selectedjoonly:
                    #    nnfeature = next(self.joinvl.getSelectedFeatures(
                    #             QgsFeatureRequest(nearestid)))

                if nnfeature is not None:
                    mindist = inputgeom.distance(nnfeature.geometry())
                    minfound = True
            # Not points on the join side
            # Handle common (non multi) non-point geometries
            elif (self.joinWkbType == QgsWkbTypes.Polygon or
                  self.joinWkbType == QgsWkbTypes.Polygon25D or
                  self.joinWkbType == QgsWkbTypes.LineString or
                  self.joinWkbType == QgsWkbTypes.LineString25D):
                # Use the join layer index to speed up the join when
                # the join layer geometry type is polygon or line
                # and the input layer geometry type is point or a
                # point approximation
                nearestids = self.joinlind.nearestNeighbor(
                    inputgeom.asPoint(), 1)
                # Possibe index out of range!!! ???
                nearestindexid = nearestids[0]
                # Check for self join (possible if approx input)
                if self.selfjoin and nearestindexid == infeatureid:
                    # Self join and same feature, so get the
                    # first two neighbours
                    nearestindexes = self.joinlind.nearestNeighbor(
                                             inputgeom.asPoint(), 2)
                    # Possibe index out of range!!! ???
                    nearestindexid = nearestindexes[0]
                    if (nearestindexid == infeatureid and
                                  len(nearestindexes) > 1):
                        nearestindexid = nearestindexes[1]

                # If exclude containing, check for containment
                if self.excludecontaining:
                    contained = False
                    nearfeature = next(self.joinvl.getFeatures(
                                           QgsFeatureRequest(nearestindexid)))
                    # Check for containment
                    if nearfeature.geometry().contains(inputgeom):
                        contained = True
                    if inputgeom.contains(nearfeature.geometry()):
                        contained = True
                    numberofnn = 2
                    # Assumes that nearestNeighbor returns hits in the same
                    # sequence for all numbers of nearest neighbour
                    while contained:
                        if self.abort is True:
                            break
                        nearestindexes = self.joinlind.nearestNeighbor(
                                               inputgeom.asPoint(), numberofnn)
                        if len(nearestindexes) < numberofnn:
                            nearestindexid = nearestindexes[numberofnn - 2]
                            self.status.emit('No non-containing geometries!')
                            break
                        else:
                            nearestindexid = nearestindexes[numberofnn - 1]
                            # Seems to respect selection...?
                            nearfeature = next(self.joinvl.getFeatures(
                                QgsFeatureRequest(nearestindexid)))
                            # Check for containment  # Works!
                            if nearfeature.geometry().contains(
                                                         inputgeom):
                                contained = True
                            elif inputgeom.contains(
                                    nearfeature.geometry()):
                                contained = True
                            else:
                                contained = False
                        numberofnn = numberofnn + 1
                    # end while

                # Get the feature among the candidates from the index
                #if self.selectedjoonly:
                #    # Does not get the correct feature!
                #    nnfeature = next(self.joinvl.getSelectedFeatures(
                #        QgsFeatureRequest(nearestindexid)))
                # This seems to work also in the presence of selections
                nnfeature = next(self.joinvl.getFeatures(
                    QgsFeatureRequest(nearestindexid)))
                mindist = inputgeom.distance(nnfeature.geometry())
                if mindist == 0:
                    insidep = nnfeature.geometry().contains(
                                                          inputgeom.asPoint())
                    # self.status.emit('0 distance! - ' + str(nearestindexid))
                    # self.status.emit('Inside: ' + str(insidep))
                px = inputgeom.asPoint().x()
                py = inputgeom.asPoint().y()
                # Search the neighbourhood
                closefids = self.joinlind.intersects(QgsRectangle(
                    px - mindist,
                    py - mindist,
                    px + mindist,
                    py + mindist))
                for closefid in closefids:
                    if self.abort is True:
                        break
                    # Check for self join and same feature
                    if self.selfjoin and closefid == infeatureid:
                        continue
                    # If exclude containing, check for containment
                    if self.excludecontaining:
                        # Seems to respect selection...?
                        closefeature = next(self.joinvl.getFeatures(
                            QgsFeatureRequest(closefid)))
                        # Check for containment
                        if closefeature.geometry().contains(
                                                         inputgeom.asPoint()):
                            continue
                    if False:
                    #if self.selectedjoonly:
                        closef = next(self.joinvl.getSelectedFeatures(
                            QgsFeatureRequest(closefid)))
                    else:
                        closef = next(self.joinvl.getFeatures(
                            QgsFeatureRequest(closefid)))
                    thisdistance = inputgeom.distance(closef.geometry())
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = closef
                    if mindist == 0:
                        # self.status.emit('  Mindist = 0!')
                        break
            # Other geometry on the join side (multi and more)
            else:
                # Join with no index use
                # Go through all the features from the join layer!
                for inFeatJoin in self.joinf:
                    if self.abort is True:
                        break
                    joingeom = inFeatJoin.geometry()
                    thisdistance = inputgeom.distance(joingeom)
                    if thisdistance < 0:
                        self.status.emit("Warning: Join feature with "
                                         "missing geometry: " +
                                         str(inFeatJoin.id()))
                        continue
                    # If the distance is 0, check for equality of the
                    # features (in case it is a self join)
                    if (thisdistance == 0 and self.selfjoin and
                            infeatureid == inFeatJoin.id()):
                        continue
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = inFeatJoin
                    # For 0 distance, settle with the first feature
                    if mindist == 0:
                        break
        # non (simple) point input geometries (could be multipoint)
        else:
            if (self.nonpointexactindex):
                # Use the spatial index on the join layer (default).
                # First we do an approximate search
                # Get the input geometry centroid
                centroid = infeature.geometry().centroid()
                centroidgeom = centroid.asPoint()
                # Find the nearest neighbour (index geometries only)
                # Possibe index out of range!!! ???
                nearestid = self.joinlind.nearestNeighbor(centroidgeom, 1)[0]
                # Check for self join
                if self.selfjoin and nearestid == infeatureid:
                    # Self join and same feature, so get the two
                    # first two neighbours
                    nearestindexes = self.joinlind.nearestNeighbor(
                        centroidgeom, 2)
                    nearestid = nearestindexes[0]
                    if nearestid == infeatureid and len(nearestindexes) > 1:
                        nearestid = nearestindexes[1]
                # Get the feature!
                if False:
                #if self.selectedjoonly:
                    nnfeature = next(self.joinvl.getSelectedFeatures(
                        QgsFeatureRequest(nearestid)))
                else:
                    nnfeature = next(self.joinvl.getFeatures(
                        QgsFeatureRequest(nearestid)))
                mindist = inputgeom.distance(nnfeature.geometry())
                # Calculate the search rectangle (inputgeom BBOX
                inpbbox = infeature.geometry().boundingBox()
                minx = inpbbox.xMinimum() - mindist
                maxx = inpbbox.xMaximum() + mindist
                miny = inpbbox.yMinimum() - mindist
                maxy = inpbbox.yMaximum() + mindist
                # minx = min(inpbbox.xMinimum(), centroidgeom.x() - mindist)
                # maxx = max(inpbbox.xMaximum(), centroidgeom.x() + mindist)
                # miny = min(inpbbox.yMinimum(), centroidgeom.y() - mindist)
                # maxy = max(inpbbox.yMaximum(), centroidgeom.y() + mindist)
                searchrectangle = QgsRectangle(minx, miny, maxx, maxy)
                # Fetch the candidate join geometries
                closefids = self.joinlind.intersects(searchrectangle)
                # Loop through the geometries and choose the closest
                # one
                for closefid in closefids:
                    if self.abort is True:
                        break
                    # Check for self join and identical feature
                    if self.selfjoin and closefid == infeatureid:
                        continue
                    if False:
                    #if self.selectedjoonly:
                        closef = next(self.joinvl.getSelectedFeatures(
                            QgsFeatureRequest(closefid)))
                    else:
                        closef = next(self.joinvl.getFeatures(
                            QgsFeatureRequest(closefid)))
                    thisdistance = inputgeom.distance(closef.geometry())
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = closef
                    if mindist == 0:
                        break
            else:
                # Join with no index use
                # Check all the features of the join layer!
                mindist = float("inf")  # should not be necessary
                for inFeatJoin in self.joinf:
                    if self.abort is True:
                        break
                    joingeom = inFeatJoin.geometry()
                    thisdistance = inputgeom.distance(joingeom)
                    if thisdistance < 0:
                        self.status.emit("Warning: Join feature with "
                                         "missing geometry: " +
                                         str(inFeatJoin.id()))
                        continue
                    # If the distance is 0, check for equality of the
                    # features (in case it is a self join)
                    if (thisdistance == 0 and self.selfjoin and
                            infeatureid == inFeatJoin.id()):
                        continue
                    if thisdistance < mindist:
                        mindist = thisdistance
                        nnfeature = inFeatJoin
                    # For 0 distance, settle with the first feature
                    if mindist == 0:
                        break

        if not self.abort:
            # self.status.emit('Near feature - ' + str(nnfeature.id()))
            # Collect the attribute
            atMapA = infeature.attributes()
            if nnfeature is not None:
                atMapB = nnfeature.attributes()
            else:
                atMapB = []
                for thefield in self.joinvl.fields():
                    atMapB.extend([None])
            attrs = []
            attrs.extend(atMapA)
            attrs.extend(atMapB)
            attrs.append(mindist)
            # Create the feature
            outFeat = QgsFeature()
            # Use the original input layer geometry!:
            outFeat.setGeometry(infeature.geometry())
            # Use the modified input layer geometry (could be
            # centroid)
            # outFeat.setGeometry(inputgeom)
            # Add the attributes
            outFeat.setAttributes(attrs)
            # self.calculate_progress()
            self.features.append(outFeat)
            # self.mem_joinl.dataProvider().addFeatures([outFeat])
    # end of do_indexjoin

    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('NNJoinEngine', message)
Exemple #31
0
class PubTool:
    def __init__(self, iface):
        self.iface = iface
        self.index_built = False

    def initGui(self):
        self.action = QAction('Pub!', self.iface.mainWindow())
        self.action.triggered.connect(self.run)
        self.iface.addToolBarIcon(self.action)

        # build map tool
        self.mapTool = QgsMapToolEmitPoint(self.iface.mapCanvas())
        self.mapTool.canvasClicked.connect(self.handle_click)

        #build projection transformer
        self.pj_transformer = QgsCoordinateTransform(\
            QgsCoordinateReferenceSystem("EPSG:4326"),\
            QgsCoordinateReferenceSystem("EPSG:5514"),\
            QgsProject.instance())
        self.allowed_projections = ["EPSG:4326", "EPSG:5514"]

        #Calculate distance
        self.distance_calculator = QgsDistanceArea()
        self.distance_calculator.setSourceCrs(
            QgsCoordinateReferenceSystem("EPSG:5514"))

    def unload(self):
        self.iface.removeToolBarIcon(self.action)
        del self.action, self.index, self.mapTool
        del self.pj_transformer, self.allowed_projections, self.index_built

    def build_index(self):
        self.index = QgsSpatialIndex(self.iface.activeLayer().getFeatures())
        self.index_built = True

    def run(self):
        if (not self.index_built):
            self.build_index()

        self.iface.mapCanvas().setMapTool(self.mapTool)

    def get_nearest_point_name(self, point):

        # Check for projection
        c_proj = self.iface.mapCanvas().mapSettings().destinationCrs().authid()
        if c_proj not in self.allowed_projections:
            raise Exception(
                f'Your canvas projection {c_proj} is not allowed with plugin pub-tools;\
                 allowed projections: {self.allowed_projections}')
        elif c_proj == "EPSG:4326":
            point = QgsPoint(point)
            point.transform(self.pj_transformer)
            point = QgsPointXY(point)

        nearest_id = self.index.nearestNeighbor(point, 1)

        request = QgsFeatureRequest()
        request.setFilterFids(nearest_id)

        #TODO: Check if vector layer is activated!
        features = self.iface.activeLayer().getFeatures(request)

        # https://qgis.org/pyqgis/master/core/QgsSpatialIndex.html?highlight=spatialindex#module-QgsSpatialIndex
        elem = next(features, False)

        if not elem:
            raise Exception("No pub close")

        #TODO: Check if feature has Name attribute!
        return elem.attribute("Name")

    def handle_click(self, point):

        try:
            nearest = self.get_nearest_point_name(point)

            msg = f'You are at x: {point.x()} y: {point.y()}\
                \nNearest point to you: {nearest}'

            QMessageBox.information(None, 'Pub Tool', msg)

        except Exception as ex:
            self.iface.messageBar().pushMessage("Error",
                                                str(ex),
                                                level=Qgis.Critical)
Exemple #32
0
class Serval(object):

    LINE_SELECTION = "line"
    POLYGON_SELECTION = "polygon"
    RGB = "RGB"
    SINGLE_BAND = "Single band"

    def __init__(self, iface):
        self.iface = iface
        self.canvas = self.iface.mapCanvas()
        self.plugin_dir = os.path.dirname(__file__)
        self.uc = UserCommunication(iface, 'Serval')
        self.load_settings()
        self.raster = None
        self.handler = None
        self.spin_boxes = None
        self.exp_dlg = None
        self.exp_builder = None
        self.block_pts_layer = None
        self.px, self.py = [0, 0]
        self.last_point = QgsPointXY(0, 0)
        self.rbounds = None
        self.changes = dict()  # dict with rasters changes {raster_id: RasterChanges instance}
        self.project = QgsProject.instance()
        self.crs_transform = None
        self.all_touched = None
        self.selection_mode = None
        self.spatial_index_time = dict()  # {layer_id: creation time}
        self.spatial_index = dict()  # {layer_id: spatial index}
        self.selection_layers_count = 1
        self.debug = DEBUG
        self.logger = get_logger() if self.debug else None

        self.menu = u'Serval'
        self.actions = []
        self.actions_always_on = []
        self.toolbar = self.iface.addToolBar(u'Serval Main Toolbar')
        self.toolbar.setObjectName(u'Serval Main Toolbar')
        self.toolbar.setToolTip(u'Serval Main Toolbar')

        self.sel_toolbar = self.iface.addToolBar(u'Serval Selection Toolbar')
        self.sel_toolbar.setObjectName(u'Serval Selection Toolbar')
        self.sel_toolbar.setToolTip(u'Serval Selection Toolbar')

        # Map tools
        self.probe_tool = QgsMapToolEmitPoint(self.canvas)
        self.probe_tool.setObjectName('ServalProbeTool')
        self.probe_tool.setCursor(QCursor(QPixmap(icon_path('probe_tool.svg')), hotX=2, hotY=22))
        self.probe_tool.canvasClicked.connect(self.point_clicked)
        self.draw_tool = QgsMapToolEmitPoint(self.canvas)
        self.draw_tool.setObjectName('ServalDrawTool')
        self.draw_tool.setCursor(QCursor(QPixmap(icon_path('draw_tool.svg')), hotX=2, hotY=22))
        self.draw_tool.canvasClicked.connect(self.point_clicked)
        self.selection_tool = RasterCellSelectionMapTool(self.iface, self.uc, self.raster, debug=self.debug)
        self.selection_tool.setObjectName('RasterSelectionTool')
        self.map_tool_btn = dict()  # {map tool: button activating the tool}

        self.iface.currentLayerChanged.connect(self.set_active_raster)
        self.project.layersAdded.connect(self.set_active_raster)
        self.canvas.mapToolSet.connect(self.check_active_tool)

        self.register_exp_functions()

    def load_settings(self):
        """Return plugin settings dict - default values are overriden by user prefered values from QSettings."""
        self.default_settings = {
            "undo_steps": {"value": 3, "vtype": int},
        }
        self.settings = dict()
        s = QSettings()
        s.beginGroup("serval")
        for k, v in self.default_settings.items():
            user_val = s.value(k, v["value"], v["vtype"])
            self.settings[k] = user_val

    def edit_settings(self):
        """Open dialog with plugin settings."""
        s = QSettings()
        s.beginGroup("serval")
        k = "undo_steps"
        cur_val = self.settings[k]
        val_type = self.default_settings[k]["vtype"]
        cur_steps = s.value(k, cur_val, val_type)

        label = 'Nr of Undo/Redo steps:'
        steps, ok = QInputDialog.getInt(None, "Serval Settings", label, cur_steps)
        if not ok:
            return
        if steps >= 0:
            s.setValue("undo_steps", steps)
        self.load_settings()
        self.uc.show_info("Some new settings may require QGIS restart.")

    def initGui(self):
        _ = self.add_action(
            'serval_icon.svg',
            text=u'Show Serval Toolbars',
            add_to_menu=True,
            callback=self.show_toolbar,
            always_on=True, )

        _ = self.add_action(
            'serval_icon.svg',
            text=u'Hide Serval Toolbars',
            add_to_menu=True,
            callback=self.hide_toolbar,
            always_on=True, )

        self.probe_btn = self.add_action(
            'probe.svg',
            text="Probe raster",
            callback=self.activate_probing,
            add_to_toolbar=self.toolbar,
            checkable=True, )
        self.map_tool_btn[self.probe_tool] = self.probe_btn

        self.color_btn = QgsColorButton()
        self.color_btn.setColor(Qt.gray)
        self.color_btn.setMinimumSize(QSize(40, 24))
        self.color_btn.setMaximumSize(QSize(40, 24))
        self.toolbar.addWidget(self.color_btn)
        self.color_picker_connection(connect=True)
        self.color_btn.setDisabled(True)

        self.toolbar.addWidget(QLabel("Band:"))
        self.bands_cbo = QComboBox()
        self.bands_cbo.addItem("1", 1)
        self.toolbar.addWidget(self.bands_cbo)
        self.bands_cbo.currentIndexChanged.connect(self.update_active_bands)
        self.bands_cbo.setDisabled(True)

        self.spin_boxes = BandBoxes()
        self.toolbar.addWidget(self.spin_boxes)
        self.spin_boxes.enter_hit.connect(self.apply_spin_box_values)

        self.draw_btn = self.add_action(
            'draw.svg',
            text="Apply Value(s) To Single Cell",
            callback=self.activate_drawing,
            add_to_toolbar=self.toolbar,
            checkable=True, )
        self.map_tool_btn[self.draw_tool] = self.draw_btn

        self.apply_spin_box_values_btn = self.add_action(
            'apply_const_value.svg',
            text="Apply Value(s) to Selection",
            callback=self.apply_spin_box_values,
            add_to_toolbar=self.toolbar, )

        self.gom_btn = self.add_action(
            'apply_nodata_value.svg',
            text="Apply NoData to Selection",
            callback=self.apply_nodata_value,
            add_to_toolbar=self.toolbar, )

        self.exp_dlg_btn = self.add_action(
            'apply_expression_value.svg',
            text="Apply Expression Value To Selection",
            callback=self.define_expression,
            add_to_toolbar=self.toolbar,
            checkable=False, )

        self.low_pass_filter_btn = self.add_action(
            'apply_low_pass_filter.svg',
            text="Apply Low-Pass 3x3 Filter To Selection",
            callback=self.apply_low_pass_filter,
            add_to_toolbar=self.toolbar,
            checkable=False, )

        self.undo_btn = self.add_action(
            'undo.svg',
            text="Undo",
            callback=self.undo,
            add_to_toolbar=self.toolbar, )

        self.redo_btn = self.add_action(
            'redo.svg',
            text="Redo",
            callback=self.redo,
            add_to_toolbar=self.toolbar, )

        self.set_nodata_btn = self.add_action(
            'set_nodata.svg',
            text="Edit Raster NoData Values",
            callback=self.set_nodata,
            add_to_toolbar=self.toolbar, )

        self.settings_btn = self.add_action(
            'edit_settings.svg',
            text="Serval Settings",
            callback=self.edit_settings,
            add_to_toolbar=self.toolbar,
            always_on=True, )

        self.show_help = self.add_action(
            'help.svg',
            text="Help",
            add_to_menu=True,
            callback=self.show_website,
            add_to_toolbar=self.toolbar,
            always_on=True, )

        # Selection Toolbar

        line_width_icon = QIcon(icon_path("line_width.svg"))
        line_width_lab = QLabel()
        line_width_lab.setPixmap(line_width_icon.pixmap(22, 12))
        self.sel_toolbar.addWidget(line_width_lab)

        self.line_width_sbox = QgsDoubleSpinBox()
        self.line_width_sbox.setMinimumSize(QSize(50, 24))
        self.line_width_sbox.setMaximumSize(QSize(50, 24))
        # self.line_width_sbox.setButtonSymbols(QAbstractSpinBox.NoButtons)
        self.line_width_sbox.setValue(1)
        self.line_width_sbox.setMinimum(0.01)
        self.line_width_sbox.setShowClearButton(False)
        self.line_width_sbox.setToolTip("Selection Line Width")
        self.line_width_sbox.valueChanged.connect(self.update_selection_tool)

        self.width_unit_cbo = QComboBox()
        self.width_units = ("map units", "pixel width", "pixel height", "hairline",)
        for u in self.width_units:
            self.width_unit_cbo.addItem(u)
        self.width_unit_cbo.setToolTip("Selection Line Width Unit")
        self.sel_toolbar.addWidget(self.line_width_sbox)
        self.sel_toolbar.addWidget(self.width_unit_cbo)
        self.width_unit_cbo.currentIndexChanged.connect(self.update_selection_tool)

        self.line_select_btn = self.add_action(
            'select_line.svg',
            text="Select Raster Cells by Line",
            callback=self.activate_line_selection,
            add_to_toolbar=self.sel_toolbar,
            checkable=True, )

        self.polygon_select_btn = self.add_action(
            'select_polygon.svg',
            text="Select Raster Cells by Polygon",
            callback=self.activate_polygon_selection,
            add_to_toolbar=self.sel_toolbar,
            checkable=True, )

        self.selection_from_layer_btn = self.add_action(
            'select_from_layer.svg',
            text="Create Selection From Layer",
            callback=self.selection_from_layer,
            add_to_toolbar=self.sel_toolbar, )

        self.selection_to_layer_btn = self.add_action(
            'selection_to_layer.svg',
            text="Create Memory Layer From Selection",
            callback=self.selection_to_layer,
            add_to_toolbar=self.sel_toolbar, )

        self.clear_selection_btn = self.add_action(
            'clear_selection.svg',
            text="Clear selection",
            callback=self.clear_selection,
            add_to_toolbar=self.sel_toolbar, )

        self.toggle_all_touched_btn = self.add_action(
            'all_touched.svg',
            text="Toggle All Touched Get Selected",
            callback=self.toggle_all_touched,
            checkable=True, checked=True,
            add_to_toolbar=self.sel_toolbar, )
        self.all_touched = True

        self.enable_toolbar_actions(enable=False)
        self.check_undo_redo_btns()

    def add_action(self, icon_name, callback=None, text="", enabled_flag=True, add_to_menu=False, add_to_toolbar=None,
                   status_tip=None, whats_this=None, checkable=False, checked=False, always_on=False):
            
        icon = QIcon(icon_path(icon_name))
        action = QAction(icon, text, self.iface.mainWindow())
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)
        action.setCheckable(checkable)
        action.setChecked(checked)

        if status_tip is not None:
            action.setStatusTip(status_tip)
        if whats_this is not None:
            action.setWhatsThis(whats_this)
        if add_to_toolbar is not None:
            add_to_toolbar.addAction(action)
        if add_to_menu:
            self.iface.addPluginToMenu(self.menu, action)

        self.actions.append(action)
        if always_on:
            self.actions_always_on.append(action)
        return action

    def unload(self):
        self.changes = None
        if self.selection_tool:
            self.selection_tool.reset()
        if self.spin_boxes is not None:
            self.spin_boxes.remove_spinboxes()
        for action in self.actions:
            self.iface.removePluginMenu('Serval', action)
            self.iface.removeToolBarIcon(action)
        del self.toolbar
        del self.sel_toolbar
        self.iface.actionPan().trigger()
        self.unregister_exp_functions()

    def show_toolbar(self):
        if self.toolbar:
            self.toolbar.show()
            self.sel_toolbar.show()

    def hide_toolbar(self):
        if self.toolbar:
            self.toolbar.hide()
            self.sel_toolbar.hide()

    @staticmethod
    def register_exp_functions():
        QgsExpression.registerFunction(nearest_feature_attr_value)
        QgsExpression.registerFunction(nearest_pt_on_line_interpolate_z)
        QgsExpression.registerFunction(intersecting_features_attr_average)
        QgsExpression.registerFunction(interpolate_from_mesh)

    @staticmethod
    def unregister_exp_functions():
        QgsExpression.unregisterFunction('nearest_feature_attr_value')
        QgsExpression.unregisterFunction('nearest_pt_on_line_interpolate_z')
        QgsExpression.unregisterFunction('intersecting_features_attr_average')
        QgsExpression.unregisterFunction('interpolate_from_mesh')

    def uncheck_all_btns(self):
        self.probe_btn.setChecked(False)
        self.draw_btn.setChecked(False)
        self.gom_btn.setChecked(False)
        self.line_select_btn.setChecked(False)
        self.polygon_select_btn.setChecked(False)

    def check_active_tool(self, cur_tool):
        self.uncheck_all_btns()
        if cur_tool in self.map_tool_btn:
            self.map_tool_btn[cur_tool].setChecked(True)
        if cur_tool == self.selection_tool:
            if self.selection_mode == self.LINE_SELECTION:
                self.line_select_btn.setChecked(True)
            else:
                self.polygon_select_btn.setChecked(True)

    def activate_probing(self):
        self.mode = 'probe'
        self.canvas.setMapTool(self.probe_tool)

    def define_expression(self):
        if not self.selection_tool.selected_geometries:
            self.uc.bar_warn("No selection for raster layer. Select some cells and retry...")
            return
        self.handler.select(self.selection_tool.selected_geometries, all_touched_cells=self.all_touched)
        self.handler.create_cell_pts_layer()
        if self.handler.cell_pts_layer.featureCount() == 0:
            self.uc.bar_warn("No selection for raster layer. Select some cells and retry...")
            return
        self.exp_dlg = QgsExpressionBuilderDialog(self.handler.cell_pts_layer)
        self.exp_builder = self.exp_dlg.expressionBuilder()
        self.exp_dlg.accepted.connect(self.apply_exp_value)
        self.exp_dlg.show()

    def apply_exp_value(self):
        if not self.exp_dlg.expressionText() or not self.exp_builder.isExpressionValid():
            return
        QApplication.setOverrideCursor(Qt.WaitCursor)
        exp = self.exp_dlg.expressionText()
        idx = self.handler.cell_pts_layer.addExpressionField(exp, QgsField('exp_val', QVariant.Double))
        self.handler.exp_field_idx = idx
        self.handler.write_block()
        QApplication.restoreOverrideCursor()
        self.raster.triggerRepaint()

    def activate_drawing(self):
        self.mode = 'draw'
        self.canvas.setMapTool(self.draw_tool)

    def get_cur_line_width(self):
        width_coef = {
            "map units": 1.,
            "pixel width": self.raster.rasterUnitsPerPixelX(),
            "pixel height": self.raster.rasterUnitsPerPixelY(),
            "hairline": 0.000001,
        }
        return self.line_width_sbox.value() * width_coef[self.width_unit_cbo.currentText()]

    def set_selection_tool(self, mode):
        if self.raster is None:
            self.uc.bar_warn("Select a raster layer")
            return
        self.selection_mode = mode
        self.selection_tool.init_tool(self.raster, mode=self.selection_mode, line_width=self.get_cur_line_width())
        self.selection_tool.set_prev_tool(self.canvas.mapTool())
        self.canvas.setMapTool(self.selection_tool)

    def activate_line_selection(self):
        self.set_selection_tool(self.LINE_SELECTION)

    def activate_polygon_selection(self):
        self.set_selection_tool(self.POLYGON_SELECTION)

    def update_selection_tool(self):
        """Reactivate the selection tool with updated line width and units."""
        if self.selection_mode == self.LINE_SELECTION:
            self.activate_line_selection()
        elif self.selection_mode == self.POLYGON_SELECTION:
            self.activate_polygon_selection()
        else:
            pass

    def apply_values(self, new_values):
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.handler.select(self.selection_tool.selected_geometries, all_touched_cells=self.all_touched)
        self.handler.write_block(new_values)
        QApplication.restoreOverrideCursor()
        self.raster.triggerRepaint()

    def apply_values_single_cell(self, new_vals):
        """Create single cell selection and apply the new values."""
        cp = self.last_point
        if self.logger:
            self.logger.debug(f"Changing single cell for pt {cp}")
        col, row = self.handler.point_to_index([cp.x(), cp.y()])
        px, py = self.handler.index_to_point(row, col, upper_left=False)
        d = 0.001
        bbox = QgsRectangle(px - d, py - d, px + d, py + d)
        if self.logger:
            self.logger.debug(f"Changing single cell in {bbox}")
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.handler.select([QgsGeometry.fromRect(bbox)], all_touched_cells=False, transform=False)
        self.handler.write_block(new_vals)
        QApplication.restoreOverrideCursor()
        self.raster.triggerRepaint()

    def apply_spin_box_values(self):
        if not self.selection_tool.selected_geometries:
            return
        self.apply_values(self.spin_boxes.get_values())

    def apply_nodata_value(self):
        if not self.selection_tool.selected_geometries:
            return
        self.apply_values(self.handler.nodata_values)

    def apply_low_pass_filter(self):
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.handler.select(self.selection_tool.selected_geometries, all_touched_cells=self.all_touched)
        self.handler.write_block(low_pass_filter=True)
        QApplication.restoreOverrideCursor()
        self.raster.triggerRepaint()

    def clear_selection(self):
        if self.selection_tool:
            self.selection_tool.clear_all_selections()

    def selection_from_layer(self):
        """Create a new selection from layer."""
        self.selection_tool.init_tool(self.raster, mode=self.POLYGON_SELECTION, line_width=self.get_cur_line_width())
        dlg = LayerSelectDialog()
        if not dlg.exec_():
            return
        cur_layer = dlg.cbo.currentLayer()
        if not cur_layer.type() == QgsMapLayerType.VectorLayer:
            return
        self.selection_tool.selection_from_layer(cur_layer)

    def selection_to_layer(self):
        """Create a memory layer from current selection"""
        geoms = self.selection_tool.selected_geometries
        if geoms is None or not self.raster:
            return
        crs_str = self.raster.crs().toProj()
        nr = self.selection_layers_count
        self.selection_layers_count += 1
        mlayer = QgsVectorLayer(f"Polygon?crs={crs_str}&field=fid:int", f"Raster selection {nr}", "memory")
        fields = mlayer.dataProvider().fields()
        features = []
        for i, geom in enumerate(geoms):
            feat = QgsFeature(fields)
            feat["fid"] = i + 1
            feat.setGeometry(geom)
            features.append(feat)
        mlayer.dataProvider().addFeatures(features)
        self.project.addMapLayer(mlayer)

    def toggle_all_touched(self):
        """Toggle selection mode."""
        # button is toggled automatically when clicked, just update the attribute
        self.all_touched = self.toggle_all_touched_btn.isChecked()

    def point_clicked(self, point=None, button=None):
        if self.raster is None:
            self.uc.bar_warn("Choose a raster to work with...", dur=3)
            return

        if self.logger:
            self.logger.debug(f"Clicked point in canvas CRS: {point if point else self.last_point}")

        if point is None:
            ptxy_in_src_crs = self.last_point
        else:
            if self.crs_transform:
                if self.logger:
                    self.logger.debug(f"Transforming clicked point {point}")
                try:
                    ptxy_in_src_crs = self.crs_transform.transform(point)
                except QgsCsException as err:
                    self.uc.show_warn(
                        "Point coordinates transformation failed! Check the raster projection:\n\n{}".format(repr(err)))
                    return
            else:
                ptxy_in_src_crs = QgsPointXY(point.x(), point.y())

        if self.logger:
            self.logger.debug(f"Clicked point in raster CRS: {ptxy_in_src_crs}")
        self.last_point = ptxy_in_src_crs

        ident_vals = self.handler.provider.identify(ptxy_in_src_crs, QgsRaster.IdentifyFormatValue).results()
        cur_vals = list(ident_vals.values())

        # check if the point is within active raster extent
        if not self.rbounds[0] <= ptxy_in_src_crs.x() <= self.rbounds[2]:
            self.uc.bar_info("Out of x bounds", dur=3)
            return
        if not self.rbounds[1] <= ptxy_in_src_crs.y() <= self.rbounds[3]:
            self.uc.bar_info("Out of y bounds", dur=3)
            return

        if self.mode == 'draw':
            new_vals = self.spin_boxes.get_values()
            if self.logger:
                self.logger.debug(f"Applying const value {new_vals}")
            self.apply_values_single_cell(new_vals)
        else:
            self.spin_boxes.set_values(cur_vals)
            if 2 < self.handler.bands_nr < 5:
                self.color_picker_connection(connect=False)
                self.color_btn.setColor(QColor(*self.spin_boxes.get_values()[:4]))
                self.color_picker_connection(connect=True)

    def set_values_from_picker(self, c):
        """Set bands spinboxes values after color change in the color picker"""
        values = None
        if self.handler.bands_nr > 2:
            values = [c.red(), c.green(), c.blue()]
            if self.handler.bands_nr == 4:
                values.append(c.alpha())
        if values:
            self.spin_boxes.set_values(values)

    def set_nodata(self):
        """Set NoData value(s) for each band of current raster."""
        if not self.raster:
            self.uc.bar_warn('Select a raster layer to define/change NoData value!')
            return
        if self.handler.provider.userNoDataValues(1):
            note = '\nNote: there is a user defined NODATA value.\nCheck the raster properties (Transparency).'
        else:
            note = ''
        dt = self.handler.provider.dataType(1)
        
        # current NODATA value
        if self.handler.provider.sourceHasNoDataValue(1):
            cur_nodata = self.handler.provider.sourceNoDataValue(1)
            if dt < 6:
                cur_nodata = '{0:d}'.format(int(float(cur_nodata)))
        else:
            cur_nodata = ''
        
        label = 'Define/change raster NODATA value.\n\n'
        label += 'Raster src_data type: {}.{}'.format(dtypes[dt]['name'], note)
        nd, ok = QInputDialog.getText(None, "Define NODATA Value", label, QLineEdit.Normal, str(cur_nodata))
        if not ok:
            return
        if not is_number(nd):
            self.uc.show_warn('Wrong NODATA value!')
            return
        new_nodata = int(nd) if dt < 6 else float(nd)
        
        # set the NODATA value for each band
        res = []
        for nr in self.handler.bands_range:
            res.append(self.handler.provider.setNoDataValue(nr, new_nodata))
            self.handler.provider.sourceHasNoDataValue(nr)
        
        if False in res:
            self.uc.show_warn('Setting new NODATA value failed!')
        else:
            self.uc.bar_info('Successful setting new NODATA values!', dur=2)

        self.set_active_raster()
        self.raster.triggerRepaint()
        
    def check_undo_redo_btns(self):
        """Enable/Disable undo and redo buttons based on availability of undo/redo for current raster."""
        self.undo_btn.setDisabled(True)
        self.redo_btn.setDisabled(True)
        if self.raster is None or self.raster.id() not in self.changes:
            return
        changes = self.changes[self.raster.id()]
        if changes.nr_undos() > 0:
            self.undo_btn.setEnabled(True)
        if changes.nr_redos() > 0:
            self.redo_btn.setEnabled(True)

    def enable_toolbar_actions(self, enable=True):
        """Enable / disable all toolbar actions but Help (for vectors and unsupported rasters)"""
        for widget in self.actions + [self.width_unit_cbo, self.line_width_sbox]:
            widget.setEnabled(enable)
            if widget in self.actions_always_on:
                widget.setEnabled(True)
        self.spin_boxes.enable(enable)

    @staticmethod
    def check_layer(layer):
        """Check if we can work with the raster"""
        if layer is None:
            return False
        if layer.type() != QgsMapLayerType.RasterLayer:
            return False
        if layer.providerType() != 'gdal':
            return False
        if all([
            layer.isValid(),
            layer.crs() is not None,
            check_gdal_driver_create_option(layer),                 # GDAL driver has CREATE option
            os.path.isfile(layer.dataProvider().dataSourceUri()),   # is it a local file?
        ]):
            return True
        else:
            return False

    def set_bands_cbo(self):
        self.bands_cbo.currentIndexChanged.disconnect(self.update_active_bands)
        self.bands_cbo.clear()
        for band in self.handler.bands_range:
            self.bands_cbo.addItem(f"{band}", [band])
        if self.handler.bands_nr > 1:
            self.bands_cbo.addItem(self.RGB, [1, 2, 3])
        self.bands_cbo.setCurrentIndex(0)
        self.bands_cbo.currentIndexChanged.connect(self.update_active_bands)

    def update_active_bands(self, idx):
        bands = self.bands_cbo.currentData()
        self.handler.active_bands = bands
        self.spin_boxes.create_spinboxes(bands, self.handler.data_types, self.handler.nodata_values)
        self.color_btn.setEnabled(len(bands) > 1)
        self.exp_dlg_btn.setEnabled(len(bands) == 1)

    def set_active_raster(self):
        """Active layer has changed - check if it is a raster layer and prepare it for the plugin"""
        old_spin_boxes_values = self.spin_boxes.get_values()
        self.crs_transform = None
        layer = self.iface.activeLayer()
        if self.check_layer(layer):
            self.raster = layer
            self.crs_transform = None if self.project.crs() == self.raster.crs() else \
                QgsCoordinateTransform(self.project.crs(), self.raster.crs(), self.project)
            self.handler = RasterHandler(self.raster, self.uc, self.debug)
            supported, unsupported_type = self.handler.write_supported()
            if supported:
                self.enable_toolbar_actions()
                self.set_bands_cbo()
                self.spin_boxes.create_spinboxes(self.handler.active_bands,
                                                 self.handler.data_types, self.handler.nodata_values)
                if self.handler.bands_nr == len(old_spin_boxes_values):
                    self.spin_boxes.set_values(old_spin_boxes_values)
                self.bands_cbo.setEnabled(self.handler.bands_nr > 1)
                self.color_btn.setEnabled(len(self.handler.active_bands) > 1)
                self.rbounds = self.raster.extent().toRectF().getCoords()
                self.handler.raster_changed.connect(self.add_to_undo)
                if self.raster.id() not in self.changes:
                    self.changes[self.raster.id()] = RasterChanges(nr_to_keep=self.settings["undo_steps"])
            else:
                msg = f"The raster has unsupported src_data type: {unsupported_type}"
                msg += "\nServal can't work with it, sorry..."
                self.uc.show_warn(msg)
                self.enable_toolbar_actions(enable=False)
                self.reset_raster()
        
        else:
            # unsupported raster
            self.enable_toolbar_actions(enable=False)
            self.reset_raster()

        self.check_undo_redo_btns()

    def add_to_undo(self, change):
        """Add the old and new blocks to undo stack."""
        self.changes[self.raster.id()].add_change(change)
        self.check_undo_redo_btns()
        if self.logger:
            self.logger.debug(self.get_undo_redo_values())

    def get_undo_redo_values(self):
        changes = self.changes[self.raster.id()]
        return f"nr undos: {changes.nr_undos()}, redos: {changes.nr_redos()}"

    def undo(self):
        undo_data = self.changes[self.raster.id()].undo()
        self.handler.write_block_undo(undo_data)
        self.raster.triggerRepaint()
        self.check_undo_redo_btns()

    def redo(self):
        redo_data = self.changes[self.raster.id()].redo()
        self.handler.write_block_undo(redo_data)
        self.raster.triggerRepaint()
        self.check_undo_redo_btns()

    def reset_raster(self):
        self.raster = None
        self.color_btn.setDisabled(True)

    def color_picker_connection(self, connect=True):
        if connect:
            self.color_btn.colorChanged.connect(self.set_values_from_picker)
        else:
            self.color_btn.colorChanged.disconnect(self.set_values_from_picker)

    @staticmethod
    def show_website():
        QDesktopServices.openUrl(QUrl("https://github.com/lutraconsulting/serval/blob/master/Serval/docs/user_manual.md"))

    def recreate_spatial_index(self, layer):
        """Check if spatial index exists for the layer and if it is relatively old and eventually recreate it."""
        ctime = self.spatial_index_time[layer.id()] if layer.id() in self.spatial_index_time else None
        if ctime is None or datetime.now() - ctime > timedelta(seconds=30):
            self.spatial_index = QgsSpatialIndex(layer.getFeatures(), None, QgsSpatialIndex.FlagStoreFeatureGeometries)
            self.spatial_index_time[layer.id()] = datetime.now()

    def get_nearest_feature(self, pt_feat, vlayer_id):
        """Given the point feature, return nearest feature from vlayer."""
        vlayer = self.project.mapLayer(vlayer_id)
        self.recreate_spatial_index(vlayer)
        ptxy = pt_feat.geometry().asPoint()
        near_fid = self.spatial_index.nearestNeighbor(ptxy)[0]
        return vlayer.getFeature(near_fid)

    def nearest_feature_attr_value(self, pt_feat, vlayer_id, attr_name):
        """Find nearest feature to pt_feat and return its attr_name attribute value."""
        near_feat = self.get_nearest_feature(pt_feat, vlayer_id)
        return near_feat[attr_name]

    def nearest_pt_on_line_interpolate_z(self, pt_feat, vlayer_id):
        """Find nearest line feature to pt_feat and interpolate z value from vertices."""
        near_feat = self.get_nearest_feature(pt_feat, vlayer_id)
        near_geom = near_feat.geometry()
        closest_pt_dist = near_geom.lineLocatePoint(pt_feat.geometry())
        closest_pt = near_geom.interpolate(closest_pt_dist)
        return closest_pt.get().z()

    def intersecting_features_attr_average(self, pt_feat, vlayer_id, attr_name, only_center):
        """
        Find all features intersecting current feature (cell center, or raster cell polygon) and calculate average
        value of their attr_name attribute.
        """
        vlayer = self.project.mapLayer(vlayer_id)
        self.recreate_spatial_index(vlayer)
        ptxy = pt_feat.geometry().asPoint()
        pt_x, pt_y = ptxy.x(), ptxy.y()
        dxy = 0.001
        half_pix_x = self.handler.pixel_size_x / 2.
        half_pix_y = self.handler.pixel_size_y / 2.
        if only_center:
            cell = QgsRectangle(pt_x, pt_y, pt_x + dxy, pt_y + dxy)
        else:
            cell = QgsRectangle(pt_x - half_pix_x, pt_y - half_pix_y,
                                pt_x + half_pix_x, pt_y + half_pix_y)
        inter_fids = self.spatial_index.intersects(cell)
        values = []
        for fid in inter_fids:
            feat = vlayer.getFeature(fid)
            if not feat.geometry().intersects(cell):
                continue
            val = feat[attr_name]
            if not is_number(val):
                continue
            values.append(val)
        if len(values) == 0:
            return None
        return sum(values) / float(len(values))

    def interpolate_from_mesh(self, pt_feat, mesh_layer_id, group, dataset, above_existing):
        """Interpolate from mesh."""
        mesh_layer = self.project.mapLayer(mesh_layer_id)
        ptxy = pt_feat.geometry().asPoint()
        dataset_val = mesh_layer.datasetValue(QgsMeshDatasetIndex(group, dataset), ptxy)
        val = dataset_val.scalar()
        if math.isnan(val):
            return val
        if above_existing:
            ident_vals = self.handler.provider.identify(ptxy, QgsRaster.IdentifyFormatValue).results()
            org_val = list(ident_vals.values())[0]
            if org_val == self.handler.nodata_values[0]:
                return val
            return max(org_val, val)
        else:
            return val
    class SequenceGenerator:
        def __init__(self,
                     centroid_layer,
                     trajectory_layer,
                     feedback,
                     timezone,
                     weight_field=None):
            centroids = [f for f in centroid_layer.getFeatures()]
            self.cell_index = QgsSpatialIndex()
            for f in centroids:
                self.cell_index.insertFeature(f)
            self.id_to_centroid = {
                f.id(): [f, [0, 0, 0, 0, 0]]
                for (f) in centroids
            }
            self.timezone = timezone
            self.weight_field = weight_field
            if weight_field is not None:
                self.weightIdx = trajectory_layer.fields().indexFromName(
                    weight_field)
            else:
                self.weightIdx = None
            self.sequences = {}

            n_traj = float(trajectory_layer.featureCount())
            for i, traj in enumerate(trajectory_layer.getFeatures()):
                self.evaluate_trajectory(traj)
                feedback.setProgress(i / n_traj * 100)

        def evaluate_trajectory(self, trajectory):
            points = trajectory.geometry().asPolyline()
            this_sequence = []
            weight = 1 if self.weight_field is None else trajectory.attributes(
            )[self.weightIdx]
            prev_cell_id = None
            for i, pt in enumerate(points):
                nn_id = self.cell_index.nearestNeighbor(pt, 1)[0]
                nearest_cell = self.id_to_centroid[nn_id][0]
                nearest_cell_id = nearest_cell.id()
                if len(this_sequence) >= 1:
                    prev_cell_id = this_sequence[-1]
                    if nearest_cell_id != prev_cell_id:
                        if (prev_cell_id, nearest_cell_id) in self.sequences:
                            self.sequences[(prev_cell_id,
                                            nearest_cell_id)] += weight
                        else:
                            self.sequences[(prev_cell_id,
                                            nearest_cell_id)] = weight
                if nearest_cell_id != prev_cell_id:
                    # we have changed to a new cell --> up the counter
                    m = trajectory.geometry().vertexAt(i).m()
                    if math.isnan(m):
                        m = 0
                    t = datetime(1970, 1, 1) + timedelta(
                        seconds=m) + timedelta(hours=self.timezone)
                    h = t.hour
                    self.id_to_centroid[nn_id][1][0] += weight
                    self.id_to_centroid[nn_id][1][int(h / 6) + 1] += weight
                    this_sequence.append(nearest_cell_id)

        def create_flow_lines(self):
            lines = []
            for key, value in self.sequences.items():
                p1 = self.id_to_centroid[key[0]][0].geometry().asPoint()
                p2 = self.id_to_centroid[key[1]][0].geometry().asPoint()
                p1 = QgsPoint(p1.x(), p1.y())
                p2 = QgsPoint(p2.x(), p2.y())
                feat = QgsFeature()
                feat.setGeometry(QgsGeometry.fromPolyline([p1, p2]))
                feat.setAttributes([key[0], key[1], value])
                lines.append(feat)
            return lines
Exemple #34
0
    def processAlgorithm(self, parameters, context, feedback):

        try:
            import processing as st
            import networkx as nx
        except Exception as e:
            feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))
            feedback.reportError(QCoreApplication.translate('Error',' '))
            feedback.reportError(QCoreApplication.translate('Error','Error loading modules - please install the necessary dependencies'))
            return {}

        Network = self.parameterAsLayer(parameters, self.Network, context)
        Sources = self.parameterAsLayer(parameters, self.Sources, context)
        d = parameters[self.lineDen]

        Precision = 6

        explode = st.run("native:explodelines",{'INPUT':Network,'OUTPUT':'memory:'},context=context,feedback=feedback)

        wF = parameters[self.Weight]

        fet = QgsFeature()
        fs = QgsFields()

        skip = ['Distance']
        fields = Network.fields()
        for field in fields:
            if field.name() not in skip:
                fs.append( QgsField(field.name() ,field.type() ))

        fs.append(QgsField('Distance', QVariant.Double))

        (writer, dest_id) = self.parameterAsSink(parameters, self.SP, context,
                                            fs, QgsWkbTypes.LineString, Network.sourceCrs())

        index = QgsSpatialIndex(explode['OUTPUT'].getFeatures())
        orig_data = {feature.id():feature for feature in explode['OUTPUT'].getFeatures()}

        srcs,data = [],{}

        if d > 0 and Sources.geometryType() == 1:
            params = {'INPUT':Sources,'INTERVAL':d,'OUTPUT':'memory:'}
            densify = st.run("native:densifygeometriesgivenaninterval",params,context=context,feedback=feedback)
            infc = densify['OUTPUT']
        else:
            infc = Sources

        params = {'INPUT':infc,'OUTPUT':'memory:'}  #Create nodes
        vertices = st.run('native:extractvertices',params,context=context,feedback=feedback)

        G = nx.Graph()

        feedback.pushInfo(QCoreApplication.translate('Model','Defining Source Nodes'))
        total = 100.0/vertices['OUTPUT'].featureCount()
        for enum,feature in enumerate(vertices['OUTPUT'].getFeatures()): #Find source node
            try:
                if total > 0:
                    feedback.setProgress(int(enum*total))

                pnt = feature.geometry().asPoint()

                startx,starty = (round(pnt.x(),Precision),round(pnt.y(),Precision))

                featFIDs = index.nearestNeighbor(QgsPointXY(startx,starty), 2)

                d = 1e10
                for FID in featFIDs:
                    feature2 = orig_data[FID]
                    testGeom = QgsGeometry.fromPointXY(QgsPointXY(startx,starty))
                    dist = QgsGeometry.distance(testGeom,feature2.geometry())

                    if dist < d: #Find closest vertex in graph to source point
                        d = dist
                        geom = feature2.geometry()

                        start,end = geom.asPolyline()

                        startx2,starty2 = (round(start.x(),Precision),round(start.y(),Precision))
                        endx2,endy2 = (round(end.x(),Precision),round(end.y(),Precision))

                        testGeom2 = QgsGeometry.fromPointXY(QgsPointXY(startx2,starty2))
                        testGeom3 = QgsGeometry.fromPointXY(QgsPointXY(endx2,endy2))
                        near = QgsGeometry.distance(testGeom2,testGeom)
                        near2 = QgsGeometry.distance(testGeom3,testGeom)
                        if near < near2:
                            x,y = startx2,starty2
                        else:
                            x,y = endx2,endy2

                dx = startx - x
                dy =  starty - y
                w = math.sqrt((dx**2)+(dy**2))
                srcs.append((startx,starty)) #srcs.append((x,y))
                G.add_edge((startx,starty),(x,y),weight=w)
                G.add_edge((x,y),(startx,starty),weight=w)

            except Exception as e:
                feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))

        total = 100.0/explode['OUTPUT'].featureCount()

        feedback.pushInfo(QCoreApplication.translate('Model','Building Graph'))
        for enum,feature in enumerate(explode['OUTPUT'].getFeatures()): #Build Graph
            try:
                if total > 0:
                    feedback.setProgress(int(enum*total))

                geom = feature.geometry()
                if geom.isMultipart():
                    start,end = geom.asMultiPolyline()[0]
                else:
                    start,end = geom.asPolyline()

                startx,starty = (round(start.x(),Precision),round(start.y(),Precision))
                endx,endy = (round(end.x(),Precision),round(end.y(),Precision))

                if wF:
                    w = float(feature[wF])*feature.geometry().length()
                else:
                    w = feature.geometry().length()

                G.add_edge((startx,starty),(endx,endy),weight=w)
                #G.add_edge((endx,endy),(startx,starty),weight=w)

                rows = []
                for field in fields:
                    if field.name() not in skip:
                        rows.append(feature[field.name()])
                data[((endx,endy),(startx,starty))] = rows

            except Exception as e:
                feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))

        feedback.pushInfo(QCoreApplication.translate('Model','Creating Fracture Network'))
        try:
            if len(srcs) > 0:
                total = 100.0/len(srcs)
                lengths = None
                for enum,source in enumerate(srcs):
                    feedback.setProgress(int(enum*total))

                    if G.has_node(source):
                        if lengths != None:
                            lengths2 = nx.single_source_dijkstra_path_length(G,source)
                            for k in lengths2.keys():
                                if k in lengths:
                                    v = lengths2[k]
                                    v2 = lengths[k]
                                    if v < v2:
                                        lengths[k] = v
                                else:
                                    lengths[k] = lengths2[k]
                            del lengths2
                        else:
                            lengths = nx.single_source_dijkstra_path_length(G,source)

                if lengths: #if connection exists

                    for edge in G.edges():
                        L = -9999

                        for node in edge:

                            if node in lengths:
                                dist = lengths[node]

                                if L == -9999:
                                    L = dist
                                    prev = node

                                elif dist < L:
                                    L = dist
                                    midS = node
                                    midE = prev

                                else:
                                    midS = prev
                                    midE = node

                        if L != -9999: # Check if there is a connection

                            if (edge[1],edge[0]) in data:
                                rows = data[(edge[1],edge[0])]
                            elif (edge[0],edge[1]) in data:
                                rows = data[(edge[0],edge[1])]
                            else:
                                continue

                            midEx,midEy = midE
                            midSx,midSy = midS
                            points = [QgsPointXY(midSx,midSy),QgsPointXY(midEx,midEy)]

                            outGeom = QgsGeometry.fromPolylineXY(points)
                            fet.setGeometry(outGeom)

                            rows.extend([float(L)])

                            fet.setAttributes(rows)
                            writer.addFeature(fet)

        except Exception as e:
            feedback.reportError(QCoreApplication.translate('Error','%s'%(e)))

        return {self.SP:dest_id}