예제 #1
0
class LayerIndex:
    
    def __init__(self, layer):
        self.__layer = layer        
        self.__index = QgsSpatialIndex()
        feats = vector.features(layer)
        for ft in feats:
            self.__index.insertFeature(ft)
        
    def contains(self, point):
        """Return true if the point intersects the layer"""
        intersects = self.__index.intersects(point.boundingBox())
        for i in intersects:
            request = QgsFeatureRequest().setFilterFid(i)
            feat = self.__layer.getFeatures(request).next()
            tmpGeom = QgsGeometry(feat.geometry())
            if point.intersects(tmpGeom):
                return True
        return False
    
    def countIntersection(self,bufferGeom,nb):
        """Return true if the buffer intersects enough entities"""
        count = 0
        intersects = self.__index.intersects(bufferGeom.boundingBox())
        for i in intersects:
            request = QgsFeatureRequest().setFilterFid(i)
            feat = self.__layer.getFeatures(request).next()
            tmpGeom = QgsGeometry(feat.geometry())
            if bufferGeom.intersects(tmpGeom):
                count += 1
                if count >= nb:
                    return True
        return False
예제 #2
0
    def poly2nb(self):
        lst = []

        index = QgsSpatialIndex()
        featsA = self.lyr.getFeatures()
        featsB = self.lyr.getFeatures()
        for ft in featsA:
            index.insertFeature(ft)

        featB = QgsFeature()
        prv = self.lyr.dataProvider()
        while featsB.nextFeature(featB):
            geomB = featB.constGeometry()
            idb = featB.id()
            idxs = index.intersects(geomB.boundingBox())
            sor = []
            for idx in idxs:
                rqst = QgsFeatureRequest().setFilterFid(idx)
                featA = prv.getFeatures(rqst).next()
                ida = featA.id()
                geomA = QgsGeometry(featA.geometry())
                if idb!=ida:
                    if geomB.touches(geomA)==True:
                        sor.append(ida)

            lst.append(sor)

        return lst
예제 #3
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context)

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

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

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

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

        index = QgsSpatialIndex()
        points = dict()

        random.seed()

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

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

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

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

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

        return {self.OUTPUT: dest_id}
예제 #4
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
예제 #5
0
class TriangleMesh:

  # 0 - 3
  # | / |
  # 1 - 2

  def __init__(self, xmin, ymin, xmax, ymax, x_segments, y_segments):
    self.flen = 0
    self.quadrangles = []
    self.spatial_index = QgsSpatialIndex()

    xres = (xmax - xmin) / x_segments
    yres = (ymax - ymin) / y_segments
    for y in range(y_segments):
      for x in range(x_segments):
        pt0 = QgsPoint(xmin + x * xres, ymax - y * yres)
        pt1 = QgsPoint(xmin + x * xres, ymax - (y + 1) * yres)
        pt2 = QgsPoint(xmin + (x + 1) * xres, ymax - (y + 1) * yres)
        pt3 = QgsPoint(xmin + (x + 1) * xres, ymax - y * yres)
        self._addQuadrangle(pt0, pt1, pt2, pt3)

  def _addQuadrangle(self, pt0, pt1, pt2, pt3):
    f = QgsFeature(self.flen)
    f.setGeometry(QgsGeometry.fromPolygon([[pt0, pt1, pt2, pt3, pt0]]))
    self.quadrangles.append(f)
    self.spatial_index.insertFeature(f)
    self.flen += 1

  def intersects(self, geom):
    for fid in self.spatial_index.intersects(geom.boundingBox()):
      quad = self.quadrangles[fid].geometry()
      if quad.intersects(geom):
        yield quad

  def splitPolygons(self, geom):
    for quad in self.intersects(geom):
      pts = quad.asPolygon()[0]
      tris = [[[pts[0], pts[1], pts[3], pts[0]]], [[pts[3], pts[1], pts[2], pts[3]]]]
      if geom.contains(quad):
        yield tris[0]
        yield tris[1]
      else:
        for i, tri in enumerate(map(QgsGeometry.fromPolygon, tris)):
          if geom.contains(tri):
            yield tris[i]
          elif geom.intersects(tri):
            poly = geom.intersection(tri)
            if poly.isMultipart():
              for sp in poly.asMultiPolygon():
                yield sp
            else:
              yield poly.asPolygon()
예제 #6
0
    def compute_graph(features, feedback, create_id_graph=False, min_distance=0):
        """ compute topology from a layer/field """
        s = Graph(sort_graph=False)
        id_graph = None
        if create_id_graph:
            id_graph = Graph(sort_graph=True)

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

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

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

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

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

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

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

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

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

        return s, id_graph
예제 #7
0
class LayerIndex(object):
    """Check an intersection between a QgsGeometry and a QgsVectorLayer."""

    def __init__(self, layer):
        self.__layer = layer

        if QGis.QGIS_VERSION_INT >= 20700:
            self.__index = QgsSpatialIndex(layer.getFeatures())
        else:
            self.__index = QgsSpatialIndex()
            for ft in layer.getFeatures():
                self.__index.insertFeature(ft)

    def contains(self, point):
        """Return true if the point intersects the layer."""
        intersects = self.__index.intersects(point.boundingBox())
        for i in intersects:
            request = QgsFeatureRequest().setFilterFid(i)
            feat = self.__layer.getFeatures(request).next()
            if point.intersects(QgsGeometry(feat.geometry())):
                return True
        return False

    def count_intersection(self, buffer_geom, nb):
        """Return true if the buffer intersects enough entities."""
        count = 0
        intersects = self.__index.intersects(buffer_geom.boundingBox())
        for i in intersects:
            request = QgsFeatureRequest().setFilterFid(i)
            feat = self.__layer.getFeatures(request).next()

            if buffer_geom.intersects(QgsGeometry(feat.geometry())):
                count += 1
                if count >= nb:
                    return True
        return False
예제 #8
0
파일: Difference.py 프로젝트: CS-SI/QGIS
    def processAlgorithm(self, parameters, context, feedback):
        sourceA = self.parameterAsSource(parameters, self.INPUT, context)
        sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)

        geomType = QgsWkbTypes.multiType(sourceA.wkbType())

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               sourceA.fields(), geomType, sourceA.sourceCrs())

        featB = QgsFeature()
        outFeat = QgsFeature()

        indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs(), context.transformContext())), feedback)

        total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1
        count = 0

        for featA in sourceA.getFeatures():
            if feedback.isCanceled():
                break

            if featA.hasGeometry():
                geom = featA.geometry()
                diffGeom = QgsGeometry(geom)
                attrs = featA.attributes()
                intersects = indexB.intersects(geom.boundingBox())
                request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([])
                request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext())
                for featB in sourceB.getFeatures(request):
                    if feedback.isCanceled():
                        break
                    tmpGeom = featB.geometry()
                    if diffGeom.intersects(tmpGeom):
                        diffGeom = QgsGeometry(diffGeom.difference(tmpGeom))

                outFeat.setGeometry(diffGeom)
                outFeat.setAttributes(attrs)
                sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
            else:
                sink.addFeature(featA, QgsFeatureSink.FastInsert)

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

        return {self.OUTPUT: dest_id}
예제 #9
0
    def processAlgorithm(self, progress):
        fileName = self.getParameterValue(self.INPUT)
        layer = dataobjects.getObjectFromUri(fileName)
        fieldName = self.getParameterValue(self.FIELD)
        value = self.getParameterValue(self.VALUE)

        selected = layer.selectedFeaturesIds()
        if len(selected) == 0:
            GeoAlgorithmExecutionException(
                self.tr('There is no selection in the input layer. '
                        'Select one feature and try again.'))

        ft = layer.selectedFeatures()[0]
        geom = ft.geometry()
        attrSum = ft[fieldName]

        idx = QgsSpatialIndex(layer.getFeatures(QgsFeatureRequest.setSubsetOfAttributes([])))
        req = QgsFeatureRequest()
        completed = False
        while not completed:
            intersected = idx.intersects(geom.boundingBox())
            if len(intersected) < 0:
                progress.setInfo(self.tr('No adjacent features found.'))
                break

            req = QgsFeatureRequest().setFilterFids(intersected).setSubsetOfAttributes([fieldName], layer.fields())
            for ft in layer.getFeatures(req):
                tmpGeom = ft.geometry()
                if tmpGeom.touches(geom):
                    geom = tmpGeom.combine(geom)
                    selected.append(i)
                    attrSum += ft[fieldName]
                    if attrSum >= value:
                        completed = True
                        break

        layer.selectByIds(selected)
        self.setOutputValue(self.OUTPUT, fileName)
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()
예제 #11
0
    def processAlgorithm(self, parameters, context, feedback):
        sourceA = self.parameterAsSource(parameters, self.INPUT, context)
        sourceB = self.parameterAsSource(parameters, self.INTERSECT, context)

        fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context)
        fieldsB = self.parameterAsFields(parameters, self.INTERSECT_FIELDS, context)

        fieldListA = QgsFields()
        field_indices_a = []
        if len(fieldsA) > 0:
            for f in fieldsA:
                idxA = sourceA.fields().lookupField(f)
                if idxA >= 0:
                    field_indices_a.append(idxA)
                    fieldListA.append(sourceA.fields()[idxA])
        else:
            fieldListA = sourceA.fields()
            field_indices_a = [i for i in range(0, fieldListA.count())]

        fieldListB = QgsFields()
        field_indices_b = []
        if len(fieldsB) > 0:
            for f in fieldsB:
                idxB = sourceB.fields().lookupField(f)
                if idxB >= 0:
                    field_indices_b.append(idxB)
                    fieldListB.append(sourceB.fields()[idxB])
        else:
            fieldListB = sourceB.fields()
            field_indices_b = [i for i in range(0, fieldListB.count())]

        fieldListB = vector.testForUniqueness(fieldListA, fieldListB)
        for b in fieldListB:
            fieldListA.append(b)

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fieldListA, QgsWkbTypes.Point, sourceA.sourceCrs())

        spatialIndex = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback)

        outFeat = QgsFeature()
        features = sourceA.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(field_indices_a))
        total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 0
        for current, inFeatA in enumerate(features):
            if feedback.isCanceled():
                break

            if not inFeatA.hasGeometry():
                continue

            inGeom = inFeatA.geometry()
            has_intersections = False
            lines = spatialIndex.intersects(inGeom.boundingBox())

            engine = None
            if len(lines) > 0:
                has_intersections = True
                # use prepared geometries for faster intersection tests
                engine = QgsGeometry.createGeometryEngine(inGeom.geometry())
                engine.prepareGeometry()

            if has_intersections:
                request = QgsFeatureRequest().setFilterFids(lines)
                request.setDestinationCrs(sourceA.sourceCrs())
                request.setSubsetOfAttributes(field_indices_b)

                for inFeatB in sourceB.getFeatures(request):
                    if feedback.isCanceled():
                        break

                    tmpGeom = inFeatB.geometry()

                    points = []
                    if engine.intersects(tmpGeom.geometry()):
                        tempGeom = inGeom.intersection(tmpGeom)
                        out_attributes = [inFeatA.attributes()[i] for i in field_indices_a]
                        out_attributes.extend([inFeatB.attributes()[i] for i in field_indices_b])
                        if tempGeom.type() == QgsWkbTypes.PointGeometry:
                            if tempGeom.isMultipart():
                                points = tempGeom.asMultiPoint()
                            else:
                                points.append(tempGeom.asPoint())

                            for j in points:
                                outFeat.setGeometry(tempGeom.fromPoint(j))
                                outFeat.setAttributes(out_attributes)
                                sink.addFeature(outFeat, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
예제 #12
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
예제 #13
0
    def get_pair_boundary_plot(self,
                               boundary_layer,
                               plot_layer,
                               id_field,
                               use_selection=True):
        id_field_idx = plot_layer.fields().indexFromName(id_field)
        request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx])
        polygons = plot_layer.getSelectedFeatures(
            request) if use_selection else plot_layer.getFeatures(request)
        intersect_more_pairs = list()
        intersect_less_pairs = list()

        if boundary_layer.featureCount() == 0:
            return (intersect_more_pairs, intersect_less_pairs)

        id_field_idx = boundary_layer.fields().indexFromName(id_field)
        request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx])
        dict_features = {
            feature.id(): feature
            for feature in boundary_layer.getFeatures(request)
        }
        index = QgsSpatialIndex(boundary_layer)
        candidate_features = None

        for polygon in polygons:
            bbox = polygon.geometry().boundingBox()
            bbox.scale(1.001)
            candidates_ids = index.intersects(bbox)

            candidate_features = [
                dict_features[candidate_id] for candidate_id in candidates_ids
            ]

            for candidate_feature in candidate_features:
                polygon_geom = polygon.geometry()
                is_multipart = polygon_geom.isMultipart()
                candidate_geometry = candidate_feature.geometry()

                if polygon_geom.intersects(candidate_geometry):
                    # Does the current multipolygon have inner rings?
                    has_inner_rings = False
                    multi_polygon = None
                    single_polygon = None

                    if is_multipart:
                        multi_polygon = polygon_geom.get()
                        for part in range(multi_polygon.numGeometries()):
                            if multi_polygon.ringCount(part) > 1:
                                has_inner_rings = True
                                break
                    else:
                        single_polygon = polygon_geom.get()
                        if single_polygon.numInteriorRings() > 0:
                            has_inner_rings = True

                    # Now we'll test intersections against borders
                    if has_inner_rings:
                        # In this case we need to identify whether the
                        # intersection is with outer rings (goes to MOREBFS
                        # table) or with inner rings (goes to LESS table)
                        multi_outer_rings = QgsMultiLineString()
                        multi_inner_rings = QgsMultiLineString()

                        if is_multipart and multi_polygon:
                            for i in range(multi_polygon.numGeometries()):
                                temp_polygon = multi_polygon.geometryN(i)
                                multi_outer_rings.addGeometry(
                                    temp_polygon.exteriorRing().clone())
                                for j in range(
                                        temp_polygon.numInteriorRings()):
                                    multi_inner_rings.addGeometry(
                                        temp_polygon.interiorRing(j).clone())

                        elif not is_multipart and single_polygon:
                            multi_outer_rings.addGeometry(
                                single_polygon.exteriorRing().clone())
                            for j in range(single_polygon.numInteriorRings()):
                                multi_inner_rings.addGeometry(
                                    single_polygon.interiorRing(j).clone())

                        intersection_type = QgsGeometry(
                            multi_outer_rings).intersection(
                                candidate_geometry).type()
                        if intersection_type == QgsWkbTypes.LineGeometry:
                            intersect_more_pairs.append(
                                (polygon[id_field],
                                 candidate_feature[id_field]))
                        else:
                            self.logger.warning(
                                __name__,
                                "(MoreBFS) Intersection between plot (t_id={}) and boundary (t_id={}) is a geometry of type: {}"
                                .format(polygon[id_field],
                                        candidate_feature[id_field],
                                        intersection_type))

                        intersection_type = QgsGeometry(
                            multi_inner_rings).intersection(
                                candidate_geometry).type()
                        if intersection_type == QgsWkbTypes.LineGeometry:
                            intersect_less_pairs.append(
                                (polygon[id_field],
                                 candidate_feature[id_field]))
                        else:
                            self.logger.warning(
                                __name__,
                                "(Less) Intersection between plot (t_id={}) and boundary (t_id={}) is a geometry of type: {}"
                                .format(polygon[id_field],
                                        candidate_feature[id_field],
                                        intersection_type))

                    else:
                        boundary = None
                        if is_multipart and multi_polygon:
                            boundary = multi_polygon.boundary()
                        elif not is_multipart and single_polygon:
                            boundary = single_polygon.boundary()

                        intersection_type = QgsGeometry(boundary).intersection(
                            candidate_geometry).type()
                        if boundary and intersection_type == QgsWkbTypes.LineGeometry:
                            intersect_more_pairs.append(
                                (polygon[id_field],
                                 candidate_feature[id_field]))
                        else:
                            self.logger.warning(
                                __name__,
                                "(MoreBFS) Intersection between plot (t_id={}) and boundary (t_id={}) is a geometry of type: {}"
                                .format(polygon[id_field],
                                        candidate_feature[id_field],
                                        intersection_type))
        # free up memory
        del candidate_features
        del dict_features
        gc.collect()
        return (intersect_more_pairs, intersect_less_pairs)
예제 #14
0
class DsgGeometrySnapper(QObject):
    SnappedToRefNode, SnappedToRefSegment, Unsnapped = range(3)
    PreferNodes, PreferClosest = range(2)

    featureSnapped = pyqtSignal()

    def __init__(self, referenceLayer):
        """
        Constructor
        :param referenceLayer: QgsVectorLayer
        """
        super(self.__class__,self).__init__()
        self.referenceLayer = referenceLayer
        # Build spatial index
        self.index = QgsSpatialIndex(self.referenceLayer.getFeatures())
        
    def polyLineSize(self, geom, iPart, iRing):
        """
        Gets the number of vertexes
        :param geom: QgsAbstractGeometryV2
        :param iPart: int
        :param iRing: int
        :return:
        """
        nVerts = geom.vertexCount( iPart, iRing)
        if isinstance(geom, QgsMultiPolygonV2) or isinstance(geom, QgsPolygonV2) or isinstance(geom, QgsCircularStringV2):
            front = geom.vertexAt(QgsVertexId( iPart, iRing, 0, QgsVertexId.SegmentVertex))
            back = geom.vertexAt(QgsVertexId( iPart, iRing, nVerts - 1, QgsVertexId.SegmentVertex))
            if front == back:
                return nVerts - 1
        return nVerts

    def snapFeatures(self, features, snapTolerance, mode=PreferNodes):
        """
        Snap features from a layer
        :param features: list of QgsFeatures
        :param snapTolerance: float
        :param mode: DsgGeometrySnapper.PreferNodes or DsgGeometrySnapper.PreferClosest
        :return:
        """
        for feature in features:
            self.processFeature(feature, snapTolerance, mode)
            self.featureSnapped.emit()
        return features

    def processFeature(self, feature, snapTolerance, mode):
        """
        Process QgsFeature
        :param feature: QgsFeature
        :param snapTolerance: float
        :param mode: DsgGeometrySnapper.PreferNodes or DsgGeometrySnapper.PreferClosest
        :return:
        """
        if feature.geometry():
            feature.setGeometry(self.snapGeometry(feature.geometry(), snapTolerance, mode))
    
    def projPointOnSegment(self, p, s1, s2):
        """
        p: QgsPointV2
        s1: QgsPointV2 of segment
        s2: QgsPointV2 of segment
        """
        nx = s2.y() - s1.y()
        ny = -( s2.x() - s1.x() )
        a = ( p.x() * ny - p.y() * nx - s1.x() * ny + s1.y() * nx )
        b = ( ( s2.x() - s1.x() ) * ny - ( s2.y() - s1.y() ) * nx )
        if s1 == s2:
            return s1
        t = a / b
        if t < 0.:
            return s1
        elif t > 1.:
            return s2
        else:
            return QgsPointV2( s1.x() + ( s2.x() - s1.x() ) * t, s1.y() + ( s2.y() - s1.y() ) * t )

    def buildReferenceIndex(self, segments):
        refDict = {}
        index = QgsSpatialIndex()
        for i, segment in enumerate(segments):
            refDict[i] = segment
            feature = QgsFeature(i)
            feature.setGeometry(segment)
            index.insertFeature(feature)
        return refDict, index

    def segmentFromPoints(self, start, end):
        """
        Makes a QgsGeometry from start and end points
        :param start: QgsPoint
        :param end: QgsPoint
        :return:
        """
        return QgsGeometry.fromPolyline([start, end])

    def breakQgsGeometryIntoSegments(self, geometry):
        """
        Makes a list of QgsGeometry made with segments
        :param geometry: QgsGeometry
        :return: list of QgsGeometry
        """
        segments = []
        wbkType = geometry.wkbType()
        if wbkType == QGis.WKBPoint:
            return [geometry]
        elif wbkType == QGis.WKBMultiPoint:
            return [geometry]
        elif wbkType == QGis.WKBLineString:
            line = geometry.asPolyline()
            for i in xrange(len(line) - 1):
                segments.append(self.segmentFromPoints(line[i], line[i + 1]))
        elif wbkType == QGis.WKBMultiLineString:
            multiLine = geometry.asMultiPolyline()
            for j in xrange(len(multiLine)):
                line = multiLine[j]
                for i in xrange(len(line) - 1):
                    segments.append(self.segmentFromPoints(line[i], line[i + 1]))
        elif wbkType == QGis.WKBPolygon:
            poly = geometry.asPolygon()
            for j in xrange(len(poly)):
                line = poly[j]
                for i in xrange(len(line) - 1):
                    segments.append(self.segmentFromPoints(line[i], line[i + 1]))
        elif wbkType == QGis.WKBMultiPolygon:
            multiPoly = geometry.asMultiPolygon()
            for k in xrange(len(multiPoly)):
                poly = multiPoly[k]
                for j in xrange(len(poly)):
                    line = poly[j]
                    for i in xrange(len(line) - 1):
                        segments.append(self.segmentFromPoints(line[i], line[i + 1]))

        return segments

    def snapGeometry(self, geometry, snapTolerance, mode=PreferNodes):
        """
        Snaps a QgsGeometry in the reference layer
        :param geometry: QgsGeometry
        :param snapTolerance: float
        :param mode: DsgGeometrySnapper.PreferNodes or DsgGeometrySnapper.PreferClosest
        :return:
        """
        center = QgsPointV2(geometry.boundingBox().center())

        # Get potential reference features and construct snap index
        refGeometries = []
        searchBounds = geometry.boundingBox()
        searchBounds.grow(snapTolerance)
        # filter by bounding box to get candidates
        refFeatureIds = self.index.intersects(searchBounds)

        # End here in case we don't find candidates
        if len(refFeatureIds) == 0:
            return geometry

        # speeding up the process to consider only intersecting geometries
        refFeatureRequest = QgsFeatureRequest().setFilterFids(refFeatureIds)
        for refFeature in self.referenceLayer.getFeatures(refFeatureRequest):
            refGeometry = refFeature.geometry()
            segments = self.breakQgsGeometryIntoSegments(refGeometry)
            # testing intersection
            for segment in segments:
                if segment.intersects(searchBounds):
                    refGeometries.append(segment)

        # End here in case we don't find geometries
        if len(refGeometries) == 0:
            return geometry

        # building geometry index
        refDict, index = self.buildReferenceIndex(refGeometries)
        refSnapIndex = DsgSnapIndex(center, 10*snapTolerance)
        for geom in refGeometries:
            refSnapIndex.addGeometry(geom.geometry())

        # Snap geometries
        subjGeom = geometry.geometry().clone()
        subjPointFlags = []

        # Pass 1: snap vertices of subject geometry to reference vertices
        for iPart in xrange(subjGeom.partCount()):
            subjPointFlags.append([])
            for iRing in xrange(subjGeom.ringCount(iPart)):
                subjPointFlags[iPart].append([])
                for iVert in xrange(self.polyLineSize(subjGeom, iPart, iRing)):
                    vidx = QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex)
                    p = QgsPointV2(subjGeom.vertexAt(vidx))
                    pF = QgsPoint(p.toQPointF())
                    snapPoint, snapSegment = refSnapIndex.getSnapItem(p, snapTolerance)
                    success = snapPoint or snapSegment
                    if not success:
                        subjPointFlags[iPart][iRing].append(DsgGeometrySnapper.Unsnapped )
                    else:
                        if mode == DsgGeometrySnapper.PreferNodes:
                            # Prefer snapping to point
                            if snapPoint:
                                subjGeom.moveVertex(vidx, snapPoint.getSnapPoint(p))
                                subjPointFlags[iPart][iRing].append(DsgGeometrySnapper.SnappedToRefNode)
                            elif snapSegment:
                                subjGeom.moveVertex( vidx, snapSegment.getSnapPoint(p))
                                subjPointFlags[iPart][iRing].append(DsgGeometrySnapper.SnappedToRefSegment)
                        elif mode == DsgGeometrySnapper.PreferClosest:
                            nodeSnap = None
                            segmentSnap = None
                            distanceNode = sys.float_info.max
                            distanceSegment = sys.float_info.max
                            if snapPoint:
                                nodeSnap = snapPoint.getSnapPoint(p)
                                nodeSnapF = QgsPoint(nodeSnap.toQPointF())
                                distanceNode = nodeSnapF.sqrDist(pF)
                            if snapSegment:
                                segmentSnap = snapSegment.getSnapPoint(p)
                                segmentSnapF = QgsPoint(segmentSnap.toQPointF())
                                distanceSegment = segmentSnapF.sqrDist(pF)
                            if snapPoint and (distanceNode < distanceSegment):
                                subjGeom.moveVertex( vidx, nodeSnap )
                                subjPointFlags[iPart][iRing].append(DsgGeometrySnapper.SnappedToRefNode)
                            elif snapSegment:
                                subjGeom.moveVertex(vidx, segmentSnap)
                                subjPointFlags[iPart][iRing].append(DsgGeometrySnapper.SnappedToRefSegment)

        #nothing more to do for points
        if isinstance(subjGeom, QgsPointV2):
            return QgsGeometry(subjGeom)
        
        # SnapIndex for subject feature
        subjSnapIndex = DsgSnapIndex(center, 10*snapTolerance)
        subjSnapIndex.addGeometry(subjGeom)
        
        origSubjGeom = subjGeom.clone()
        origSubjSnapIndex = DsgSnapIndex(center, 10*snapTolerance)
        origSubjSnapIndex.addGeometry(origSubjGeom)
        
        # Pass 2: add missing vertices to subject geometry
        for refGeom in refGeometries:
            for iPart in xrange(refGeom.geometry().partCount()):
                for iRing in xrange(refGeom.geometry().ringCount(iPart)):
                    for iVert in xrange(self.polyLineSize(refGeom.geometry(), iPart, iRing)):
                        point = refGeom.geometry().vertexAt(QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex))
                        # QgsPoint used to calculate squared distance
                        pointF = QgsPoint(point.toQPointF())
                        snapPoint, snapSegment = subjSnapIndex.getSnapItem(point, snapTolerance)
                        success = snapPoint or snapSegment
                        if success:
                            # Snap to segment, unless a subject point was already snapped to the reference point
                            if snapPoint and (QgsPoint(snapPoint.getSnapPoint(point).toQPointF()).sqrDist(pointF) < 1E-16):
                                continue
                            elif snapSegment:
                                # Look if there is a closer reference segment, if so, ignore this point
                                pProj = snapSegment.getSnapPoint(point)
                                pProjF = QgsPoint(pProj.toQPointF())
                                closest = refSnapIndex.getClosestSnapToPoint(point, pProj)
                                closestF = QgsPoint(closest.toQPointF())
                                if pProjF.sqrDist(pointF) > pProjF.sqrDist(closestF):
                                    continue
                                # If we are too far away from the original geometry, do nothing
                                if not origSubjSnapIndex.getSnapItem(point, snapTolerance):
                                    continue
                                idx = snapSegment.idxFrom
                                subjGeom.insertVertex(QgsVertexId(idx.vidx.part, idx.vidx.ring, idx.vidx.vertex + 1, QgsVertexId.SegmentVertex), point)
                                subjPointFlags[idx.vidx.part][idx.vidx.ring].insert(idx.vidx.vertex + 1, DsgGeometrySnapper.SnappedToRefNode )
                                subjSnapIndex = DsgSnapIndex(center, 10*snapTolerance)
                                subjSnapIndex.addGeometry(subjGeom)

        # Pass 3: remove superfluous vertices: all vertices which are snapped to a segment and not preceded or succeeded by an unsnapped vertex
        for iPart in xrange(subjGeom.partCount()):
            for iRing in xrange(subjGeom.ringCount(iPart)):
                ringIsClosed = subjGeom.vertexAt(QgsVertexId(iPart, iRing, 0, QgsVertexId.SegmentVertex)) == subjGeom.vertexAt(QgsVertexId(iPart, iRing, subjGeom.vertexCount( iPart, iRing ) - 1, QgsVertexId.SegmentVertex))
                nVerts = self.polyLineSize(subjGeom, iPart, iRing)
                iVert = 0
                while iVert < nVerts:
                    iPrev = ( iVert - 1 + nVerts ) % nVerts
                    iNext = ( iVert + 1 ) % nVerts
                    pMid = subjGeom.vertexAt(QgsVertexId( iPart, iRing, iVert, QgsVertexId.SegmentVertex))
                    pPrev = subjGeom.vertexAt(QgsVertexId( iPart, iRing, iPrev, QgsVertexId.SegmentVertex))
                    pNext = subjGeom.vertexAt(QgsVertexId( iPart, iRing, iNext, QgsVertexId.SegmentVertex))

                    pointOnSeg = self.projPointOnSegment( pMid, pPrev, pNext)
                    pointOnSegF = QgsPoint(pointOnSeg.toQPointF())
                    pMidF = QgsPoint(pMid.toQPointF())
                    dist = pointOnSegF.sqrDist(pMidF)

                    if subjPointFlags[iPart][iRing][iVert] == DsgGeometrySnapper.SnappedToRefSegment \
                     and subjPointFlags[iPart][iRing][iPrev] != DsgGeometrySnapper.Unsnapped \
                     and subjPointFlags[iPart][iRing][iNext] != DsgGeometrySnapper.Unsnapped \
                     and dist < 1E-12:
                        if (ringIsClosed and nVerts > 3 ) or ( not ringIsClosed and nVerts > 2 ):
                            subjGeom.deleteVertex(QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex))
                            del subjPointFlags[iPart][iRing][iVert]
                            iVert -= 1
                            nVerts -= 1
                        else:
                            # Don't delete vertices if this would result in a degenerate geometry
                            break
                    iVert += 1
        return QgsGeometry(subjGeom)
예제 #15
0
    def enclaveRemover(self):
        field_id = self.activeLayer.fieldNameIndex(self.distfield)
        self.activeLayer.startEditing()
        # Create a dictionary of all features
        feature_dict = {f.id(): f for f in self.activeLayer.getFeatures()}

        QgsMessageLog.logMessage("Building spatial index...")
        # Build a spatial index
        index = QgsSpatialIndex()
        for f in feature_dict.values():
            index.insertFeature(f)

        QgsMessageLog.logMessage("Finding neighbors...")
        # Loop through all features and find features that touch each feature
        for f in feature_dict.values():
            geom = f.geometry()
            # Find all features that intersect the bounding box of the current feature.
            # We use spatial index to find the features intersecting the bounding box
            # of the current feature. This will narrow down the features that we need
            # to check neighboring features.
            intersecting_ids = index.intersects(geom.boundingBox())
            # Initalize neighbors list and sum
            neighbors = []
            neighbors_district = -1
            finished = 0
            if f[self.distfield] == 0:
                QgsMessageLog.logMessage("feature " + str(f.id()) +
                                         " with null distfield found!")
                while neighbors_district <> -2 and finished == 0:
                    finished = 0
                    for intersecting_id in intersecting_ids:
                        # Look up the feature from the dictionary
                        intersecting_f = feature_dict[intersecting_id]
                        QgsMessageLog.logMessage("Neighbor found!")
                        # For our purpose we consider a feature as 'neighbor' if it touches or
                        # intersects a feature. We use the 'disjoint' predicate to satisfy
                        # these conditions. So if a feature is not disjoint, it is a neighbor.
                        if (f != intersecting_f and
                                not intersecting_f.geometry().disjoint(geom)):
                            if intersecting_f[self.distfield] > 0:
                                QgsMessageLog.logMessage(
                                    "Neighbor found with > 0!")
                                if neighbors_district == -1:
                                    neighbors_district = intersecting_f[
                                        self.distfield]
                                    QgsMessageLog.logMessage(
                                        "neighbors_district set to " +
                                        str(neighbors_district))
                                elif neighbors_district != intersecting_f[
                                        self.distfield]:
                                    neighbors_district = -2
                                    QgsMessageLog.logMessage(
                                        "neighbors_district set to " +
                                        str(neighbors_district) + ", " +
                                        str(intersecting_f[self.distfield]) +
                                        " not matching")
                    if neighbors_district > 0:
                        QgsMessageLog.logMessage(
                            str(f.id()) + " updating district to " +
                            str(neighbors_district))
                        self.activeLayer.changeAttributeValue(
                            f.id(), field_id, neighbors_district)
                        # Update the layer with new attribute values.
                    finished = 1

        self.activeLayer.commitChanges()
예제 #16
0
class Network(object):
    def __init__(self, network_id, feature_source, index=True):
        self._id = network_id

        if isinstance(feature_source, dict):
            self._src = None
            self._edge_map = feature_source
        else:
            self._src = feature_source
            self._edge_map = {}

        self._edge_index = QgsSpatialIndex()
        self._edge_nodes = {}

        self._node_map = {}
        self._node_index = QgsSpatialIndex()
        self._node_edges = defaultdict(set)

        if index:
            self.build_indexes()

    def __repr__(self):
        return '<Network %s>' % (str(self._id), )

    def build_indexes(self, index_nodes=True, iterate=False):
        next_node_id = 1
        coords_node = {}
        if self._src is not None:
            request = QgsFeatureRequest()
            request.FetchAttributes = False
            self._edge_map = dict([(f.id(), f)
                                   for f in self._src.getFeatures(request)])
        total = len(self._edge_map)

        for (i, (fid, feature)) in enumerate(self._edge_map.items(), start=1):
            fid = feature.id()
            ls = feature.geometry().constGet()
            endpoints = []
            for point in [ls.startPoint(), ls.endPoint()]:
                coords = (point.x(), point.y())
                node_id = coords_node.get(coords, None)

                if node_id is None:
                    node_id = next_node_id
                    next_node_id += 1
                    coords_node[coords] = node_id
                    node_feature = self._make_node_feature(node_id, point)
                    self._node_map[node_id] = node_feature
                    if index_nodes:
                        self._node_index.insertFeature(node_feature)

                endpoints.append(node_id)
                self._node_edges[node_id].add(fid)

            self._edge_index.insertFeature(feature)
            self._edge_nodes[fid] = endpoints

            if iterate:
                yield float(i) / total

    def _make_node_feature(self, fid, point):
        feature = QgsFeature(fid)
        # TODO: Figure out why point.clone() does not work in the line below.
        # (It creates weird issues with the original geometry later in
        # the process.)
        feature.setGeometry(QgsGeometry(QgsPoint(point.x(), point.y())))
        return feature

    def _vertex_id(self, eid, nid):
        if self.get_edge_nids(eid)[0] == nid:
            return 0
        return self.get_edge(eid).geometry().constGet().nCoordinates() - 1

    def _node_angle(self, eid, nid):
        vid = self._vertex_id(eid, nid)
        angle = self.get_edge(eid).geometry().angleAtVertex(vid) \
            * 180 / math.pi
        if vid > 0:
            angle += 180 if angle < 180 else -180
        return angle

    def eids(self):
        return self._edge_map.keys()

    def nids(self):
        return self._node_map.keys()

    def get_edge(self, eid):
        return self._edge_map[eid]

    def get_node(self, nid):
        return self._node_map[nid]

    def find_nids(self, bbox):
        return self._node_index.intersects(bbox)

    def find_eids(self, bbox):
        return self._edge_index.intersects(bbox)

    def get_edge_nids(self, eid):
        return self._edge_nodes[eid]

    def get_node_eids(self, nid):
        return self._node_edges[nid]

    def get_other_nid(self, eid, nid):
        nids = self._edge_nodes[eid]
        if nids[0] == nid:
            return nids[1]
        return nids[0]

    def get_node_angles(self, nid):
        eids = self.get_node_eids(nid)
        return dict([(eid, self._node_angle(eid, nid)) for eid in eids])

    def is_loop(self, eid):
        return len(set(self.get_edge_nids(eid))) == 1
예제 #17
0
    def run(self):
        """Experimental impact function."""
        self.validate()
        self.prepare()

        # Get parameters from layer's keywords
        self.hazard_class_attribute = self.hazard.keyword("field")
        self.hazard_class_mapping = self.hazard.keyword("value_map")
        self.exposure_class_attribute = self.exposure.keyword("structure_class_field")

        # Prepare Hazard Layer
        hazard_provider = self.hazard.layer.dataProvider()

        # Check affected field exists in the hazard layer
        affected_field_index = hazard_provider.fieldNameIndex(self.hazard_class_attribute)
        if affected_field_index == -1:
            message = (
                tr(
                    'Field "%s" is not present in the attribute table of the '
                    "hazard layer. Please change the Affected Field parameter in "
                    "the IF Option."
                )
                % self.hazard_class_attribute
            )
            raise GetDataError(message)

        srs = self.exposure.layer.crs().toWkt()
        exposure_provider = self.exposure.layer.dataProvider()
        exposure_fields = exposure_provider.fields()

        # Check self.exposure_class_attribute exists in exposure layer
        building_type_field_index = exposure_provider.fieldNameIndex(self.exposure_class_attribute)
        if building_type_field_index == -1:
            message = (
                tr(
                    'Field "%s" is not present in the attribute table of '
                    "the exposure layer. Please change the Building Type "
                    "Field parameter in the IF Option."
                )
                % self.exposure_class_attribute
            )
            raise GetDataError(message)

        # If target_field does not exist, add it:
        if exposure_fields.indexFromName(self.target_field) == -1:
            exposure_provider.addAttributes([QgsField(self.target_field, QVariant.Int)])
        target_field_index = exposure_provider.fieldNameIndex(self.target_field)
        exposure_fields = exposure_provider.fields()

        # Create layer to store the lines from E and extent
        building_layer = QgsVectorLayer("Polygon?crs=" + srs, "impact_buildings", "memory")
        building_provider = building_layer.dataProvider()

        # Set attributes
        building_provider.addAttributes(exposure_fields.toList())
        building_layer.startEditing()
        building_layer.commitChanges()

        # Filter geometry and data using the requested extent
        requested_extent = QgsRectangle(*self.requested_extent)

        # This is a hack - we should be setting the extent CRS
        # in the IF base class via safe/engine/core.py:calculate_impact
        # for now we assume the extent is in 4326 because it
        # is set to that from geo_extent
        # See issue #1857
        transform = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem("EPSG:%i" % self._requested_extent_crs), self.hazard.layer.crs()
        )
        projected_extent = transform.transformBoundingBox(requested_extent)
        request = QgsFeatureRequest()
        request.setFilterRect(projected_extent)

        # Split building_layer by H and save as result:
        #   1) Filter from H inundated features
        #   2) Mark buildings as inundated (1) or not inundated (0)

        # make spatial index of affected polygons
        hazard_index = QgsSpatialIndex()
        hazard_geometries = {}  # key = feature id, value = geometry
        has_hazard_objects = False
        for feature in self.hazard.layer.getFeatures(request):
            value = feature[affected_field_index]
            if value not in self.hazard_class_mapping[self.wet]:
                continue
            hazard_index.insertFeature(feature)
            hazard_geometries[feature.id()] = QgsGeometry(feature.geometry())
            has_hazard_objects = True

        if not has_hazard_objects:
            message = tr(
                "There are no objects in the hazard layer with %s "
                "value in %s. Please check your data or use another "
                "attribute."
            ) % (self.hazard_class_attribute, ", ".join(self.hazard_class_mapping[self.wet]))
            raise GetDataError(message)

        features = []
        for feature in self.exposure.layer.getFeatures(request):
            building_geom = feature.geometry()
            affected = False
            # get tentative list of intersecting hazard features
            # only based on intersection of bounding boxes
            ids = hazard_index.intersects(building_geom.boundingBox())
            for fid in ids:
                # run (slow) exact intersection test
                if hazard_geometries[fid].intersects(building_geom):
                    affected = True
                    break
            f = QgsFeature()
            f.setGeometry(building_geom)
            f.setAttributes(feature.attributes())
            f[target_field_index] = 1 if affected else 0
            features.append(f)

            # every once in a while commit the created features
            # to the output layer
            if len(features) == 1000:
                (_, __) = building_provider.addFeatures(features)
                features = []

        (_, __) = building_provider.addFeatures(features)
        building_layer.updateExtents()

        # Generate simple impact report
        self.buildings = {}
        self.affected_buildings = OrderedDict([(tr("Flooded"), {})])
        buildings_data = building_layer.getFeatures()
        building_type_field_index = building_layer.fieldNameIndex(self.exposure_class_attribute)
        for building in buildings_data:
            record = building.attributes()
            building_type = record[building_type_field_index]
            if building_type in [None, "NULL", "null", "Null"]:
                building_type = "Unknown type"
            if building_type not in self.buildings:
                self.buildings[building_type] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][building_type] = OrderedDict([(tr("Buildings Affected"), 0)])
            self.buildings[building_type] += 1

            if record[target_field_index] == 1:
                self.affected_buildings[tr("Flooded")][building_type][tr("Buildings Affected")] += 1

        # Lump small entries and 'unknown' into 'other' category
        self._consolidate_to_other()

        impact_summary = self.generate_html_report()

        # For printing map purpose
        map_title = tr("Buildings inundated")
        legend_title = tr("Structure inundated status")

        style_classes = [
            dict(label=tr("Not Inundated"), value=0, colour="#1EFC7C", transparency=0, size=0.5),
            dict(label=tr("Inundated"), value=1, colour="#F31A1C", transparency=0, size=0.5),
        ]
        style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type="categorizedSymbol")

        # Convert QgsVectorLayer to inasafe layer and return it.
        if building_layer.featureCount() < 1:
            raise ZeroImpactException(tr("No buildings were impacted by this flood."))
        building_layer = Vector(
            data=building_layer,
            name=tr("Flooded buildings"),
            keywords={
                "impact_summary": impact_summary,
                "map_title": map_title,
                "legend_title": legend_title,
                "target_field": self.target_field,
                "buildings_total": self.total_buildings,
                "buildings_affected": self.total_affected_buildings,
            },
            style_info=style_info,
        )
        self._impact = building_layer
        return building_layer
예제 #18
0
    def processAlgorithm(self, progress):
        layerA = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_A))
        splitLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_B))

        sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B)
        fieldList = layerA.fields()

        writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList,
                                                                     layerA.wkbType(), layerA.crs())

        spatialIndex = QgsSpatialIndex()
        splitGeoms = {}
        request = QgsFeatureRequest()
        request.setSubsetOfAttributes([])

        for aSplitFeature in vector.features(splitLayer, request):
            splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry()
            spatialIndex.insertFeature(aSplitFeature)
            # honor the case that user has selection on split layer and has setting "use selection"

        outFeat = QgsFeature()
        features = vector.features(layerA)

        if len(features) == 0:
            total = 100
        else:
            total = 100.0 / float(len(features))

        multiGeoms = 0 # how many multi geometries were encountered

        for current, inFeatA in enumerate(features):
            inGeom = inFeatA.geometry()

            if inGeom.isMultipart():
                multiGeoms += 1
                # MultiGeometries are not allowed because the result of a splitted part cannot be clearly defined:
                # 1) add both new parts as new features
                # 2) store one part as a new feature and the other one as part of the multi geometry
                # 2a) which part should be which, seems arbitrary
            else:
                attrsA = inFeatA.attributes()
                outFeat.setAttributes(attrsA)
                inGeoms = [inGeom]
                lines = spatialIndex.intersects(inGeom.boundingBox())

                if len(lines) > 0:  # has intersection of bounding boxes
                    splittingLines = []

                    engine = QgsGeometry.createGeometryEngine(inGeom.geometry())
                    engine.prepareGeometry()

                    for i in lines:
                        try:
                            splitGeom = splitGeoms[i]
                        except:
                            continue

                        # check if trying to self-intersect
                        if sameLayer:
                            if inFeatA.id() == i:
                                continue

                        if engine.intersects(splitGeom.geometry()):
                            splittingLines.append(splitGeom)

                    if len(splittingLines) > 0:
                        for splitGeom in splittingLines:
                            splitterPList = None
                            outGeoms = []

                            split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry())
                            split_geom_engine.prepareGeometry()

                            while len(inGeoms) > 0:
                                inGeom = inGeoms.pop()

                                if split_geom_engine.intersects(inGeom.geometry()):
                                    inPoints = vector.extractPoints(inGeom)
                                    if splitterPList == None:
                                        splitterPList = vector.extractPoints(splitGeom)

                                    try:
                                        result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False)
                                    except:
                                        ProcessingLog.addToLog(ProcessingLog.LOG_WARNING,
                                                               self.tr('Geometry exception while splitting'))
                                        result = 1

                                    # splitGeometry: If there are several intersections
                                    # between geometry and splitLine, only the first one is considered.
                                    if result == 0:  # split occurred
                                        if inPoints == vector.extractPoints(inGeom):
                                            # bug in splitGeometry: sometimes it returns 0 but
                                            # the geometry is unchanged
                                            QgsMessageLog.logMessage("appending")
                                            outGeoms.append(inGeom)
                                        else:
                                            inGeoms.append(inGeom)

                                            for aNewGeom in newGeometries:
                                                inGeoms.append(aNewGeom)
                                    else:
                                        QgsMessageLog.logMessage("appending else")
                                        outGeoms.append(inGeom)
                                else:
                                    outGeoms.append(inGeom)

                            inGeoms = outGeoms

                for aGeom in inGeoms:
                    passed = True

                    if QgsWkbTypes.geometryType( aGeom.wkbType() )  == QgsWkbTypes.LineGeometry \
                            and not QgsWkbTypes.isMultiType(aGeom.wkbType()):
                        passed = len(aGeom.asPolyline()) > 2

                        if not passed:
                            passed = (len(aGeom.asPolyline()) == 2 and
                                      aGeom.asPolyline()[0] != aGeom.asPolyline()[1])
                            # sometimes splitting results in lines of zero length

                    if passed:
                        outFeat.setGeometry(aGeom)
                        writer.addFeature(outFeat)

            progress.setPercentage(int(current * total))

        if multiGeoms > 0:
            ProcessingLog.addToLog(ProcessingLog.LOG_INFO,
                                   self.tr('Feature geometry error: %s input features ignored due to multi-geometry.') % str(multiGeoms))

        del writer
예제 #19
0
def get_feats_on_bbox(layer, bbox):
    index = QgsSpatialIndex(layer.getFeatures())
    feats_int = index.intersects(bbox)
    return feats_int
class LeastCommonDenominatorProcedure(WorkerThread):
    def __init__(self, parentThread, flayer, tlayer, ffield, tfield):
        WorkerThread.__init__(self, parentThread)
        self.flayer = flayer
        self.tlayer = tlayer
        self.ffield = ffield
        self.tfield = tfield
        self.error = None
        self.result = None
        self.output_type = None
        self.poly_types = poly_types + multi_poly
        self.line_types = line_types + multi_line
        self.point_types = point_types + multi_point

    def doWork(self):
        flayer = self.flayer
        tlayer = self.tlayer
        ffield = self.ffield
        tfield = self.tfield

        self.from_layer = get_vector_layer_by_name(flayer)
        self.to_layer = get_vector_layer_by_name(tlayer)

        self.transform = None
        if self.from_layer.dataProvider().crs() != self.to_layer.dataProvider().crs():
            self.transform = QgsCoordinateTransform(self.from_layer.dataProvider().crs(),
                                                    self.to_layer.dataProvider().crs())

        # FIELDS INDICES
        idx = self.from_layer.fieldNameIndex(ffield)
        fid = self.to_layer.fieldNameIndex(tfield)

        # We create an spatial self.index to hold all the features of the layer that will receive the data
        # And a dictionary that will hold all the features IDs found to intersect with each feature in the spatial index
        self.emit(SIGNAL("ProgressMaxValue( PyQt_PyObject )"), self.to_layer.dataProvider().featureCount())
        self.emit(SIGNAL("ProgressText( PyQt_PyObject )"), 'Building Spatial Index')
        allfeatures = {}
        merged = {}
        self.index = QgsSpatialIndex()
        for i, feature in enumerate(self.to_layer.getFeatures()):
            allfeatures[feature.id()] = feature
            merged[feature.id()] = feature
            self.index.insertFeature(feature)
            self.emit(SIGNAL("ProgressValue( PyQt_PyObject )"), i)

        self.emit(SIGNAL("ProgressText( PyQt_PyObject )"), 'Duplicating Layers')
        self.all_attr = {}
        # We create the memory layer that will have the analysis result, which is the lowest common
        # denominator of both layers
        epsg_code = int(self.to_layer.crs().authid().split(":")[1])
        if self.from_layer.wkbType() in self.poly_types and self.to_layer.wkbType() in self.poly_types:
            lcd_layer = QgsVectorLayer("MultiPolygon?crs=epsg:" + str(epsg_code), "output", "memory")
            self.output_type = 'Poly'

        elif self.from_layer.wkbType() in self.poly_types + self.line_types and \
                self.to_layer.wkbType() in self.poly_types + self.line_types:
            lcd_layer = QgsVectorLayer("MultiLineString?crs=epsg:" + str(epsg_code), "output", "memory")
            self.output_type = 'Line'
        else:
            lcd_layer = QgsVectorLayer("MultiPoint?crs=epsg:" + str(epsg_code), "output", "memory")
            self.output_type = 'Point'

        lcdpr = lcd_layer.dataProvider()
        lcdpr.addAttributes([QgsField("Part_ID", QVariant.Int),
                             QgsField(ffield, self.from_layer.fields().field(idx).type()),
                             QgsField(tfield, self.to_layer.fields().field(fid).type()),
                             QgsField('P-' + str(ffield), QVariant.Double),  # percentage of the from field
                             QgsField('P-' + str(tfield), QVariant.Double)])  # percentage of the to field
        lcd_layer.updateFields()

        # PROGRESS BAR
        self.emit(SIGNAL("ProgressMaxValue( PyQt_PyObject )"), self.from_layer.dataProvider().featureCount())
        self.emit(SIGNAL("ProgressText( PyQt_PyObject )"), 'Running Analysis')
        part_id = 1
        features = []
        for fc, feat in enumerate(self.from_layer.getFeatures()):
            geom = feat.geometry()
            if geom is not None:
                if self.transform is not None:
                    a = geom.transform(self.transform)
                geometry, statf = self.find_geometry(geom)
                uncovered, statf = self.find_geometry(geom)
                # uncovered = copy.deepcopy(geometry)

                intersecting = self.index.intersects(geometry.boundingBox())
                # Find all intersecting parts
                for f in intersecting:
                    g = geometry.intersection(allfeatures[f].geometry())
                    if g.area() > 0:
                        feature = QgsFeature()
                        geo, stati = self.find_geometry(g)
                        feature.setGeometry(geo)
                        geo, statt = self.find_geometry(allfeatures[f].geometry())
                        perct = stati / statt
                        percf = stati / statf
                        feature.setAttributes([part_id,
                                               feat.attributes()[idx],
                                               allfeatures[f].attributes()[fid],
                                               percf,
                                               perct])
                        features.append(feature)

                        # prepare the data for the non overlapping
                        if uncovered is not None:
                            uncovered = uncovered.difference(g)
                            aux = merged[f].geometry().difference(g)
                            if aux is not None:
                                merged[f].setGeometry(aux)
                            part_id += 1

                # Find the part that does not intersect anything
                if uncovered is not None:
                    if uncovered.area() > 0:
                        feature = QgsFeature()
                        geo, stati = self.find_geometry(uncovered)
                        feature.setGeometry(geo)
                        perct = 0
                        percf = stati / statf
                        feature.setAttributes([part_id,
                                               feat.attributes()[idx],
                                               '',
                                               percf,
                                               perct])
                        features.append(feature)
                        part_id += 1

            self.emit(SIGNAL("ProgressValue( PyQt_PyObject )"), fc)
            self.emit(SIGNAL("ProgressText( PyQt_PyObject )"),
                      'Running Analysis (' + "{:,}".format(fc) + '/' + "{:,}".format(
                          self.from_layer.featureCount()) + ')')

        # Find the features on TO that have no correspondence in FROM
        for f, feature in merged.iteritems():
            geom = feature.geometry()
            aux, statt = self.find_geometry(allfeatures[f].geometry())
            if geom.area() > 0:
                feature = QgsFeature()
                geo, stati = self.find_geometry(geom)
                feature.setGeometry(geo)
                perct = stati / statt
                percf = 0
                feature.setAttributes([part_id,
                                       '',
                                       allfeatures[f].attributes()[fid],
                                       percf,
                                       perct])
                features.append(feature)
                part_id += 1

        if features:
            a = lcdpr.addFeatures(features)
        self.result = lcd_layer

        self.emit(SIGNAL("ProgressValue( PyQt_PyObject )"), self.from_layer.dataProvider().featureCount())
        self.emit(SIGNAL("finished_threaded_procedure( PyQt_PyObject )"), "procedure")

    def find_geometry(self, g):
        if self.output_type == 'Poly':
            stat = g.area()
            if g.isMultipart():
                geometry = QgsGeometry.fromMultiPolygon(g.asMultiPolygon())
            else:
                geometry = QgsGeometry.fromPolygon(g.asPolygon())
        elif self.output_type == 'Line':
            stat = g.length()
            if g.isMultipart():
                geometry = QgsGeometry.fromMultiLineString(g.asMultiPolyLine())
            else:
                geometry = QgsGeometry.fromLineString(g.asPoly())
        else:
            stat = 1
            if g.isMultipart():
                geometry = QgsGeometry.fromMultiPoint(g.asMultiPoint())
            else:
                geometry = QgsGeometry.fromPoint(g.asPoint())
        return geometry, stat
예제 #21
0
    def processAlgorithm(self, parameters, context, feedback):
        sourceA = self.parameterAsSource(parameters, self.INPUT, context)
        sourceB = self.parameterAsSource(parameters, self.INTERSECT, context)

        fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS,
                                         context)
        fieldsB = self.parameterAsFields(parameters, self.INTERSECT_FIELDS,
                                         context)

        fieldListA = QgsFields()
        field_indices_a = []
        if len(fieldsA) > 0:
            for f in fieldsA:
                idxA = sourceA.fields().lookupField(f)
                if idxA >= 0:
                    field_indices_a.append(idxA)
                    fieldListA.append(sourceA.fields()[idxA])
        else:
            fieldListA = sourceA.fields()
            field_indices_a = [i for i in range(0, fieldListA.count())]

        fieldListB = QgsFields()
        field_indices_b = []
        if len(fieldsB) > 0:
            for f in fieldsB:
                idxB = sourceB.fields().lookupField(f)
                if idxB >= 0:
                    field_indices_b.append(idxB)
                    fieldListB.append(sourceB.fields()[idxB])
        else:
            fieldListB = sourceB.fields()
            field_indices_b = [i for i in range(0, fieldListB.count())]

        fieldListB = vector.testForUniqueness(fieldListA, fieldListB)
        for b in fieldListB:
            fieldListA.append(b)

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, fieldListA,
                                               QgsWkbTypes.Point,
                                               sourceA.sourceCrs())

        spatialIndex = QgsSpatialIndex(
            sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(
                []).setDestinationCrs(sourceA.sourceCrs())), feedback)

        outFeat = QgsFeature()
        features = sourceA.getFeatures(
            QgsFeatureRequest().setSubsetOfAttributes(field_indices_a))
        total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 0
        for current, inFeatA in enumerate(features):
            if feedback.isCanceled():
                break

            if not inFeatA.hasGeometry():
                continue

            inGeom = inFeatA.geometry()
            has_intersections = False
            lines = spatialIndex.intersects(inGeom.boundingBox())

            engine = None
            if len(lines) > 0:
                has_intersections = True
                # use prepared geometries for faster intersection tests
                engine = QgsGeometry.createGeometryEngine(inGeom.geometry())
                engine.prepareGeometry()

            if has_intersections:
                request = QgsFeatureRequest().setFilterFids(lines)
                request.setDestinationCrs(sourceA.sourceCrs())
                request.setSubsetOfAttributes(field_indices_b)

                for inFeatB in sourceB.getFeatures(request):
                    if feedback.isCanceled():
                        break

                    tmpGeom = inFeatB.geometry()

                    points = []
                    if engine.intersects(tmpGeom.geometry()):
                        tempGeom = inGeom.intersection(tmpGeom)
                        out_attributes = [
                            inFeatA.attributes()[i] for i in field_indices_a
                        ]
                        out_attributes.extend(
                            [inFeatB.attributes()[i] for i in field_indices_b])
                        if tempGeom.type() == QgsWkbTypes.PointGeometry:
                            if tempGeom.isMultipart():
                                points = tempGeom.asMultiPoint()
                            else:
                                points.append(tempGeom.asPoint())

                            for j in points:
                                outFeat.setGeometry(tempGeom.fromPoint(j))
                                outFeat.setAttributes(out_attributes)
                                sink.addFeature(outFeat,
                                                QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
예제 #22
0
class TriangleMesh:

    # 0 - 3
    # | / |
    # 1 - 2

    def __init__(self, xmin, ymin, xmax, ymax, x_segments, y_segments):
        self.vbands = []
        self.hbands = []
        self.vidx = QgsSpatialIndex()
        self.hidx = QgsSpatialIndex()

        xres = (xmax - xmin) / x_segments
        yres = (ymax - ymin) / y_segments
        self.xmin, self.ymax, self.xres, self.yres = xmin, ymax, xres, yres

        def addVBand(idx, geom):
            f = QgsFeature(idx)
            f.setGeometry(geom)
            self.vbands.append(f)
            self.vidx.insertFeature(f)

        def addHBand(idx, geom):
            f = QgsFeature(idx)
            f.setGeometry(geom)
            self.hbands.append(f)
            self.hidx.insertFeature(f)

        for x in range(x_segments):
            addVBand(
                x,
                QgsGeometry.fromRect(
                    QgsRectangle(xmin + x * xres, ymin, xmin + (x + 1) * xres,
                                 ymax)))

        for y in range(y_segments):
            addHBand(
                y,
                QgsGeometry.fromRect(
                    QgsRectangle(xmin, ymax - (y + 1) * yres, xmax,
                                 ymax - y * yres)))

    def vSplit(self, geom):
        """split polygon vertically"""
        for idx in self.vidx.intersects(geom.boundingBox()):
            yield idx, geom.intersection(self.vbands[idx].geometry())

    def hIntersects(self, geom):
        """indices of horizontal bands that intersect with geom"""
        for idx in self.hidx.intersects(geom.boundingBox()):
            if geom.intersects(self.hbands[idx].geometry()):
                yield idx

    def splitPolygons(self, geom):
        xmin, ymax, xres, yres = self.xmin, self.ymax, self.xres, self.yres

        for x, vi in self.vSplit(geom):
            for y in self.hIntersects(vi):
                pt0 = QgsPoint(xmin + x * xres, ymax - y * yres)
                pt1 = QgsPoint(xmin + x * xres, ymax - (y + 1) * yres)
                pt2 = QgsPoint(xmin + (x + 1) * xres, ymax - (y + 1) * yres)
                pt3 = QgsPoint(xmin + (x + 1) * xres, ymax - y * yres)
                quad = QgsGeometry.fromPolygon([[pt0, pt1, pt2, pt3, pt0]])
                tris = [[[pt0, pt1, pt3, pt0]], [[pt3, pt1, pt2, pt3]]]

                if geom.contains(quad):
                    yield tris[0]
                    yield tris[1]
                else:
                    for i, tri in enumerate(map(QgsGeometry.fromPolygon,
                                                tris)):
                        if geom.contains(tri):
                            yield tris[i]
                        elif geom.intersects(tri):
                            poly = geom.intersection(tri)
                            if poly.isMultipart():
                                for sp in poly.asMultiPolygon():
                                    yield sp
                            else:
                                yield poly.asPolygon()
예제 #23
0
class autoRedistrict:
    """QGIS Plugin Implementation."""
    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(self.plugin_dir, 'i18n',
                                   'autoRedistrict_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Automated Redistricting')

        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads
        self.first_start = None

        #initialise the other variables
        self.distfield = None
        self.popfield = None
        self.geofield = None
        self.activeLayer = None
        self.sortType = 0
        self.totalpop = 0
        self.targetpop = 0
        self.districts = 0
        self.sortIndex = 0
        self.spatialIndex = QgsSpatialIndex()
        self.distpop = []
        self.feature_dict = {}

    # noinspection PyMethodMayBeStatic
    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('autoRedistrict', message)

    def add_action(self,
                   icon_path,
                   text,
                   callback,
                   enabled_flag=True,
                   add_to_menu=True,
                   add_to_toolbar=True,
                   status_tip=None,
                   whats_this=None,
                   parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            # Adds plugin icon to Plugins toolbar
            self.iface.addToolBarIcon(action)

        if add_to_menu:
            self.iface.addPluginToMenu(self.menu, action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/autoRedistrict/icon.png'
        self.add_action(icon_path,
                        text=self.tr(u'Auto Redistricting'),
                        callback=self.run,
                        parent=self.iface.mainWindow())

        # will be set False in run()
        self.first_start = True

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginMenu(self.tr(u'&Automated Redistricting'),
                                        action)
            self.iface.removeToolBarIcon(action)

    def run(self):
        """Run method that performs all the real work"""

        #create the triggers - only need one

        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.first_start = False
            self.dlg = autoRedistrictDialog()

            self.dlg.cmbActiveLayer.currentIndexChanged.connect(
                self.updateFieldCombos)

            self.dlg.cmbDirection.clear()
            self.dlg.cmbDirection.addItems([
                'West to East', 'East to West', 'North to South',
                'South to North'
            ])

            self.updateDialog()

        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            self.createVariables()
            self.initialiseSpatialIndex()
            #            self.resetDistrictColumn()
            self.redistrictLayer()

    def updateDialog(self):
        layers = [
            tree_layer.layer() for tree_layer in
            QgsProject.instance().layerTreeRoot().findLayers()
        ]
        layer_list = []
        for layer in layers:
            layer_list.append(layer.name())
        self.dlg.cmbActiveLayer.clear()
        self.dlg.cmbActiveLayer.addItems(layer_list)

    def updateFieldCombos(self):
        self.dlg.cmbPopField.clear()
        self.dlg.cmbDistField.clear()
        self.dlg.cmbGeoField.clear()
        #        activeDirectionIndex = self.dlg.cmbDirection.currentIndex()

        layers = [
            tree_layer.layer() for tree_layer in
            QgsProject.instance().layerTreeRoot().findLayers()
        ]
        selectedLayerIndex = self.dlg.cmbActiveLayer.currentIndex()
        selectedLayer = layers[selectedLayerIndex]

        self.dlg.cmbGeoField.addItems(['None'])
        if hasattr(selectedLayer, 'fields'):
            fields = selectedLayer.fields()
            field_names = [field.name() for field in fields]
            self.dlg.cmbPopField.addItems(field_names)
            self.dlg.cmbDistField.addItems(field_names)
            self.dlg.cmbGeoField.addItems(field_names)

    def createVariables(self):
        self.iface.statusBarIface().showMessage(u"Creating variables...")
        QCoreApplication.processEvents()
        self.popfield = self.dlg.cmbPopField.currentText()
        self.distfield = self.dlg.cmbDistField.currentText()
        self.geofield = self.dlg.cmbGeoField.currentText()
        self.sortIndex = self.dlg.cmbDirection.currentIndex()
        layers = [
            tree_layer.layer() for tree_layer in
            QgsProject.instance().layerTreeRoot().findLayers()
        ]
        self.districts = self.dlg.inpDistricts.value()

        del self.distpop
        self.distpop = []
        self.distpop.append(0)

        selectedLayerIndex = self.dlg.cmbActiveLayer.currentIndex()
        selectedLayer = layers[selectedLayerIndex]
        self.activeLayer = selectedLayer

        for feature in self.activeLayer.getFeatures():
            self.totalpop = self.totalpop + int(feature[self.popfield])

        if self.districts > 0:
            self.targetpop = self.totalpop / self.districts
            print('districts:' + str(self.districts))
            print('targetpop:' + str(self.targetpop))
        else:
            pass

    def redistrictLayer(self):
        field_id = self.activeLayer.fields().indexFromName(self.distfield)
        self.iface.statusBarIface().showMessage(u"Redistricting layer...")
        QCoreApplication.processEvents()

        districtctr = 0
        found = 0
        while found != -1:
            strExpr = "\"" + self.distfield + "\" = '0' or \"" + self.distfield + "\" = None"
            expr = QgsExpression(strExpr)

            if self.sortIndex == 1:
                # east to west
                features = sorted(self.activeLayer.getFeatures(strExpr),
                                  key=sort_by_x,
                                  reverse=True)
            elif self.sortIndex == 2:
                features = sorted(self.activeLayer.getFeatures(strExpr),
                                  key=sort_by_y,
                                  reverse=False)
            elif self.sortIndex == 3:
                features = sorted(self.activeLayer.getFeatures(strExpr),
                                  key=sort_by_y,
                                  reverse=True)
            else:
                features = sorted(self.activeLayer.getFeatures(strExpr),
                                  key=sort_by_x)
            found = 0
            for f in features:
                found = 1
                if f[self.distfield] == 0 or f[self.distfield] == None or f[
                        self.distfield] == 'NULL':
                    print('New District:' + str(f['BLOCKID10']) + '|' +
                          str(f[self.distfield]))
                    districtctr = districtctr + 1
                    """
                    if districtctr == 2:
                        strExpr = "\"" + self.distfield + "\" = '1'"
                        expr = QgsExpression(strExpr)
                        iterator = self.activeLayer.getFeatures(QgsFeatureRequest(expr))
                        ids = [i.id() for i in iterator]
                        self.activeLayer.select(ids)
                    """
                    print('New district being created:' + str(districtctr))
                    self.iface.statusBarIface().showMessage(
                        u"Redistricting layer (starting district " +
                        str(districtctr) + ")")
                    QCoreApplication.processEvents()
                    self.distpop.append(0)
                    is_enclave = self.floodFillDistrict(f, districtctr)
                    if is_enclave == 1:
                        districtctr = districtctr - 1
                    break
            if found == 0:
                #close the loop
                found = -1
        self.iface.statusBarIface().showMessage(u"...done.")

    def initialiseSpatialIndex(self):
        self.iface.statusBarIface().showMessage(
            u"Initialising spatial index...")
        QCoreApplication.processEvents()
        self.feature_dict = {f.id(): f for f in self.activeLayer.getFeatures()}
        for f in list(self.feature_dict.values()):
            self.spatialIndex.addFeature(f)

    def resetDistrictColumn(self):
        self.iface.statusBarIface().showMessage(u"Clearing district column:")
        QCoreApplication.processEvents()
        field_id = self.activeLayer.fields().indexFromName(self.distfield)

        self.activeLayer.startEditing()
        counter = 0
        request = QgsFeatureRequest().setFlags(
            QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(
                [self.distfield], self.activeLayer.fields())
        for f in self.activeLayer.getFeatures(request):
            counter = counter + 1
            self.activeLayer.changeAttributeValue(f.id(), field_id, 0)
            if counter % 300 == 0:
                #speeds things up significantly
                self.activeLayer.commitChanges()
                self.activeLayer.startEditing()
                self.iface.statusBarIface().showMessage(
                    u"Clearing district column: " + str(counter) +
                    " records processed")
                QCoreApplication.processEvents()
        self.activeLayer.commitChanges()

    def floodFillDistrict(self, feature, district_number):
        QgsMessageLog.logMessage("Starting district " + str(district_number))
        print('district_number:' + str(district_number))
        field_id = self.activeLayer.fields().indexFromName(self.distfield)
        select_list = []
        outside_geo_list = []
        neighboring_districts = []
        if self.geofield != 'None':
            activeGeoField = feature[self.geofield]
            QgsMessageLog.logMessage("GeoField set to " + activeGeoField)
        geoCounter = 0
        self.distpop[district_number] = feature[self.popfield]
        self.activeLayer.startEditing()
        feature[self.distfield] = district_number
        self.activeLayer.changeAttributeValue(feature.id(), field_id,
                                              district_number, 0)
        self.activeLayer.commitChanges()
        self.activeLayer.startEditing()
        print('starting at ' + str(feature.id()) + '|' +
              str(feature[self.distfield]),
              end=' ')
        select_list.append(feature)
        geoCounter = geoCounter + 1
        QCoreApplication.processEvents()
        surrounded = 0  #whether the feature is completely surrounded by another district
        # Loop through all features and find features that touch each feature
        counter = 0
        for f in select_list:
            geom = f.geometry()
            # Find all features that intersect the bounding box of the current feature.
            # We use spatial index to find the features intersecting the bounding box
            # of the current feature. This will narrow down the features that we need
            # to check neighboring features.
            intersecting_ids = self.spatialIndex.intersects(geom.boundingBox())
            counter = counter + 1
            if counter % 500 == 0:
                self.iface.statusBarIface().showMessage(
                    u"Redistricting layer (district " + str(district_number) +
                    ") " + str(counter) +
                    " polygons added, current population " +
                    str(self.distpop[district_number]) + "/" +
                    str(self.targetpop))
                QCoreApplication.processEvents()
                self.activeLayer.commitChanges()
                self.activeLayer.startEditing()

            for intersecting_id in intersecting_ids:
                # Look up the feature from the dictionary
                intersecting_f = self.feature_dict[intersecting_id]
                # For our purpose we consider a feature as 'neighbor' if it touches or
                # intersects a feature. We use the 'disjoint' predicate to satisfy
                # these conditions. So if a feature is not disjoint, it is a neighbor.
                #                    if (f.id() != intersecting_f.id() and feature.id() != intersecting_f.id() and not intersecting_f.geometry().disjoint(geom)):
                if (f.id() != intersecting_f.id()
                        and feature.id() != intersecting_f.id()
                        and intersecting_f.geometry().intersects(geom)):
                    if intersecting_f[self.distfield] == 0 or intersecting_f[
                            self.distfield] == None:
                        if intersecting_f[
                                self.
                                geofield] == activeGeoField or self.geofield == 'None':
                            if intersecting_f not in select_list:
                                if surrounded != -1:
                                    surrounded = -1
                                self.distpop[district_number] = self.distpop[
                                    district_number] + intersecting_f[
                                        self.popfield]
                                intersecting_f[
                                    self.distfield] = district_number
                                self.activeLayer.changeAttributeValue(
                                    intersecting_f.id(), field_id,
                                    district_number, 0)
                                #                                            print(':' + str(intersecting_f[self.distfield]), end=":")
                                select_list.append(intersecting_f)
                                geoCounter = geoCounter + 1
                                if self.distpop[
                                        district_number] > self.targetpop:
                                    self.activeLayer.commitChanges()
                                    return 0
                        else:
                            if intersecting_f not in outside_geo_list and intersecting_f not in select_list:
                                outside_geo_list.append(intersecting_f)
                                surrounded = -1
                    elif surrounded != -1:
                        #kill one-district, completely surrounded enclaves as we find them
                        if surrounded == 0:
                            surrounded = intersecting_f[self.distfield]
                        elif surrounded != intersecting_f[self.distfield]:
                            if surrounded > 0:
                                neighboring_districts.append(surrounded)
                            surrounded = -1
                    elif surrounded == -1:
                        if intersecting_f[
                                self.distfield] not in neighboring_districts:
                            neighboring_districts.append(
                                intersecting_f[self.distfield])
                        """
                                #old code commented out which gets greedy and expands districts, but it doesn't work well - shoots down interstates, etc
                                if self.distpop[intersecting_f[self.distfield]] - intersecting_f[self.popfield] > self.targetpop:
                                    self.distpop[intersecting_f[self.distfield]] = self.distpop[intersecting_f[self.distfield]] - intersecting_f[self.popfield]
                                    self.distpop[district_number] = self.distpop[district_number] + intersecting_f[self.popfield]
                                    intersecting_f[self.distfield] = district_number
                                    self.activeLayer.changeAttributeValue(intersecting_f.id(),field_id,district_number,0)
                                    select_list.append(intersecting_f)
                                """

                        if self.distpop[district_number] > self.targetpop:
                            self.activeLayer.commitChanges()
                            return 0

            if surrounded > 0:
                QgsMessageLog.logMessage(
                    "enclave found, reassigning to district " +
                    str(surrounded))
                self.activeLayer.changeAttributeValue(feature.id(), field_id,
                                                      surrounded, 0)
                self.distpop[district_number] = self.distpop[
                    district_number] - feature[self.popfield]
                self.distpop[surrounded] = self.distpop[surrounded] + feature[
                    self.popfield]
                self.activeLayer.commitChanges()
                return 1

            #if geoCounter is zero, this means we change the active geography
            geoCounter = geoCounter - 1
            activeGeoField = ''
            if geoCounter == 0 and self.geofield != 'None':
                for of in outside_geo_list:
                    if activeGeoField == '':
                        activeGeoField = of[self.geofield]
                        QgsMessageLog.logMessage("GeoField updated to " +
                                                 activeGeoField)
                    if of[self.geofield] == activeGeoField:
                        select_list.append(of)
                        outside_geo_list.remove(of)
                        self.distpop[district_number] = self.distpop[
                            district_number] + of[self.popfield]
                        of[self.distfield] = district_number
                        self.activeLayer.changeAttributeValue(
                            of.id(), field_id, district_number, 0)
                        geoCounter = geoCounter + 1

        if self.distpop[district_number] < (self.targetpop * (1 - 0.9)):
            new_district = 0
            lower_pop = -1
            for n in neighboring_districts:
                if self.distpop[n] > lower_pop or lower_pop == -1:
                    lower_pop = self.distpop[n]
                    new_district = n
            QgsMessageLog.logMessage(
                "target population too low, reassigning to district " +
                str(new_district))
            for f in select_list:
                self.activeLayer.changeAttributeValue(f.id(), field_id,
                                                      new_district, 0)
                self.distpop[district_number] = self.distpop[
                    district_number] - feature[self.popfield]
                self.distpop[new_district] = self.distpop[
                    new_district] + feature[self.popfield]
            self.activeLayer.commitChanges()
            return 1
예제 #24
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        proximity = self.parameterAsDouble(parameters, self.PROXIMITY, context)
        radius = self.parameterAsDouble(parameters, self.DISTANCE, context)
        horizontal = self.parameterAsBool(parameters, self.HORIZONTAL, context)

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

        features = source.getFeatures()

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

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

        index = QgsSpatialIndex()

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

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

            if not f.hasGeometry():
                continue

            point = f.geometry().asPoint()

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

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

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

            feedback.setProgress(int(current * total))

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

        fullPerimeter = 2 * math.pi

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

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

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

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

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

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

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

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

        return {self.OUTPUT: dest_id}
예제 #25
0
class ContourTool():
    def updateReference(self, referenceLayer):
        self.reference = referenceLayer
        self.populateIndex()

    def populateIndex(self):
        #spatial index
        self.index = QgsSpatialIndex()
        for feat in self.reference.getFeatures():
            self.index.insertFeature(feat)
            
    def getCandidates(self, bbox):
        #features that might satisfy the query
        ids = self.index.intersects(bbox)
        candidates = []
        for id in ids:
            candidates.append(self.reference.getFeatures(QgsFeatureRequest().setFilterFid(id)).next())
        return candidates            
            
    def getFeatures(self, geom):
        #features that satisfy the query
        ret = []
        
        rect = geom.boundingBox()
        candidates = self.getCandidates(rect)
        for candidate in candidates:
            featGeom = candidate.geometry()
            if featGeom.intersects(geom):
                ret.append(candidate)
                
        return ret
    
    def getKey(self, item):
        return item[0]
                
    def sortFeatures(self, geom, features):
        #sorting by distance
        distances = []
        
        firstPoint = geom.asPolyline()[0]
        pointGeom = QgsGeometry.fromPoint(firstPoint)

        for intersected in features:
            intersection = geom.intersection(intersected.geometry())
            if intersection.type() == QGis.Point:
                distance = intersection.distance(pointGeom)
                distances.append((distance, intersected))
        
        ordered = sorted(distances, key=self.getKey)
        #returning a list of tuples (distance, feature)
        return ordered

    def reproject(self, geom, canvasCrs):
        destCrs = self.reference.crs()
        if canvasCrs.authid() != destCrs.authid():
            coordinateTransformer = QgsCoordinateTransform(canvasCrs, destCrs)
            geom.transform(coordinateTransformer)
    
    def assignValues(self, attribute, pace, geom, canvasCrs):
        self.reproject(geom, canvasCrs)
        features = self.getFeatures(geom)
        ordered = self.sortFeatures(geom, features)

        #the first feature must have the initial value already assigned
        first_feature = ordered[0][1]
        #getting the initial value
        first_value = first_feature.attribute(attribute)

        #getting the filed index that must be updated
        fieldIndex = self.reference.fieldNameIndex(attribute)

        self.reference.startEditing()
        for i in range(1, len(ordered)):
            value = first_value + pace*i
            feature = ordered[i][1]
            #feature id that will be updated
            id = feature.id()
            #attribute pair that will be changed
            attrs = {fieldIndex:value}
            #actual update in the database
            self.reference.dataProvider().changeAttributeValues({id:attrs})
        return self.reference.commitChanges()
예제 #26
0
    def processAlgorithm(self, parameters, context, feedback):
        sourceA = self.parameterAsSource(parameters, self.INPUT, context)
        sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)

        geomType = QgsWkbTypes.multiType(sourceA.wkbType())
        fields = vector.combineFields(sourceA.fields(), sourceB.fields())

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, geomType, sourceA.sourceCrs())

        featB = QgsFeature()
        outFeat = QgsFeature()

        indexA = QgsSpatialIndex(sourceA)
        indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())))

        total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1
        count = 0

        for featA in sourceA.getFeatures():
            if feedback.isCanceled():
                break

            geom = featA.geometry()
            diffGeom = QgsGeometry(geom)
            attrs = featA.attributes()
            intersects = indexB.intersects(geom.boundingBox())
            request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([])
            request.setDestinationCrs(sourceA.sourceCrs())
            for featB in sourceB.getFeatures(request):
                if feedback.isCanceled():
                    break
                tmpGeom = featB.geometry()
                if diffGeom.intersects(tmpGeom):
                    diffGeom = QgsGeometry(diffGeom.difference(tmpGeom))

            try:
                outFeat.setGeometry(diffGeom)
                outFeat.setAttributes(attrs)
                sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
            except:
                QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'),
                                         self.tr('Processing'), QgsMessageLog.WARNING)
                continue

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

        length = len(sourceA.fields())

        for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())):
            if feedback.isCanceled():
                break

            geom = featA.geometry()
            diffGeom = QgsGeometry(geom)
            attrs = featA.attributes()
            attrs = [NULL] * length + attrs
            intersects = indexA.intersects(geom.boundingBox())
            request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([])
            for featB in sourceA.getFeatures(request):
                if feedback.isCanceled():
                    break

                tmpGeom = featB.geometry()
                if diffGeom.intersects(tmpGeom):
                    diffGeom = QgsGeometry(diffGeom.difference(tmpGeom))

            try:
                outFeat.setGeometry(diffGeom)
                outFeat.setAttributes(attrs)
                sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
            except:
                QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'),
                                         self.tr('Processing'), QgsMessageLog.WARNING)
                continue

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

        return {self.OUTPUT: dest_id}
예제 #27
0
파일: base.py 프로젝트: jelledijkema/legger
class BaseCollection(object):
    """Collection with same functions as fiona.collection, but which can be created in memory. Uses
        rtree to speedup spatial filtering"""

    def __init__(self, geometry_type='Point'):
        self.geometry_type = geometry_type
        self._spatial_index = QgsSpatialIndex()

        self.ordered_dict = OrderedDict()

    @property
    def schema(self):
        # todo
        return

    @property
    def meta(self):
        # todo
        return

    def filter(self, *args, **kwds):
        """Returns an iterator over records, but filtered by a test for
        spatial intersection with the provided ``bbox``, a (minx, miny,
        maxx, maxy) tuple or a geometry ``mask``.

        Positional arguments ``stop`` or ``start, stop[, step]`` allows
        iteration to skip over items or stop at a specific item.
        """
        selected = self.keys(*args, **kwds)

        for i in selected:
            if i in self.ordered_dict:
                yield self.ordered_dict[i]

    def items(self, *args, **kwds):
        """Returns an iterator over FID, record pairs, optionally
        filtered by a test for spatial intersection with the provided
        ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry
        ``mask``.

        Positional arguments ``stop`` or ``start, stop[, step]`` allows
        iteration to skip over items or stop at a specific item.
        """
        selected = self.keys(*args, **kwds)

        for i in selected:
            if i in self.ordered_dict:
                yield (i, self.ordered_dict[i])

    def keys(self, start=0, stop=None, step=1, **kwds):
        """Returns an iterator over FIDs, optionally
        filtered by a test for spatial intersection with the provided
        ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry
        ``mask``.

        Positional arguments ``stop`` or ``start, stop[, step]`` allows
        iteration to skip over items or stop at a specific item.
        """
        selected = set(self.ordered_dict.keys())

        # warning: this is not supported by Fiona
        if len(selected) == 0:
            return selected

        if stop is None:
            stop = max(selected) + 1
        elif stop < 0:
            stop = max(0, max(selected) + stop + 1)

        if start is None:
            start = min(selected)
        elif start < 0:
            start = max(0, max(selected) + start + 1)

        selected.intersection_update(set(range(start, stop, step)))

        bbox = kwds.get('bbox')
        bbox_precision = kwds.get('precision', 0.0)
        mask = kwds.get('mask')

        if bbox is not None:
            bbox = (
                bbox[0] - bbox_precision,
                bbox[1] - bbox_precision,
                bbox[2] + bbox_precision,
                bbox[3] + bbox_precision,
            )

            # rtree
            # selected.intersection_update(set(self._spatial_index.intersection(bbox)))
            # qgis
            qbbox = QgsRectangle(*bbox)
            selected.intersection_update(set(self._spatial_index.intersects(qbbox)))

        if mask:
            # todo
            pass

        return selected

    @property
    def bounds(self):
        """Returns (minx, miny, maxx, maxy)."""
        # rtree
        # return self._spatial_index.bounds
        # qgis: not implemented
        return [None, None, None, None]

    def writerecords(self, records):
        """Stages multiple records."""

        if type(records) != list:
            raise ValueError('list expected, got {0}'.format(type(records)))
        if len(self) == 0:
            nr = 0
        else:
            nr = next(reversed(self.ordered_dict)) + 1

        for record in records:
            record['id'] = nr
            self.ordered_dict[nr] = record

            # rtree
            # self._spatial_index.insert(nr, geom)
            # QGIS:
            feature = QgsFeature()
            feature.setFeatureId(nr)
            try:
                geom = shape(record['geometry'])
                qgeom = QgsGeometry()
                qgeom.fromWkb(geom.to_wkb())

            except:
                wkt = "{}({})".format(
                    record['geometry']['type'],
                    ",".join(["{} {}".format(*c) for c in record['geometry']['coordinates']]))
                qgeom = QgsGeometry()
                qgeom.fromWkt(wkt)

            feature.setGeometry(qgeom)
            self._spatial_index.insertFeature(feature)
            nr += 1

    def write(self, record):
        """Stages a record."""
        self.writerecords([record])

    def save(self,
             filename,
             crs=None,
             driver='ESRI Shapefile',
             schema=None):
        """

        """
        import fiona

        f = fiona.open(filename,
                       'w',
                       crs=crs,
                       driver=driver,
                       schema=schema)

        records = [feat for feat in self.filter()]
        f.writerecords(records)
        f.close()

        # todo: check fields and append field metadata dynamicaly

    def __len__(self):

        return len(self.ordered_dict)

    def __getitem__(self, key):

        return self.ordered_dict[key]

    def __iter__(self):

        return self.filter()
예제 #28
0
    def processAlgorithm(self, parameters, context, feedback):
        sourceA = self.parameterAsSource(parameters, self.INPUT, context)
        sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)

        geomType = QgsWkbTypes.multiType(sourceA.wkbType())
        fields = QgsProcessingUtils.combineFields(sourceA.fields(),
                                                  sourceB.fields())

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, fields, geomType,
                                               sourceA.sourceCrs())

        featA = QgsFeature()
        featB = QgsFeature()
        outFeat = QgsFeature()

        indexA = QgsSpatialIndex(sourceA, feedback)
        indexB = QgsSpatialIndex(
            sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(
                []).setDestinationCrs(sourceA.sourceCrs(),
                                      context.transformContext())), feedback)

        total = 100.0 / (sourceA.featureCount() *
                         sourceB.featureCount()) if sourceA.featureCount(
                         ) and sourceB.featureCount() else 1
        count = 0

        for featA in sourceA.getFeatures():
            if feedback.isCanceled():
                break

            lstIntersectingB = []
            geom = featA.geometry()
            atMapA = featA.attributes()
            intersects = indexB.intersects(geom.boundingBox())
            if len(intersects) < 1:
                try:
                    geom.convertToMultiType()
                    outFeat.setGeometry(geom)
                    outFeat.setAttributes(atMapA)
                    sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                except:
                    # This really shouldn't happen, as we haven't
                    # edited the input geom at all
                    feedback.pushInfo(
                        self.
                        tr('Feature geometry error: One or more output features ignored due to invalid geometry.'
                           ))
            else:
                request = QgsFeatureRequest().setFilterFids(
                    intersects).setSubsetOfAttributes([])
                request.setDestinationCrs(sourceA.sourceCrs(),
                                          context.transformContext())

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

                for featB in sourceB.getFeatures(request):
                    atMapB = featB.attributes()
                    tmpGeom = featB.geometry()

                    if engine.intersects(tmpGeom.constGet()):
                        int_geom = geom.intersection(tmpGeom)
                        lstIntersectingB.append(tmpGeom)

                        if not int_geom:
                            # There was a problem creating the intersection
                            feedback.pushInfo(
                                self.
                                tr('Feature geometry error: One or more output features ignored due to invalid geometry.'
                                   ))
                            int_geom = QgsGeometry()
                        else:
                            int_geom = QgsGeometry(int_geom)

                        if int_geom.wkbType(
                        ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(
                                int_geom.wkbType(
                                )) == QgsWkbTypes.GeometryCollection:
                            # Intersection produced different geomety types
                            temp_list = int_geom.asGeometryCollection()
                            for i in temp_list:
                                if i.type() == geom.type():
                                    int_geom = QgsGeometry(i)
                                    try:
                                        int_geom.convertToMultiType()
                                        outFeat.setGeometry(int_geom)
                                        outFeat.setAttributes(atMapA + atMapB)
                                        sink.addFeature(
                                            outFeat, QgsFeatureSink.FastInsert)
                                    except:
                                        feedback.pushInfo(
                                            self.
                                            tr('Feature geometry error: One or more output features ignored due to invalid geometry.'
                                               ))
                        else:
                            # Geometry list: prevents writing error
                            # in geometries of different types
                            # produced by the intersection
                            # fix #3549
                            if QgsWkbTypes.geometryType(int_geom.wkbType(
                            )) == QgsWkbTypes.geometryType(geomType):
                                try:
                                    int_geom.convertToMultiType()
                                    outFeat.setGeometry(int_geom)
                                    outFeat.setAttributes(atMapA + atMapB)
                                    sink.addFeature(outFeat,
                                                    QgsFeatureSink.FastInsert)
                                except:
                                    feedback.pushInfo(
                                        self.
                                        tr('Feature geometry error: One or more output features ignored due to invalid geometry.'
                                           ))

                # the remaining bit of featA's geometry
                # if there is nothing left, this will just silently fail and we're good
                diff_geom = QgsGeometry(geom)
                if len(lstIntersectingB) != 0:
                    intB = QgsGeometry.unaryUnion(lstIntersectingB)
                    diff_geom = diff_geom.difference(intB)

                if diff_geom.wkbType(
                ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(
                        diff_geom.wkbType()) == QgsWkbTypes.GeometryCollection:
                    temp_list = diff_geom.asGeometryCollection()
                    for i in temp_list:
                        if i.type() == geom.type():
                            diff_geom = QgsGeometry(i)
                try:
                    diff_geom.convertToMultiType()
                    outFeat.setGeometry(diff_geom)
                    outFeat.setAttributes(atMapA)
                    sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                except:
                    feedback.pushInfo(
                        self.
                        tr('Feature geometry error: One or more output features ignored due to invalid geometry.'
                           ))

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

        length = len(sourceA.fields())
        atMapA = [None] * length

        for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(
                sourceA.sourceCrs(), context.transformContext())):
            if feedback.isCanceled():
                break

            add = False
            geom = featA.geometry()
            diff_geom = QgsGeometry(geom)
            atMap = [None] * length
            atMap.extend(featA.attributes())
            intersects = indexA.intersects(geom.boundingBox())

            if len(intersects) < 1:
                try:
                    geom.convertToMultiType()
                    outFeat.setGeometry(geom)
                    outFeat.setAttributes(atMap)
                    sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                except:
                    feedback.pushInfo(
                        self.
                        tr('Feature geometry error: One or more output features ignored due to invalid geometry.'
                           ))
            else:
                request = QgsFeatureRequest().setFilterFids(
                    intersects).setSubsetOfAttributes([])
                request.setDestinationCrs(sourceA.sourceCrs(),
                                          context.transformContext())

                # use prepared geometries for faster intersection tests
                engine = QgsGeometry.createGeometryEngine(diff_geom.constGet())
                engine.prepareGeometry()

                for featB in sourceA.getFeatures(request):
                    atMapB = featB.attributes()
                    tmpGeom = featB.geometry()

                    if engine.intersects(tmpGeom.constGet()):
                        add = True
                        diff_geom = QgsGeometry(diff_geom.difference(tmpGeom))
                    else:
                        try:
                            # Ihis only happens if the bounding box
                            # intersects, but the geometry doesn't
                            diff_geom.convertToMultiType()
                            outFeat.setGeometry(diff_geom)
                            outFeat.setAttributes(atMap)
                            sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                        except:
                            feedback.pushInfo(
                                self.
                                tr('Feature geometry error: One or more output features ignored due to invalid geometry.'
                                   ))

            if add:
                try:
                    diff_geom.convertToMultiType()
                    outFeat.setGeometry(diff_geom)
                    outFeat.setAttributes(atMap)
                    sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                except:
                    feedback.pushInfo(
                        self.
                        tr('Feature geometry error: One or more output features ignored due to invalid geometry.'
                           ))

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

        return {self.OUTPUT: dest_id}
예제 #29
0
    def processAlgorithm(self, progress):
        layerA = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_A))
        splitLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_B))

        sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B)
        fieldList = layerA.fields()

        writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList,
                        QgsWkbTypes.multiType(layerA.wkbType()), layerA.crs())

        spatialIndex = QgsSpatialIndex()
        splitGeoms = {}
        request = QgsFeatureRequest()
        request.setSubsetOfAttributes([])

        for aSplitFeature in vector.features(splitLayer, request):
            splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry()
            spatialIndex.insertFeature(aSplitFeature)
            # honor the case that user has selection on split layer and has setting "use selection"

        outFeat = QgsFeature()
        features = vector.features(layerA)

        if len(features) == 0:
            total = 100
        else:
            total = 100.0 / float(len(features))

        for current, inFeatA in enumerate(features):
            inGeom = inFeatA.geometry()
            attrsA = inFeatA.attributes()
            outFeat.setAttributes(attrsA)

            if inGeom.isMultipart():
                inGeoms = []

                for g in inGeom.asGeometryCollection():
                    inGeoms.append(g)
            else:
                inGeoms = [inGeom]

            lines = spatialIndex.intersects(inGeom.boundingBox())

            if len(lines) > 0:  # has intersection of bounding boxes
                splittingLines = []

                engine = QgsGeometry.createGeometryEngine(inGeom.geometry())
                engine.prepareGeometry()

                for i in lines:
                    try:
                        splitGeom = splitGeoms[i]
                    except:
                        continue

                    # check if trying to self-intersect
                    if sameLayer:
                        if inFeatA.id() == i:
                            continue

                    if engine.intersects(splitGeom.geometry()):
                        splittingLines.append(splitGeom)

                if len(splittingLines) > 0:
                    for splitGeom in splittingLines:
                        splitterPList = None
                        outGeoms = []

                        split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry())
                        split_geom_engine.prepareGeometry()

                        while len(inGeoms) > 0:
                            inGeom = inGeoms.pop()

                            if inGeom.isEmpty(): # this has been encountered and created a run-time error
                                continue

                            if split_geom_engine.intersects(inGeom.geometry()):
                                inPoints = vector.extractPoints(inGeom)
                                if splitterPList == None:
                                    splitterPList = vector.extractPoints(splitGeom)

                                try:
                                    result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False)
                                except:
                                    ProcessingLog.addToLog(ProcessingLog.LOG_WARNING,
                                                           self.tr('Geometry exception while splitting'))
                                    result = 1

                                # splitGeometry: If there are several intersections
                                # between geometry and splitLine, only the first one is considered.
                                if result == 0:  # split occurred
                                    if inPoints == vector.extractPoints(inGeom):
                                        # bug in splitGeometry: sometimes it returns 0 but
                                        # the geometry is unchanged
                                        outGeoms.append(inGeom)
                                    else:
                                        inGeoms.append(inGeom)

                                        for aNewGeom in newGeometries:
                                            inGeoms.append(aNewGeom)
                                else:
                                    outGeoms.append(inGeom)
                            else:
                                outGeoms.append(inGeom)

                        inGeoms = outGeoms

            parts = []

            for aGeom in inGeoms:
                passed = True

                if QgsWkbTypes.geometryType( aGeom.wkbType() )  == QgsWkbTypes.LineGeometry:
                    numPoints = aGeom.geometry().numPoints()

                    if numPoints <= 2:
                        if numPoints == 2:
                            passed = not aGeom.geometry().isClosed() # tests if vertex 0 = vertex 1
                        else:
                            passed = False
                            # sometimes splitting results in lines of zero length

                if passed:
                    parts.append(aGeom)

            if len(parts) > 0:
                outFeat.setGeometry(QgsGeometry.collectGeometry(parts))
                writer.addFeature(outFeat)

            progress.setPercentage(int(current * total))
        del writer
예제 #30
0
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """

        # Retrieve the feature origins_source and sink. The 'sink_id' variable is used
        # to uniquely identify the feature sink, and must be included in the
        # dictionary returned by the processAlgorithm function.
        network_source = self.parameterAsVectorLayer(parameters, self.NETWORK,
                                                     context)

        rlid_field = None
        from_field = None
        to_field = None
        class_field = None
        oper_field = None
        speedf_field = None
        speedb_field = None
        adt_field = None
        type_field = None

        for field in network_source.fields():
            name = field.name()
            lname = name.lower()

            if lname == 'route_id':
                rlid_field = name
            elif lname == 'from_measure':
                from_field = name
            elif lname == 'to_measure':
                to_field = name
            elif lname == 'klass_181':
                class_field = name
            elif lname == 'vagha_6':
                oper_field = name
            elif lname == 'f_hogst_225':
                speedf_field = name
            elif lname == 'b_hogst_225':
                speedb_field = name
            elif lname == 'adt_f_117':
                adt_field = name
            elif lname == 'vagtr_474':
                type_field = name

        expr = f'"{type_field}" = 2'
        if not network_source.setSubsetString(expr):
            raise Exception('Failed to set subset')

        result = processing.run(
            'native:buffer',
            {
                'INPUT': network_source,
                #'DISSOLVE': True,
                'DISTANCE': 100,
                'END_CAP_STYLE': 2,
                'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT,
            },
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )['OUTPUT']

        buffer_layer = context.takeResultLayer(result)

        print(len(buffer_layer))
        bike_buffer = buffer_layer.getGeometry(1)
        print(bike_buffer)

        index = QgsSpatialIndex(buffer_layer.getFeatures(), feedback=feedback)
        geoms = {f.id(): f.geometry() for f in buffer_layer.getFeatures()}

        ids = index.intersects(bike_buffer.boundingBox())
        print(ids)
        for i in ids:
            if bike_buffer.intersects(geometry=geoms[i]):
                print('BOOOOM!')

        fields = QgsFields()
        fields.append(QgsField('rlid', QVariant.String))
        fields.append(QgsField('from_measure', QVariant.Double))
        fields.append(QgsField('to_measure', QVariant.Double))
        fields.append(QgsField('oper', QVariant.Int))
        fields.append(QgsField('class', QVariant.Int))
        fields.append(QgsField('speed', QVariant.Int))
        fields.append(QgsField('adt', QVariant.Int))

        fields.append(QgsField('vgu', QVariant.Int))
        fields.append(QgsField('lts', QVariant.Int))
        fields.append(QgsField('ratio', QVariant.Double))
        ratios = {1: 0, 2: 1, 3: 1.58, 4: 2}

        fields.append(QgsField('gc', QVariant.Bool))

        expr = (
            f'"{type_field}" = 1 AND "{oper_field}" IN (1, 2) AND "{class_field}" <= 5'
        )
        print(expr)
        if not network_source.setSubsetString(expr):
            raise Exception('Failed to set subset')

        network_layer = make_single(
            network_source,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        if not network_source.setSubsetString(''):
            raise Exception('Failed to set subset')

        (sink, sink_id) = self.parameterAsSink(
            parameters,
            name=self.OUTPUT,
            context=context,
            fields=fields,
            geometryType=network_layer.wkbType(),
            crs=network_layer.sourceCrs(),
        )

        n = len(network_layer)
        chunk = n // 100
        print(n, chunk)
        for i, feat in enumerate(network_layer.getFeatures()):
            """if not feat[type_field] == 1:
                continue
            if not feat[oper_field] in [1, 2]:
                continue
            if feat[class_field] > 5:
                continue"""

            geom = feat.geometry()

            _feat = QgsFeature(fields)
            _feat.setGeometry(geom)
            _feat['rlid'] = feat[rlid_field]
            _feat['from_measure'] = feat[from_field]
            _feat['to_measure'] = feat[to_field]

            _feat['speed'] = speed = max(feat[speedf_field],
                                         feat[speedb_field])
            _feat['adt'] = adt = feat[adt_field]
            _feat['class'] = clss = feat[class_field]
            _feat['oper'] = oper = feat[oper_field]

            # Find existing bike infra
            gc = False
            for fid in index.intersects(geom.boundingBox()):
                if geom.intersects(geometry=geoms[fid]):
                    gc = True
                    break

            _feat['gc'] = gc

            if not adt:
                if clss <= 3:
                    adt = 9001
                elif clss == 4:
                    adt = 3000
                else:
                    adt = 900

            if not speed:
                speed = 70

            # VGU classify required infra
            vgu = None
            if clss <= 2:
                vgu = 5
            elif speed <= 30:
                vgu = 1
            elif speed <= 60 and adt <= 2000:
                vgu = 2
            elif speed <= 80 and adt <= 4000 or speed <= 40 and adt > 4000:
                vgu = 3
            elif speed > 80 or adt > 4000:
                vgu = 4

            # Debug missing
            if not adt:
                vgu = -1
            if not speed:
                vgu = -2

            _feat['vgu'] = vgu

            # LTS
            lts = None
            if clss <= 2 or speed > 80 or (adt > 4000 and speed > 40
                                           and not gc):
                lts = 4
            elif speed <= 30 or (adt <= 1000 and speed <= 40):
                lts = 1
            elif gc and (adt <= 1000 or (speed <= 60 and adt <= 2000) or
                         (speed <= 40 and adt <= 4000)):
                lts = 1
            elif gc and speed > 60 and adt > 4000:
                lts = 3
            elif not gc and (adt > 4000 or (speed > 40 and adt >= 2000) or
                             (speed > 60 and adt >= 1000)):
                lts = 3
            else:
                lts = 2

            _feat['lts'] = lts
            _feat['ratio'] = ratios[lts]

            # Done
            sink.addFeature(_feat)

            if feedback and i % chunk == 0:
                p = 100 * i / n
                feedback.setProgress(p)

        return {self.OUTPUT: sink_id}
예제 #31
0
class breakTool(QObject):

    finished = pyqtSignal(object)
    error = pyqtSignal(Exception, basestring)
    progress = pyqtSignal(float)
    warning = pyqtSignal(str)
    killed = pyqtSignal(bool)

    def __init__(self,layer, tolerance, uid, errors, unlinks):
        QObject.__init__(self)

        self.layer = layer
        self.feat_count = self.layer.featureCount()
        self.tolerance = tolerance
        self.uid = uid

        self.errors = errors
        self.errors_features = {}
        self.unlinks = unlinks
        self.unlinked_features = []
        self.unlinks_count = 0
        self.ml_keys = {}
        self.br_keys = {}

        self.features = []
        self.attributes = {}
        self.geometries = {}
        self.geometries_wkt = {}
        self.geometries_vertices = {}
        # create spatial index object
        self.spIndex = QgsSpatialIndex()
        self.layer_fields = [QgsField(i.name(), i.type()) for i in self.layer.dataProvider().fields()]

    def add_edges(self):

        new_key_count = 0
        f_count = 1

        for f in self.layer.getFeatures():

            self.progress.emit(3 * f_count / self.feat_count)
            f_count += 1

            if self.killed is True:
                break

            geom_type = f.geometry().wkbType()

            if geom_type not in [5,2,1] and f.geometry().geometry().is3D():
                f.geometry().geometry().dropZValue()
                geom_type = f.geometry().wkbType()

            if geom_type == 5:
                if self.errors:
                    self.errors_features[f.id()] = ('multipart', f.geometry().exportToWkt())
                for multipart in f.geometry().asGeometryCollection():
                    new_key_count += 1
                    attr = f.attributes()
                    new_feat = QgsFeature()
                    new_feat.setAttributes(attr)
                    new_feat.setFeatureId(new_key_count)
                    if self.tolerance:
                        snapped_wkt = make_snapped_wkt(multipart.exportToWkt(), self.tolerance)
                    else:
                        snapped_wkt = multipart.exportToWkt()
                    snapped_geom = QgsGeometry.fromWkt(snapped_wkt)
                    new_feat.setGeometry(snapped_geom)
                    self.features.append(new_feat)
                    self.attributes[new_key_count] = attr
                    self.geometries[new_key_count] = new_feat.geometryAndOwnership()
                    self.geometries_wkt[new_key_count] = snapped_wkt
                    self.geometries_vertices[new_key_count] = [vertex for vertex in vertices_from_wkt_2(snapped_wkt)]
                    # insert features to index
                    self.spIndex.insertFeature(new_feat)
                    self.ml_keys[new_key_count] = f.id()
            elif geom_type == 1:
                if self.errors:
                    self.errors_features[f.id()] = ('point', QgsGeometry().exportToWkt())
            elif not f.geometry().isGeosValid():
                if self.errors:
                    self.errors_features[f.id()] = ('invalid', QgsGeometry().exportToWkt())
            elif geom_type == 2:
                attr = f.attributes()
                if self.tolerance:
                    snapped_wkt = make_snapped_wkt(f.geometry().exportToWkt(), self.tolerance)
                else:
                    snapped_wkt = f.geometry().exportToWkt()
                snapped_geom = QgsGeometry.fromWkt(snapped_wkt)
                f.setGeometry(snapped_geom)
                new_key_count += 1
                f.setFeatureId(new_key_count)
                self.features.append(f)
                self.attributes[f.id()] = attr
                self.geometries[f.id()] = f.geometryAndOwnership()
                self.geometries_wkt[f.id()] = snapped_wkt
                self.geometries_vertices[f.id()] = [vertex for vertex in vertices_from_wkt_2(snapped_wkt)]
                # insert features to index
                self.spIndex.insertFeature(f)
                self.ml_keys[new_key_count] = f.id()

    def break_features(self):

        broken_features = []
        f_count = 1

        for fid in self.geometries.keys():

            if self.killed is True:
                break

            f_geom = self.geometries[fid]
            f_attrs = self.attributes[fid]

            # intersecting lines
            gids = self.spIndex.intersects(f_geom.boundingBox())

            self.progress.emit((45 * f_count / self.feat_count) + 5)
            f_count += 1

            f_errors, vertices = self.find_breakages(fid, gids)

            if self.errors and f_errors:
                original_id = self.ml_keys[fid]
                try:
                    updated_errors = self.errors_features[original_id][0] + f_errors
                    self.errors_features[original_id] = (updated_errors, self.errors_features[original_id][1])
                except KeyError:
                    self.errors_features[original_id] = (f_errors, self.geometries[fid].exportToWkt())

            if f_errors is None:
                vertices = [0, len(f_geom.asPolyline()) - 1 ]

            if f_errors in ['breakage, overlap', 'breakage', 'overlap', None]:
                for ind, index in enumerate(vertices):
                    if ind != len(vertices) - 1:
                        points = [self.geometries_vertices[fid][i] for i in range(index, vertices[ind + 1] + 1)]
                        p = ''
                        for point in points:
                            p += point[0] + ' ' + point[1] + ', '
                        wkt = 'LINESTRING(' + p[:-2] + ')'
                        self.feat_count += 1
                        new_fid = self.feat_count
                        new_feat = [new_fid, f_attrs, wkt]
                        broken_features.append(new_feat)
                        self.br_keys[new_fid] = fid

        return broken_features

    def kill(self):
        self.br_killed = True

    def find_breakages(self, fid, gids):

        f_geom = self.geometries[fid]

        # errors checks
        must_break = False
        is_closed = False
        if f_geom.asPolyline()[0] == f_geom.asPolyline()[-1]:
            is_closed = True
        is_orphan = True
        is_duplicate = False
        has_overlaps = False

        # get breaking points
        breakages = []

        # is self intersecting
        is_self_intersersecting = False
        for i in f_geom.asPolyline():
            if f_geom.asPolyline().count(i) > 1:
                point = QgsGeometry().fromPoint(QgsPoint(i[0], i[1]))
                breakages.append(point)
                is_self_intersersecting = True
                must_break = True

        for gid in gids:

            g_geom = self.geometries[gid]

            if gid < fid:
                # duplicate geometry
                if f_geom.isGeosEqual(g_geom):
                    is_duplicate = True

                if self.unlinks:
                    if f_geom.crosses(g_geom):
                        crossing_point = f_geom.intersection(g_geom)
                        if crossing_point.wkbType() == 1:
                            self.unlinks_count += 1
                            unlinks_attrs = [[self.unlinks_count], [gid], [fid], [crossing_point.asPoint()[0]],
                                             [crossing_point.asPoint()[1]]]
                            self.unlinked_features.append([self.unlinks_count, unlinks_attrs, crossing_point.exportToWkt()])
                        elif crossing_point.wkbType() == 4:
                            for cr_point in crossing_point.asGeometryCollection():
                                self.unlinks_count += 1
                                unlinks_attrs = [[self.unlinks_count], [gid], [fid], [cr_point.asPoint()[0]],
                                                 [cr_point.asPoint()[1]]]
                                self.unlinked_features.append([self.unlinks_count, unlinks_attrs, cr_point.exportToWkt()])

            if is_duplicate is False:
                intersection = f_geom.intersection(g_geom)
                # intersecting geometries at point
                if intersection.wkbType() == 1 and point_is_vertex(intersection, f_geom):
                    breakages.append(intersection)
                    is_orphan = False
                    must_break = True

                # intersecting geometries at multiple points
                elif intersection.wkbType() == 4:
                    for point in intersection.asGeometryCollection():
                        if point_is_vertex(point, f_geom):
                            breakages.append(point)
                            is_orphan = False
                            must_break = True

                # overalpping geometries
                elif intersection.wkbType() == 2 and intersection.length() != f_geom.length():
                    point1 = QgsGeometry.fromPoint(QgsPoint(intersection.asPolyline()[0]))
                    point2 = QgsGeometry.fromPoint(QgsPoint(intersection.asPolyline()[-1]))
                    if point_is_vertex(point1, f_geom):
                        breakages.append(point1)
                        is_orphan = False
                        must_break = True
                    if point_is_vertex(point2, f_geom):
                        breakages.append(point2)
                        is_orphan = False
                        must_break = True

                # overalpping multi-geometries
                # every feature overlaps with itself as a multilinestring
                elif intersection.wkbType() == 5 and intersection.length() != f_geom.length():
                    point1 = QgsGeometry.fromPoint(QgsPoint(intersection.asGeometryCollection()[0].asPolyline()[0]))
                    point2 = QgsGeometry.fromPoint(QgsPoint(intersection.asGeometryCollection()[-1].asPolyline()[-1]))
                    if point_is_vertex(point1, f_geom):
                        is_orphan = False
                        has_overlaps = True
                        breakages.append(point1)
                    if point_is_vertex(point2, f_geom):
                        is_orphan = False
                        has_overlaps = True
                        breakages.append(point2)

        if is_duplicate is True:
            return 'duplicate', []
        else:
            # add first and last vertex
            vertices = set([vertex for vertex in find_vertex_index(breakages, f_geom)])
            vertices = list(vertices) + [0] + [len(f_geom.asPolyline()) - 1]
            vertices = list(set(vertices))
            vertices.sort()

            if is_orphan:
                if is_closed is True:
                    return 'closed polyline', []
                else:
                    return 'orphan', []

            elif is_self_intersersecting:
                if has_overlaps:
                    return 'breakage, overlap', vertices
                else:
                    return 'breakage', vertices

            elif has_overlaps or must_break:
                if has_overlaps is True and must_break is True:
                    return 'breakage, overlap', vertices
                elif has_overlaps is True and must_break is False:
                    return 'overlap', vertices
                elif has_overlaps is False and must_break is True:
                    if len(vertices) > 2:
                        return 'breakage', vertices
                    else:
                        return None, []
            else:
                return None, []

    def updateErrors(self, errors_dict):

        for k, v in errors_dict.items():

            try:
                original_id = self.br_keys[k]
                try:
                    original_id = self.ml_keys[k]
                except KeyError:
                    pass
            except KeyError:
                original_id = None

            if original_id:
                try:
                    updated_errors = self.errors_features[original_id][0]
                    if ', continuous line' not in self.errors_features[original_id][0]:
                        updated_errors += ', continuous line'
                    self.errors_features[original_id] = (updated_errors, self.errors_features[original_id][1])
                except KeyError:
                    self.errors_features[original_id] = ('continuous line', self.geometries[original_id].exportToWkt())
예제 #32
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        feedback.pushInfo(
            self.tr('{} duplicate features removed'.format(removed)))
        return {
            self.OUTPUT: dest_id,
            self.DUPLICATE_COUNT: removed,
            self.RETAINED_COUNT: len(output_feature_ids)
        }
예제 #33
0
class PW_OCR_Algorithm(QgsProcessingAlgorithm):

    INPUT = 'INPUT'
    RASTER_INPUT = 'RASTER INPUT'
    FIELD = 'FIELD'
    ALL_ACTIVE_RASTERS = 'ALL ACTIVE RASTERS'
    PSM = 'PSM'
    OEM = 'OEM'
    REMOVE_COMMA = 'REMOVE_COMMA'
    TEMP_FILES_LOC = 'TEMP_FILES_LOC'
    OUTPUT = 'OUTPUT'

    def tr(self, string):
        return QCoreApplication.translate('Processing', string)

    def createInstance(self):
        return PW_OCR_Algorithm()

    def name(self):
        return 'pw_ocr'

    def displayName(self):
        return self.tr('PW OCR')

    def group(self):
        return self.tr('PW')

    def groupId(self):
        return 'pw'

    def shortHelpString(self):
        help = """This algorithm recognizes text from raster images inside input polygon features and saves as attribute value of output layer.\
        <hr>
        <b>Input polygon layer</b>\
        <br>The features used to recognize text inside them.\
        <br><br><b>Text output field</b>\
        <br>The field in the input table in which the recognized text will be add.\
        <br><br><b>Run for all raster layers</b>\
        <br>The algorithm will recognize text from all active raster layers, if checked.\
        <br><br><b>Input raster layer</b>\
        <br>If above checkbox unchecked, the algorithm will recognize text only from this raster layer.\
        <br>In case of multiband raster images, the only first band will be used.\
        <br><br><b>Page Segmentation Mode</b>\
        <br><i>Tesseract</i> Page Segmentation Mode.\
        <br><br><b>OCR Engine Model</b>\
        <br><i>Tesseract</i> OCR Engine Model.\
        <br><br><b>Remove comma</b>\
        <br>If comma is the last character in recognized text, it will be removed.\
        <br><br><b>Temporary files location</b>\
        <br>Location of such transitional files like image translated to 8bit TIFF, image clipped to the single feature and shapefile contains only one feature. These files are created during iterating over all input features.\
        <br><br><b>Output layer</b>\
        <br>Location of the output layer with filled text attribute.\
        """
        return self.tr(help)

    def initAlgorithm(self, config=None):

        self.addParameter(
            QgsProcessingParameterFeatureSource(
                self.INPUT, self.tr('Input polygon layer'),
                [QgsProcessing.TypeVectorPolygon]))
        self.addParameter(
            QgsProcessingParameterField(
                self.FIELD,
                self.tr('Text output field'),
                parentLayerParameterName=self.INPUT,
                type=QgsProcessingParameterField.DataType.String))
        self.addParameter(
            QgsProcessingParameterBoolean(
                self.ALL_ACTIVE_RASTERS, self.tr('Run for all raster layers')))
        self.addParameter(
            QgsProcessingParameterRasterLayer(
                self.RASTER_INPUT,
                self.tr('Input raster layer'),
                optional=True,
            ))
        self.addParameter(
            QgsProcessingParameterEnum(
                self.PSM,
                self.tr('Page Segmentation Mode'),
                options=[
                    'Orientation and script detection (OSD) only.',
                    'Automatic page segmentation with OSD.',
                    'Automatic page segmentation, but no OSD, or OCR.',
                    'Fully automatic page segmentation, but no OSD. (Default if no config)',
                    'Assume a single column of text of variable sizes.',
                    'Assume a single uniform block of vertically aligned text.',
                    'Assume a single uniform block of text.',
                    'Treat the image as a single text line.',
                    'Treat the image as a single word.',
                    'Treat the image as a single word in a circle.',
                    'Treat the image as a single character.',
                    'Sparse text. Find as much text as possible in no particular order.',
                    'Sparse text with OSD.',
                    'Raw line. Treat the image as a single text line, bypassing hacks that are Tesseract-specific.'
                ],
                defaultValue=7))
        self.addParameter(
            QgsProcessingParameterEnum(
                self.OEM,
                self.tr('OCR Engine Model'),
                options=['Legacy Tesseract', 'LSTM', '2', '3'],
                defaultValue=1))
        self.addParameter(
            QgsProcessingParameterBoolean(self.REMOVE_COMMA,
                                          self.tr('Remove comma'), True))
        self.addParameter(
            QgsProcessingParameterFolderDestination(
                self.TEMP_FILES_LOC,
                self.tr('Temporary files location'),
                optional=True))
        self.addParameter(
            QgsProcessingParameterFeatureSink(self.OUTPUT,
                                              self.tr('Output layer')))

    def processAlgorithm(self, parameters, context, feedback):

        self.feature_source = self.parameterAsSource(parameters, self.INPUT,
                                                     context)
        raster_lyr = self.parameterAsRasterLayer(parameters, self.RASTER_INPUT,
                                                 context)
        all_rasters = self.parameterAsBool(parameters, self.ALL_ACTIVE_RASTERS,
                                           context)
        temp_path = self.parameterAsString(parameters, '', context)
        self.dest_field = self.parameterAsString(parameters, self.FIELD,
                                                 context)
        psm = self.parameterAsInt(parameters, 'PSM', context)
        oem = self.parameterAsInt(parameters, 'OEM', context)
        self.comma = self.parameterAsBool(parameters, 'Remove_comma', context)
        (self.sink,
         dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                         self.feature_source.fields(),
                                         self.feature_source.wkbType(),
                                         self.feature_source.sourceCrs())
        self.source_layer = self.feature_source.materialize(
            QgsFeatureRequest())
        feedback.pushInfo('Temporary files path: ' + str(temp_path))
        self.source_encod = self.source_layer.dataProvider().encoding()
        '''context.setDefaultEncoding(self.source_encod)
        self.output_encod = context.defaultEncoding()
        
        feedback.pushInfo('sys.getdefaultencoding(): ' + sys.getdefaultencoding())
        feedback.pushInfo('in: ' + self.source_encod + ', out: ' + self.output_encod)'''

        if self.source_layer == None:
            list = QgsProject.instance().mapLayersByName(
                self.feature_source.sourceName())
            for lyr in list:
                if self.feature_source.sourceCrs() == lyr.sourceCrs():
                    self.source_layer = lyr

        #feedback.pushInfo('self.source_layer.name(): ' + self.source_layer.name())

        if self.feature_source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))
        if raster_lyr is None and not all_rasters:
            feedback.pushInfo('\nNo raster layer selected!\n')
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.RASTER_INPUT))
        self.output_temp_tif = os.path.normpath(
            os.path.join(temp_path, 'output.tif'))
        self.output_temp_shp = os.path.normpath(
            os.path.join(temp_path, 'each_feature.shp'))
        self.output_temp_page = os.path.normpath(
            os.path.join(temp_path, 'current_page.tif'))
        '''here is tesseract config string'''
        self.config = '--psm ' + str(psm) + ' --oem ' + str(oem)
        feedback.pushInfo('Tessearct config: ' + self.config)
        '''creating temporary shp file, necessary for clipping'''
        self.crs = self.feature_source.sourceCrs().authid()
        layer = QgsVectorLayer(
            "multipolygon?crs=" + self.crs + "&field=id:integer",
            "temporary layer", "memory")
        QgsVectorFileWriter.writeAsVectorFormat(
            layer, self.output_temp_shp, self.source_encod,
            self.feature_source.sourceCrs(), "ESRI Shapefile", False)
        self.temp_shp_layer = QgsVectorLayer(self.output_temp_shp, "temp",
                                             "ogr")

        features = self.feature_source.getFeatures(QgsFeatureRequest())

        self.index = QgsSpatialIndex()
        for feat in features:
            self.index.insertFeature(feat)

        feedback.pushInfo('\nprocessing time calculating...\n')
        n = []
        if not all_rasters and raster_lyr:
            n = self.index.intersects(raster_lyr.extent())
        else:
            for layer in iface.mapCanvas().layers():
                if layer.type() == 1:
                    n = n + self.index.intersects(layer.extent())
        self.total = len(n)
        self.actual = 0
        if self.total > 0: feedback.setProgress(self.actual / self.total * 100)

        if not all_rasters:
            self.OnThisRaster(feedback, raster_lyr)
        else:
            for layer in iface.mapCanvas().layers():
                if feedback.isCanceled(): break
                if layer.type() == 1:
                    self.OnThisRaster(feedback, layer)

        return {self.OUTPUT: dest_id}

    def OnThisRaster(self, feedback, Raster_lyr):

        idsList = self.index.intersects(Raster_lyr.extent())
        if idsList:
            translateopts = gdal.TranslateOptions(
                outputType=gdal.GDT_Byte,  # Eight bit unsigned integer
                bandList=[1],  # Notice that exports only first band
                format='GTiff')
            ds = gdal.Translate(self.output_temp_page,
                                Raster_lyr.source(),
                                options=translateopts)
            if ds is not None: ds = 'image translated to GTiff'
            else: ds = '<red> something went wrong with translating to GTiff'
            feedback.pushCommandInfo('\nComputing image ' +
                                     str(Raster_lyr.name()) + '\n' + str(ds) +
                                     '\n')
            for id in idsList:
                #Gdyby tutaj się udało wkomponować coś jak getFeaturebyID byłoby pewnie szybciej
                for feat in self.feature_source.getFeatures(
                        QgsFeatureRequest()):
                    if int(feat.id()) == id:
                        self.OnThisFeature(feedback, feat,
                                           self.output_temp_page)
                        break

    def OnThisFeature(self, feedback, feat, Raster_lyr_source):

        pr = self.temp_shp_layer.dataProvider()
        for temp_feature in pr.getFeatures():
            pr.deleteFeatures([temp_feature.id()])
        pr.addFeatures([feat])

        self.ClipRasterByPolygon(feedback, Raster_lyr_source,
                                 self.output_temp_shp, self.output_temp_tif)
        img = Image.open(self.output_temp_tif)
        text = pytesseract.image_to_string(img, lang='pol', config=self.config)
        if self.comma:
            if text[-1:] == ',': text = text[:-1]

        feat[self.dest_field] = text  #.encode('utf8')#.decode('CP1250')

        self.sink.addFeature(feat, QgsFeatureSink.FastInsert)

        self.actual = self.actual + 1
        feedback.setProgress(self.actual / self.total * 100)
        feedback.setProgressText(
            str(self.actual) + '/' + str(self.total) + '       ' + 'id:  ' +
            str(feat.id()))
        feedback.pushCommandInfo(text)

    def ClipRasterByPolygon(self, feedback, rasterPath, polygonPath,
                            outputPath):

        warpopts = gdal.WarpOptions(
            outputType=gdal.GDT_Byte,
            srcSRS=self.crs,
            cutlineDSName=polygonPath,
            cropToCutline=True,
            dstNodata=
            255.0,  # to jest rozwiązanie wszystkich światowych problemów z dziedziny OCR
        )
        ds = gdal.Warp(outputPath, rasterPath, options=warpopts)
예제 #34
0
    def processAlgorithm(self, feedback):
        layerA = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_A))
        splitLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_B))

        sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B)
        fieldList = layerA.fields()

        writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList,
                                                                     QgsWkbTypes.multiType(layerA.wkbType()), layerA.crs())

        spatialIndex = QgsSpatialIndex()
        splitGeoms = {}
        request = QgsFeatureRequest()
        request.setSubsetOfAttributes([])

        for aSplitFeature in vector.features(splitLayer, request):
            splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry()
            spatialIndex.insertFeature(aSplitFeature)
            # honor the case that user has selection on split layer and has setting "use selection"

        outFeat = QgsFeature()
        features = vector.features(layerA)

        if len(features) == 0:
            total = 100
        else:
            total = 100.0 / float(len(features))

        for current, inFeatA in enumerate(features):
            inGeom = inFeatA.geometry()
            attrsA = inFeatA.attributes()
            outFeat.setAttributes(attrsA)

            if inGeom.isMultipart():
                inGeoms = []

                for g in inGeom.asGeometryCollection():
                    inGeoms.append(g)
            else:
                inGeoms = [inGeom]

            lines = spatialIndex.intersects(inGeom.boundingBox())

            if len(lines) > 0:  # has intersection of bounding boxes
                splittingLines = []

                engine = QgsGeometry.createGeometryEngine(inGeom.geometry())
                engine.prepareGeometry()

                for i in lines:
                    try:
                        splitGeom = splitGeoms[i]
                    except:
                        continue

                    # check if trying to self-intersect
                    if sameLayer:
                        if inFeatA.id() == i:
                            continue

                    if engine.intersects(splitGeom.geometry()):
                        splittingLines.append(splitGeom)

                if len(splittingLines) > 0:
                    for splitGeom in splittingLines:
                        splitterPList = None
                        outGeoms = []

                        split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry())
                        split_geom_engine.prepareGeometry()

                        while len(inGeoms) > 0:
                            inGeom = inGeoms.pop()

                            if inGeom.isNull():  # this has been encountered and created a run-time error
                                continue

                            if split_geom_engine.intersects(inGeom.geometry()):
                                inPoints = vector.extractPoints(inGeom)
                                if splitterPList is None:
                                    splitterPList = vector.extractPoints(splitGeom)

                                try:
                                    result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False)
                                except:
                                    ProcessingLog.addToLog(ProcessingLog.LOG_WARNING,
                                                           self.tr('Geometry exception while splitting'))
                                    result = 1

                                # splitGeometry: If there are several intersections
                                # between geometry and splitLine, only the first one is considered.
                                if result == 0:  # split occurred
                                    if inPoints == vector.extractPoints(inGeom):
                                        # bug in splitGeometry: sometimes it returns 0 but
                                        # the geometry is unchanged
                                        outGeoms.append(inGeom)
                                    else:
                                        inGeoms.append(inGeom)

                                        for aNewGeom in newGeometries:
                                            inGeoms.append(aNewGeom)
                                else:
                                    outGeoms.append(inGeom)
                            else:
                                outGeoms.append(inGeom)

                        inGeoms = outGeoms

            parts = []

            for aGeom in inGeoms:
                passed = True

                if QgsWkbTypes.geometryType(aGeom.wkbType()) == QgsWkbTypes.LineGeometry:
                    numPoints = aGeom.geometry().numPoints()

                    if numPoints <= 2:
                        if numPoints == 2:
                            passed = not aGeom.geometry().isClosed()  # tests if vertex 0 = vertex 1
                        else:
                            passed = False
                            # sometimes splitting results in lines of zero length

                if passed:
                    parts.append(aGeom)

            if len(parts) > 0:
                outFeat.setGeometry(QgsGeometry.collectGeometry(parts))
                writer.addFeature(outFeat)

            feedback.setProgress(int(current * total))
        del writer
예제 #35
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        line_source = self.parameterAsSource(parameters, self.LINES, context)

        sameLayer = parameters[self.INPUT] == parameters[self.LINES]

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               source.fields(), QgsWkbTypes.multiType(source.wkbType()), source.sourceCrs())

        spatialIndex = QgsSpatialIndex()
        splitGeoms = {}
        request = QgsFeatureRequest()
        request.setSubsetOfAttributes([])
        request.setDestinationCrs(source.sourceCrs())

        for aSplitFeature in line_source.getFeatures(request):
            if feedback.isCanceled():
                break

            splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry()
            spatialIndex.insertFeature(aSplitFeature)
            # honor the case that user has selection on split layer and has setting "use selection"

        outFeat = QgsFeature()
        features = source.getFeatures()

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

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

            inGeom = inFeatA.geometry()
            attrsA = inFeatA.attributes()
            outFeat.setAttributes(attrsA)

            if inGeom.isMultipart():
                inGeoms = []

                for g in inGeom.asGeometryCollection():
                    inGeoms.append(g)
            else:
                inGeoms = [inGeom]

            lines = spatialIndex.intersects(inGeom.boundingBox())

            if len(lines) > 0:  # has intersection of bounding boxes
                splittingLines = []

                engine = QgsGeometry.createGeometryEngine(inGeom.geometry())
                engine.prepareGeometry()

                for i in lines:
                    try:
                        splitGeom = splitGeoms[i]
                    except:
                        continue

                    # check if trying to self-intersect
                    if sameLayer:
                        if inFeatA.id() == i:
                            continue

                    if engine.intersects(splitGeom.geometry()):
                        splittingLines.append(splitGeom)

                if len(splittingLines) > 0:
                    for splitGeom in splittingLines:
                        splitterPList = None
                        outGeoms = []

                        split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry())
                        split_geom_engine.prepareGeometry()

                        while len(inGeoms) > 0:
                            if feedback.isCanceled():
                                break

                            inGeom = inGeoms.pop()

                            if inGeom.isNull():  # this has been encountered and created a run-time error
                                continue

                            if split_geom_engine.intersects(inGeom.geometry()):
                                inPoints = vector.extractPoints(inGeom)
                                if splitterPList is None:
                                    splitterPList = vector.extractPoints(splitGeom)

                                try:
                                    result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False)
                                except:
                                    feedback.reportError(self.tr('Geometry exception while splitting'))
                                    result = 1

                                # splitGeometry: If there are several intersections
                                # between geometry and splitLine, only the first one is considered.
                                if result == 0:  # split occurred
                                    if inPoints == vector.extractPoints(inGeom):
                                        # bug in splitGeometry: sometimes it returns 0 but
                                        # the geometry is unchanged
                                        outGeoms.append(inGeom)
                                    else:
                                        inGeoms.append(inGeom)

                                        for aNewGeom in newGeometries:
                                            inGeoms.append(aNewGeom)
                                else:
                                    outGeoms.append(inGeom)
                            else:
                                outGeoms.append(inGeom)

                        inGeoms = outGeoms

            parts = []

            for aGeom in inGeoms:
                if feedback.isCanceled():
                    break

                passed = True

                if QgsWkbTypes.geometryType(aGeom.wkbType()) == QgsWkbTypes.LineGeometry:
                    numPoints = aGeom.geometry().numPoints()

                    if numPoints <= 2:
                        if numPoints == 2:
                            passed = not aGeom.geometry().isClosed()  # tests if vertex 0 = vertex 1
                        else:
                            passed = False
                            # sometimes splitting results in lines of zero length

                if passed:
                    parts.append(aGeom)

            if len(parts) > 0:
                outFeat.setGeometry(QgsGeometry.collectGeometry(parts))
                sink.addFeature(outFeat, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))
        return {self.OUTPUT: dest_id}
예제 #36
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.
        noeuds = self.parameterAsSource(parameters, self.NODES, context)
        num = self.parameterAsFields(parameters, self.NODE_ID, context)[0]
        mode = self.parameterAsString(parameters, self.MODE, context)
        rayon = self.parameterAsDouble(parameters, self.RAYON, context)
        vitesse = self.parameterAsDouble(parameters, self.VITESSE, 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]))

        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('longueur', QVariant.Double))
        champs.append(QgsField('temps', QVariant.Double))
        champs.append(QgsField('mode', QVariant.String, len=10))
        f = {feature.id(): feature for (feature) in noeuds.getFeatures()}

        (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")
        nb = len(f)
        nbc = 0
        for i, n in enumerate(noeuds.getFeatures()):
            near = index.intersects(
                QgsRectangle(n.geometry().buffer(rayon, 12).boundingBox()))
            feedback.setProgress(i * 100 / nb)
            if len(near) > 0:
                for fid in near:
                    g = f[fid]
                    if not (n[num] == g[num]):
                        l = n.geometry().distance(g.geometry())
                        id_node = unicode(g.attribute(num))
                        id_stop = unicode(n.attribute(num))
                        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()
                            mode = unicode(mode)
                            if vitesse <= 0:
                                fline.setAttributes(
                                    [id_stop, id_node, ll / 1000, 0, mode])
                            else:
                                fline.setAttributes([
                                    id_stop, id_node, ll / 1000,
                                    ll * 60 / (vitesse * 1000), mode
                                ])
                            table_connecteurs.addFeature(fline)
                            if vitesse > 0:
                                sortie.write(id_node + ';' + id_stop + ';' +
                                             str((60 / vitesse) *
                                                 (ll / 1000.0)) + ';' +
                                             str(ll / 1000.0) +
                                             ';-1;-1;-1;-1;-1;' + mode + ';' +
                                             mode + '\n')
                                sortie.write(id_stop + ';' + id_node + ';' +
                                             str((60 / vitesse) *
                                                 (ll / 1000.0)) + ';' +
                                             str(ll / 1000.0) +
                                             ';-1;-1;-1;-1;-1;' + mode + ';' +
                                             mode + '\n')
                            else:
                                sortie.write(id_node + ';' + id_stop + ';' +
                                             str(0.0) + ';' +
                                             str(ll / 1000.0) +
                                             ';-1;-1;-1;-1;-1;' + mode + ';' +
                                             mode + '\n')
                                sortie.write(id_stop + ';' + id_node + ';' +
                                             str(0.0) + ';' +
                                             str(ll / 1000.0) +
                                             ';-1;-1;-1;-1;-1;' + mode + ';' +
                                             mode + '\n')

        feedback.setProgressText(
            unicode(nbc) + "/" + unicode(nb) + self.tr(" connected nodes"))
        sortie.close()
        return {self.OUTPUT: dest_id}
def middle(bar,buildings_layer_path,receiver_points_layer_path):
    
    buildings_layer_name = os.path.splitext(os.path.basename(buildings_layer_path))[0]
    buildings_layer = QgsVectorLayer(buildings_layer_path,buildings_layer_name,"ogr")
  
    # defines emission_points layer
    receiver_points_fields = QgsFields()
    receiver_points_fields.append(QgsField("id_pt", QVariant.Int))
    receiver_points_fields.append(QgsField("id_bui", QVariant.Int))

    receiver_points_writer = QgsVectorFileWriter(receiver_points_layer_path, "System",
                                                 receiver_points_fields, QgsWkbTypes.Point, buildings_layer.crs(),"ESRI Shapefile")


    # gets features from layer
    buildings_feat_all = buildings_layer.dataProvider().getFeatures()    
    
    # creates SpatialIndex
    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
    
    # defines distanze_point
    distance_point = 0.1
    
    # re-gets features from layer
    buildings_feat_all = buildings_layer.dataProvider().getFeatures()    
    buildings_feat_total = buildings_layer.dataProvider().featureCount()
    
    pt_id = 0
    buildings_feat_number = 0
    for buildings_feat in buildings_feat_all:
        
        buildings_feat_number = buildings_feat_number + 1
        barValue = buildings_feat_number/float(buildings_feat_total)*100
        bar.setValue(barValue)

        building_geom = buildings_feat.geometry()
        if building_geom.isMultipart():
            buildings_pt = building_geom.asMultiPolygon()[0]
            #building_geom.convertToSingleType()
        else:
            buildings_pt = buildings_feat.geometry().asPolygon()


        # creates the search rectangle to match the receiver point in the building and del them

        rect = QgsRectangle()
        rect.setXMinimum( buildings_feat.geometry().boundingBox().xMinimum() - distance_point )
        rect.setXMaximum( buildings_feat.geometry().boundingBox().xMaximum() + distance_point )
        rect.setYMinimum( buildings_feat.geometry().boundingBox().yMinimum() - distance_point )
        rect.setYMaximum( buildings_feat.geometry().boundingBox().yMaximum() + distance_point )
    
        buildings_selection = buildings_spIndex.intersects(rect)
        
        if len(buildings_pt) > 0:
            for i in range(0,len(buildings_pt)):
                
                buildings_pts = buildings_pt[i]
        
                ####
                # start part to delete pseudo vertex
                # this part it's different from the diffraction delete pseudo vertex part
                pts_index_to_delete_list = []
                m_delta = 0.01
  
                for ii in range(0,len(buildings_pts)-1):
                        
                    x1 = buildings_pts[ii-1][0]
                    x2 = buildings_pts[ii][0]
                    x3 = buildings_pts[ii+1][0]                    
                    y1 = buildings_pts[ii-1][1]
                    y2 = buildings_pts[ii][1]
                    y3 = buildings_pts[ii+1][1]

                    # particular cases: first point to delete! (remember that the first and the last have the same coordinates)
                    if ii == 0 and (x2 == x1 and y2 == y1):
                        x1 = buildings_pts[ii-2][0]
                        y1 = buildings_pts[ii-2][1]
                        
                    # angular coefficient to find pseudo vertex
                    if x2 - x1 != 0 and x3 - x1 != 0:
                        m1 = ( y2 - y1 ) / ( x2 - x1 )
                        m2 = ( y3 - y1 ) / ( x3 - x1 )

                        #if round(m1,2) <= round(m2,2) + m_delta and round(m1,2) >= round(m2,2) - m_delta:
                        if m1 <= m2 + m_delta and m1 >= m2 - m_delta:
                            pts_index_to_delete_list.append(ii)

                            # particular cases: first point to delete! (remember that the first and the last have the same coordinates)
                            # here we delete the last and add x3,y3 (buildings_pts[ii+1] - the new last point)
                            if ii == 0:
                                pts_index_to_delete_list.append(len(buildings_pts)-1)
                                buildings_pts.append(buildings_pts[ii+1])
                            
                # del pseudo vertex
                pts_index_to_delete_list = sorted(pts_index_to_delete_list, reverse=True)
                
                for pt_index_to_del in pts_index_to_delete_list:
                    del buildings_pts[pt_index_to_del]
                
                # end part to delete pseudo vertex

                        
                # for to generate receiver points
                for ii in range(0,len(buildings_pts)-1):
                    
                    x1 = buildings_pts[ii][0]
                    x2 = buildings_pts[ii+1][0]
                    y1 = buildings_pts[ii][1]
                    y2 = buildings_pts[ii+1][1]
                    
                    xm = ( x1 + x2 )/2
                    ym = ( y1 + y2 )/2
                
                    if y2 == y1:
                        dx = 0
                        dy = distance_point
                    elif x2 == x1:
                        dx = distance_point
                        dy = 0
                    else:
                        m = ( y2 - y1 )/ ( x2 - x1 )
                        m_p = -1/m
                        dx = sqrt((distance_point**2)/(1 + m_p**2))
                        dy = sqrt(((distance_point**2)*(m_p**2))/(1 + m_p**2))
        
                    if (x2 >= x1 and y2 >= y1) or (x2 < x1 and y2 < y1):
                        pt1 = QgsPointXY(xm + dx, ym - dy)
                        pt2 = QgsPointXY(xm - dx, ym + dy)
                    if (x2 >= x1 and y2 < y1) or (x2 < x1 and y2 >= y1):
                        pt1 = QgsPointXY(xm + dx, ym + dy)
                        pt2 = QgsPointXY(xm - dx, ym - dy)
                    
                    pt = QgsFeature()
                    
                    # pt1, check if is in a building and eventually add it
                    pt.setGeometry(QgsGeometry.fromPointXY(pt1))
                    intersect = 0
                    for buildings_id in buildings_selection:
                        if buildings_feat_all_dict[buildings_id].geometry().intersects(pt.geometry()) == 1:
                            intersect = 1
                            break 
                    
                    if intersect == 0:
                        pt.setAttributes([pt_id, buildings_feat.id()])
                        receiver_points_writer.addFeature(pt)
                        pt_id = pt_id + 1
                    
                    # pt2, check if is in a building and eventually add it
                    pt.setGeometry(QgsGeometry.fromPointXY(pt2))
                    intersect = 0
                    for buildings_id in buildings_selection:
                        if buildings_feat_all_dict[buildings_id].geometry().intersects(pt.geometry()) == 1:
                            intersect = 1
                            break 
                    
                    if intersect == 0:
                        pt.setAttributes([pt_id, buildings_feat.id()])
                        receiver_points_writer.addFeature(pt)
                        pt_id = pt_id + 1                
    
    del receiver_points_writer
    #print receiver_points_layer_path
    receiver_points_layer_name = os.path.splitext(os.path.basename(receiver_points_layer_path))[0]
    #print receiver_points_layer_name
    receiver_points_layer = QgsVectorLayer(receiver_points_layer_path, str(receiver_points_layer_name), "ogr")

    QgsProject.instance().addMapLayers([receiver_points_layer])
예제 #38
0
    def processAlgorithm(self, parameters, context, feedback):
        line_source = self.parameterAsSource(parameters, self.LINES, context)
        if line_source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.LINES))

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

        length_field_name = self.parameterAsString(parameters, self.LEN_FIELD, context)
        count_field_name = self.parameterAsString(parameters, self.COUNT_FIELD, context)

        fields = poly_source.fields()
        if fields.lookupField(length_field_name) < 0:
            fields.append(QgsField(length_field_name, QVariant.Double))
        length_field_index = fields.lookupField(length_field_name)
        if fields.lookupField(count_field_name) < 0:
            fields.append(QgsField(count_field_name, QVariant.Int))
        count_field_index = fields.lookupField(count_field_name)

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

        spatialIndex = QgsSpatialIndex(line_source.getFeatures(
            QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())), feedback)

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

        features = poly_source.getFeatures()
        total = 100.0 / poly_source.featureCount() if poly_source.featureCount() else 0
        for current, poly_feature in enumerate(features):
            if feedback.isCanceled():
                break

            output_feature = QgsFeature()
            count = 0
            length = 0
            if poly_feature.hasGeometry():
                poly_geom = poly_feature.geometry()
                has_intersections = False
                lines = spatialIndex.intersects(poly_geom.boundingBox())
                engine = None
                if len(lines) > 0:
                    has_intersections = True
                    # use prepared geometries for faster intersection tests
                    engine = QgsGeometry.createGeometryEngine(poly_geom.constGet())
                    engine.prepareGeometry()

                if has_intersections:
                    request = QgsFeatureRequest().setFilterFids(lines).setSubsetOfAttributes([]).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())
                    for line_feature in line_source.getFeatures(request):
                        if feedback.isCanceled():
                            break

                        if engine.intersects(line_feature.geometry().constGet()):
                            outGeom = poly_geom.intersection(line_feature.geometry())
                            length += distArea.measureLength(outGeom)
                            count += 1

                output_feature.setGeometry(poly_geom)

            attrs = poly_feature.attributes()
            if length_field_index == len(attrs):
                attrs.append(length)
            else:
                attrs[length_field_index] = length
            if count_field_index == len(attrs):
                attrs.append(count)
            else:
                attrs[count_field_index] = count
            output_feature.setAttributes(attrs)
            sink.addFeature(output_feature, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
예제 #39
0
    def processAlgorithm(self, progress):
        layerA = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_A))
        splitLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_B))

        sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B)
        fieldList = layerA.fields()

        writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList,
                                                                     layerA.wkbType(), layerA.crs())

        spatialIndex = QgsSpatialIndex()
        splitGeoms = {}
        request = QgsFeatureRequest()
        request.setSubsetOfAttributes([])

        for aSplitFeature in vector.features(splitLayer, request):
            splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry()
            spatialIndex.insertFeature(aSplitFeature)
            # honor the case that user has selection on split layer and has setting "use selection"

        outFeat = QgsFeature()
        features = vector.features(layerA)

        if len(features) == 0:
            total = 100
        else:
            total = 100.0 / float(len(features))

        multiGeoms = 0 # how many multi geometries were encountered

        for current, inFeatA in enumerate(features):
            inGeom = inFeatA.geometry()

            if inGeom.isMultipart():
                multiGeoms += 1
                # MultiGeometries are not allowed because the result of a splitted part cannot be clearly defined:
                # 1) add both new parts as new features
                # 2) store one part as a new feature and the other one as part of the multi geometry
                # 2a) which part should be which, seems arbitrary
            else:
                attrsA = inFeatA.attributes()
                outFeat.setAttributes(attrsA)
                inGeoms = [inGeom]
                lines = spatialIndex.intersects(inGeom.boundingBox())

                if len(lines) > 0:  # has intersection of bounding boxes
                    splittingLines = []

                    engine = QgsGeometry.createGeometryEngine(inGeom.geometry())
                    engine.prepareGeometry()

                    for i in lines:
                        try:
                            splitGeom = splitGeoms[i]
                        except:
                            continue

                        # check if trying to self-intersect
                        if sameLayer:
                            if inFeatA.id() == i:
                                continue

                        if engine.intersects(splitGeom.geometry()):
                            splittingLines.append(splitGeom)

                    if len(splittingLines) > 0:
                        for splitGeom in splittingLines:
                            splitterPList = None
                            outGeoms = []

                            split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry())
                            split_geom_engine.prepareGeometry()

                            while len(inGeoms) > 0:
                                inGeom = inGeoms.pop()

                                if split_geom_engine.intersects(inGeom.geometry()):
                                    inPoints = vector.extractPoints(inGeom)
                                    if splitterPList == None:
                                        splitterPList = vector.extractPoints(splitGeom)

                                    try:
                                        result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False)
                                    except:
                                        ProcessingLog.addToLog(ProcessingLog.LOG_WARNING,
                                                               self.tr('Geometry exception while splitting'))
                                        result = 1

                                    # splitGeometry: If there are several intersections
                                    # between geometry and splitLine, only the first one is considered.
                                    if result == 0:  # split occurred
                                        if inPoints == vector.extractPoints(inGeom):
                                            # bug in splitGeometry: sometimes it returns 0 but
                                            # the geometry is unchanged
                                            QgsMessageLog.logMessage("appending")
                                            outGeoms.append(inGeom)
                                        else:
                                            inGeoms.append(inGeom)

                                            for aNewGeom in newGeometries:
                                                inGeoms.append(aNewGeom)
                                    else:
                                        QgsMessageLog.logMessage("appending else")
                                        outGeoms.append(inGeom)
                                else:
                                    outGeoms.append(inGeom)

                            inGeoms = outGeoms

                for aGeom in inGeoms:
                    passed = True

                    if QgsWkbTypes.geometryType( aGeom.wkbType() )  == QgsWkbTypes.LineGeometry \
                            and not QgsWkbTypes.isMultiType(aGeom.wkbType()):
                        passed = len(aGeom.asPolyline()) > 2

                        if not passed:
                            passed = (len(aGeom.asPolyline()) == 2 and
                                      aGeom.asPolyline()[0] != aGeom.asPolyline()[1])
                            # sometimes splitting results in lines of zero length

                    if passed:
                        outFeat.setGeometry(aGeom)
                        writer.addFeature(outFeat)

            progress.setPercentage(int(current * total))

        if multiGeoms > 0:
            ProcessingLog.addToLog(ProcessingLog.LOG_INFO,
                                   self.tr('Feature geometry error: %s input features ignored due to multi-geometry.') % str(multiGeoms))

        del writer
예제 #40
0
    def processAlgorithm(self, parameters, context, feedback):
        sourceA = self.parameterAsSource(parameters, self.INPUT, context)
        sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)

        geomType = QgsWkbTypes.multiType(sourceA.wkbType())

        fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS,
                                         context)
        fieldsB = self.parameterAsFields(parameters, self.OVERLAY_FIELDS,
                                         context)

        fieldListA = QgsFields()
        field_indices_a = []
        if len(fieldsA) > 0:
            for f in fieldsA:
                idxA = sourceA.fields().lookupField(f)
                if idxA >= 0:
                    field_indices_a.append(idxA)
                    fieldListA.append(sourceA.fields()[idxA])
        else:
            fieldListA = sourceA.fields()
            field_indices_a = [i for i in range(0, fieldListA.count())]

        fieldListB = QgsFields()
        field_indices_b = []
        if len(fieldsB) > 0:
            for f in fieldsB:
                idxB = sourceB.fields().lookupField(f)
                if idxB >= 0:
                    field_indices_b.append(idxB)
                    fieldListB.append(sourceB.fields()[idxB])
        else:
            fieldListB = sourceB.fields()
            field_indices_b = [i for i in range(0, fieldListB.count())]

        output_fields = QgsProcessingUtils.combineFields(
            fieldListA, fieldListB)

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, output_fields,
                                               geomType, sourceA.sourceCrs())

        outFeat = QgsFeature()
        indexB = QgsSpatialIndex(
            sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(
                []).setDestinationCrs(sourceA.sourceCrs(),
                                      context.transformContext())), feedback)

        total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 1
        count = 0

        for featA in sourceA.getFeatures(
                QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)):
            if feedback.isCanceled():
                break

            if not featA.hasGeometry():
                continue

            geom = featA.geometry()
            atMapA = featA.attributes()
            intersects = indexB.intersects(geom.boundingBox())

            request = QgsFeatureRequest().setFilterFids(intersects)
            request.setDestinationCrs(sourceA.sourceCrs(),
                                      context.transformContext())
            request.setSubsetOfAttributes(field_indices_b)

            engine = None
            if len(intersects) > 0:
                # use prepared geometries for faster intersection tests
                engine = QgsGeometry.createGeometryEngine(geom.constGet())
                engine.prepareGeometry()

            for featB in sourceB.getFeatures(request):
                if feedback.isCanceled():
                    break

                tmpGeom = featB.geometry()
                if engine.intersects(tmpGeom.constGet()):
                    out_attributes = [
                        featA.attributes()[i] for i in field_indices_a
                    ]
                    out_attributes.extend(
                        [featB.attributes()[i] for i in field_indices_b])
                    int_geom = QgsGeometry(geom.intersection(tmpGeom))
                    if int_geom.wkbType(
                    ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(
                            int_geom.wkbType(
                            )) == QgsWkbTypes.GeometryCollection:
                        int_com = geom.combine(tmpGeom)
                        int_geom = QgsGeometry()
                        if int_com:
                            int_sym = geom.symDifference(tmpGeom)
                            int_geom = QgsGeometry(int_com.difference(int_sym))
                    if int_geom.isEmpty() or not int_geom.isGeosValid():
                        raise QgsProcessingException(
                            self.tr('GEOS geoprocessing error: One or '
                                    'more input features have invalid '
                                    'geometry.'))
                    try:
                        if QgsWkbTypes.geometryType(int_geom.wkbType(
                        )) == QgsWkbTypes.geometryType(geomType):
                            int_geom.convertToMultiType()
                            outFeat.setGeometry(int_geom)
                            outFeat.setAttributes(out_attributes)
                            sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                    except:
                        raise QgsProcessingException(
                            self.tr('Feature geometry error: One or more '
                                    'output features ignored due to invalid '
                                    'geometry.'))

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

        return {self.OUTPUT: dest_id}
예제 #41
0
    def run_stats(self):
        self.progressBar_stats.setValue(0)
        self.label_progressStats.setText('')
        # noinspection PyArgumentList
        QApplication.processEvents()

        blurred_layer = self.comboBox_blurredLayer.currentLayer()
        stats_layer = self.comboBox_statsLayer.currentLayer()

        try:

            if not blurred_layer or not stats_layer:
                raise NoLayerProvidedException

            crs_blurred_layer = blurred_layer.crs()
            crs_stats_layer = stats_layer.crs()

            if crs_blurred_layer != crs_stats_layer:
                raise DifferentCrsException(
                    epsg1=crs_blurred_layer.authid(),
                    epsg2=crs_stats_layer.authid())

            if blurred_layer == stats_layer:
                raise NoLayerProvidedException

            if not blurred_layer or not stats_layer:
                raise NoLayerProvidedException

            nb_feature_stats = stats_layer.featureCount()
            nb_feature_blurred = blurred_layer.featureCount()
            features_stats = {}

            label_preparing = tr('Preparing index on the stats layer')
            label_creating = tr('Creating index on the stats layer')
            label_calculating = tr('Calculating')

            if QGis.QGIS_VERSION_INT < 20700:
                self.label_progressStats.setText('%s 1/3' % label_preparing)

                for i, feature in enumerate(stats_layer.getFeatures()):
                    features_stats[feature.id()] = feature
                    percent = int((i + 1) * 100 / nb_feature_stats)
                    self.progressBar_stats.setValue(percent)
                    # noinspection PyArgumentList
                    QApplication.processEvents()

                self.label_progressStats.setText('%s 2/3' % label_creating)
                # noinspection PyArgumentList
                QApplication.processEvents()
                index = QgsSpatialIndex()
                for i, f in enumerate(stats_layer.getFeatures()):
                    index.insertFeature(f)

                    percent = int((i + 1) * 100 / nb_feature_stats)
                    self.progressBar_stats.setValue(percent)
                    # noinspection PyArgumentList
                    QApplication.processEvents()

                self.label_progressStats.setText('%s 3/3' % label_calculating)

            else:
                # If QGIS >= 2.7, we can speed up the spatial index.
                # From 1 min 15 to 7 seconds on my PC.
                self.label_progressStats.setText('%s 1/2' % label_creating)
                # noinspection PyArgumentList
                QApplication.processEvents()
                index = QgsSpatialIndex(stats_layer.getFeatures())
                self.label_progressStats.setText('%s 2/2' % label_calculating)

            # noinspection PyArgumentList
            QApplication.processEvents()
            self.tab = []
            for i, feature in enumerate(blurred_layer.getFeatures()):
                count = 0
                ids = index.intersects(feature.geometry().boundingBox())
                for unique_id in ids:
                    request = QgsFeatureRequest().setFilterFid(unique_id)
                    f = stats_layer.getFeatures(request).next()

                    if f.geometry().intersects(feature.geometry()):
                        count += 1
                self.tab.append(count)

                percent = int((i + 1) * 100 / nb_feature_blurred)
                self.progressBar_stats.setValue(percent)
                # noinspection PyArgumentList
                QApplication.processEvents()

            stats = Stats(self.tab)

            items_stats = [
                'Count(blurred),%d' % nb_feature_blurred,
                'Count(stats),%d' % nb_feature_stats,
                'Min,%d' % stats.min(),
                'Average,%f' % stats.average(),
                'Max,%d' % stats.max(), 'Median,%f' % stats.median(),
                'Range,%d' % stats.range(),
                'Variance,%f' % stats.variance(),
                'Standard deviation,%f' % stats.standard_deviation()
            ]

            self.tableWidget.clear()
            self.tableWidget.setColumnCount(2)
            labels = ['Parameters', 'Values']
            self.tableWidget.setHorizontalHeaderLabels(labels)
            self.tableWidget.setRowCount(len(items_stats))

            for i, item in enumerate(items_stats):
                s = item.split(',')
                self.tableWidget.setItem(i, 0, QTableWidgetItem(s[0]))
                self.tableWidget.setItem(i, 1, QTableWidgetItem(s[1]))
            self.tableWidget.resizeRowsToContents()

            self.draw_plot(self.tab)

        except GeoHealthException, e:
            self.label_progressStats.setText('')
            display_message_bar(msg=e.msg, level=e.level, duration=e.duration)
예제 #42
0
    def processAlgorithm(self, parameters, context, feedback):
        poly_source = self.parameterAsSource(parameters, self.POLYGONS,
                                             context)
        if poly_source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.POLYGONS))

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

        weight_field = self.parameterAsString(parameters, self.WEIGHT, context)
        weight_field_index = -1
        if weight_field:
            weight_field_index = point_source.fields().lookupField(
                weight_field)

        class_field = self.parameterAsString(parameters, self.CLASSFIELD,
                                             context)
        class_field_index = -1
        if class_field:
            class_field_index = point_source.fields().lookupField(class_field)

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

        fields = poly_source.fields()
        if fields.lookupField(field_name) < 0:
            fields.append(QgsField(field_name, QVariant.Int))
        field_index = fields.lookupField(field_name)

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

        spatialIndex = QgsSpatialIndex(
            point_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(
                []).setDestinationCrs(poly_source.sourceCrs(),
                                      context.transformContext())), feedback)

        point_attribute_indices = []
        if weight_field_index >= 0:
            point_attribute_indices.append(weight_field_index)
        if class_field_index >= 0:
            point_attribute_indices.append(class_field_index)

        features = poly_source.getFeatures()
        total = 100.0 / poly_source.featureCount() if poly_source.featureCount(
        ) else 0
        for current, polygon_feature in enumerate(features):
            if feedback.isCanceled():
                break

            count = 0
            output_feature = QgsFeature()
            if polygon_feature.hasGeometry():
                geom = polygon_feature.geometry()
                engine = QgsGeometry.createGeometryEngine(geom.constGet())
                engine.prepareGeometry()

                count = 0
                classes = set()

                points = spatialIndex.intersects(geom.boundingBox())
                if len(points) > 0:
                    request = QgsFeatureRequest().setFilterFids(
                        points).setDestinationCrs(poly_source.sourceCrs(),
                                                  context.transformContext())
                    request.setSubsetOfAttributes(point_attribute_indices)
                    for point_feature in point_source.getFeatures(request):
                        if feedback.isCanceled():
                            break

                        if engine.contains(
                                point_feature.geometry().constGet()):
                            if weight_field_index >= 0:
                                weight = point_feature.attributes(
                                )[weight_field_index]
                                try:
                                    count += float(weight)
                                except:
                                    # Ignore fields with non-numeric values
                                    pass
                            elif class_field_index >= 0:
                                point_class = point_feature.attributes(
                                )[class_field_index]
                                if point_class not in classes:
                                    classes.add(point_class)
                            else:
                                count += 1

                output_feature.setGeometry(geom)

            attrs = polygon_feature.attributes()

            if class_field_index >= 0:
                score = len(classes)
            else:
                score = count
            if field_index == len(attrs):
                attrs.append(score)
            else:
                attrs[field_index] = score
            output_feature.setAttributes(attrs)
            sink.addFeature(output_feature, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
예제 #43
0
    def processAlgorithm(self, parameters, context, feedback):
        line_source = self.parameterAsSource(parameters, self.LINES, context)
        if line_source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.LINES))

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

        length_field_name = self.parameterAsString(parameters, self.LEN_FIELD, context)
        count_field_name = self.parameterAsString(parameters, self.COUNT_FIELD, context)

        fields = poly_source.fields()
        if fields.lookupField(length_field_name) < 0:
            fields.append(QgsField(length_field_name, QVariant.Double))
        length_field_index = fields.lookupField(length_field_name)
        if fields.lookupField(count_field_name) < 0:
            fields.append(QgsField(count_field_name, QVariant.Int))
        count_field_index = fields.lookupField(count_field_name)

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

        spatialIndex = QgsSpatialIndex(line_source.getFeatures(
            QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())), feedback)

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

        features = poly_source.getFeatures()
        total = 100.0 / poly_source.featureCount() if poly_source.featureCount() else 0
        for current, poly_feature in enumerate(features):
            if feedback.isCanceled():
                break

            output_feature = QgsFeature()
            count = 0
            length = 0
            if poly_feature.hasGeometry():
                poly_geom = poly_feature.geometry()
                has_intersections = False
                lines = spatialIndex.intersects(poly_geom.boundingBox())
                engine = None
                if len(lines) > 0:
                    has_intersections = True
                    # use prepared geometries for faster intersection tests
                    engine = QgsGeometry.createGeometryEngine(poly_geom.constGet())
                    engine.prepareGeometry()

                if has_intersections:
                    request = QgsFeatureRequest().setFilterFids(lines).setSubsetOfAttributes([]).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())
                    for line_feature in line_source.getFeatures(request):
                        if feedback.isCanceled():
                            break

                        if engine.intersects(line_feature.geometry().constGet()):
                            outGeom = poly_geom.intersection(line_feature.geometry())
                            length += distArea.measureLength(outGeom)
                            count += 1

                output_feature.setGeometry(poly_geom)

            attrs = poly_feature.attributes()
            if length_field_index == len(attrs):
                attrs.append(length)
            else:
                attrs[length_field_index] = length
            if count_field_index == len(attrs):
                attrs.append(count)
            else:
                attrs[count_field_index] = count
            output_feature.setAttributes(attrs)
            sink.addFeature(output_feature, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
예제 #44
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 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)
예제 #45
0
    def processAlgorithm(self, parameters, context, feedback):
        sourceA = self.parameterAsSource(parameters, self.INPUT, context)
        sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)

        geomType = QgsWkbTypes.multiType(sourceA.wkbType())

        fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context)
        fieldsB = self.parameterAsFields(parameters, self.OVERLAY_FIELDS, context)

        fieldListA = QgsFields()
        field_indices_a = []
        if len(fieldsA) > 0:
            for f in fieldsA:
                idxA = sourceA.fields().lookupField(f)
                if idxA >= 0:
                    field_indices_a.append(idxA)
                    fieldListA.append(sourceA.fields()[idxA])
        else:
            fieldListA = sourceA.fields()
            field_indices_a = [i for i in range(0, fieldListA.count())]

        fieldListB = QgsFields()
        field_indices_b = []
        if len(fieldsB) > 0:
            for f in fieldsB:
                idxB = sourceB.fields().lookupField(f)
                if idxB >= 0:
                    field_indices_b.append(idxB)
                    fieldListB.append(sourceB.fields()[idxB])
        else:
            fieldListB = sourceB.fields()
            field_indices_b = [i for i in range(0, fieldListB.count())]

        fieldListB = vector.testForUniqueness(fieldListA, fieldListB)
        for b in fieldListB:
            fieldListA.append(b)

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fieldListA, geomType, sourceA.sourceCrs())

        outFeat = QgsFeature()
        indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback)

        total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 1
        count = 0

        for featA in sourceA.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)):
            if feedback.isCanceled():
                break

            if not featA.hasGeometry():
                continue

            geom = featA.geometry()
            atMapA = featA.attributes()
            intersects = indexB.intersects(geom.boundingBox())

            request = QgsFeatureRequest().setFilterFids(intersects)
            request.setDestinationCrs(sourceA.sourceCrs())
            request.setSubsetOfAttributes(field_indices_b)

            engine = None
            if len(intersects) > 0:
                # use prepared geometries for faster intersection tests
                engine = QgsGeometry.createGeometryEngine(geom.geometry())
                engine.prepareGeometry()

            for featB in sourceB.getFeatures(request):
                if feedback.isCanceled():
                    break

                tmpGeom = featB.geometry()
                if engine.intersects(tmpGeom.geometry()):
                    out_attributes = [featA.attributes()[i] for i in field_indices_a]
                    out_attributes.extend([featB.attributes()[i] for i in field_indices_b])
                    int_geom = QgsGeometry(geom.intersection(tmpGeom))
                    if int_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(int_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection:
                        int_com = geom.combine(tmpGeom)
                        int_geom = QgsGeometry()
                        if int_com:
                            int_sym = geom.symDifference(tmpGeom)
                            int_geom = QgsGeometry(int_com.difference(int_sym))
                    if int_geom.isEmpty() or not int_geom.isGeosValid():
                        raise QgsProcessingException(
                            self.tr('GEOS geoprocessing error: One or '
                                    'more input features have invalid '
                                    'geometry.'))
                    try:
                        if int_geom.wkbType() in wkbTypeGroups[wkbTypeGroups[int_geom.wkbType()]]:
                            outFeat.setGeometry(int_geom)
                            outFeat.setAttributes(out_attributes)
                            sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                    except:
                        raise QgsProcessingException(
                            self.tr('Feature geometry error: One or more '
                                    'output features ignored due to invalid '
                                    'geometry.'))

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

        return {self.OUTPUT: dest_id}
예제 #46
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER,
                                            context)
        minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE,
                                             context)

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

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

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

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

        index = QgsSpatialIndex()
        points = dict()

        random.seed()

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

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

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

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

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

        return {self.OUTPUT: dest_id}
예제 #47
0
    def processAlgorithm(self, parameters, context, feedback):
        sourceA = self.parameterAsSource(parameters, self.INPUT, context)
        sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)

        geomType = QgsWkbTypes.multiType(sourceA.wkbType())
        fields = QgsProcessingUtils.combineFields(sourceA.fields(), sourceB.fields())

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, geomType, sourceA.sourceCrs())

        featB = QgsFeature()
        outFeat = QgsFeature()

        indexA = QgsSpatialIndex(sourceA, feedback)
        indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback)

        total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1
        count = 0

        for featA in sourceA.getFeatures():
            if feedback.isCanceled():
                break

            geom = featA.geometry()
            diffGeom = QgsGeometry(geom)
            attrs = featA.attributes()
            intersects = indexB.intersects(geom.boundingBox())
            request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([])
            request.setDestinationCrs(sourceA.sourceCrs())
            for featB in sourceB.getFeatures(request):
                if feedback.isCanceled():
                    break
                tmpGeom = featB.geometry()
                if diffGeom.intersects(tmpGeom):
                    diffGeom = QgsGeometry(diffGeom.difference(tmpGeom))

            try:
                outFeat.setGeometry(diffGeom)
                outFeat.setAttributes(attrs)
                sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
            except:
                QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'),
                                         self.tr('Processing'), QgsMessageLog.WARNING)
                continue

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

        length = len(sourceA.fields())

        for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())):
            if feedback.isCanceled():
                break

            geom = featA.geometry()
            diffGeom = QgsGeometry(geom)
            attrs = featA.attributes()
            attrs = [NULL] * length + attrs
            intersects = indexA.intersects(geom.boundingBox())
            request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([])
            for featB in sourceA.getFeatures(request):
                if feedback.isCanceled():
                    break

                tmpGeom = featB.geometry()
                if diffGeom.intersects(tmpGeom):
                    diffGeom = QgsGeometry(diffGeom.difference(tmpGeom))

            try:
                outFeat.setGeometry(diffGeom)
                outFeat.setAttributes(attrs)
                sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
            except:
                QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'),
                                         self.tr('Processing'), QgsMessageLog.WARNING)
                continue

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

        return {self.OUTPUT: dest_id}
예제 #48
0
    def run(self):
        """Experimental impact function."""

        # Get parameters from layer's keywords
        self.hazard_class_attribute = self.hazard.keyword('field')
        self.hazard_class_mapping = self.hazard.keyword('value_map')
        # There is no wet in the class mapping
        if self.wet not in self.hazard_class_mapping:
            raise ZeroImpactException(tr(
                'There is no flooded area in the hazard layers, thus there '
                'is no affected building.'))
        self.exposure_class_attribute = self.exposure.keyword(
            'structure_class_field')
        exposure_value_mapping = self.exposure.keyword('value_mapping')

        # Prepare Hazard Layer
        hazard_provider = self.hazard.layer.dataProvider()

        # Check affected field exists in the hazard layer
        affected_field_index = hazard_provider.fieldNameIndex(
            self.hazard_class_attribute)
        if affected_field_index == -1:
            message = tr(
                'Field "%s" is not present in the attribute table of the '
                'hazard layer. Please change the Affected Field parameter in '
                'the IF Option.') % self.hazard_class_attribute
            raise GetDataError(message)

        srs = self.exposure.layer.crs().toWkt()
        exposure_provider = self.exposure.layer.dataProvider()
        exposure_fields = exposure_provider.fields()

        # Check self.exposure_class_attribute exists in exposure layer
        building_type_field_index = exposure_provider.fieldNameIndex(
            self.exposure_class_attribute)
        if building_type_field_index == -1:
            message = tr(
                'Field "%s" is not present in the attribute table of '
                'the exposure layer. Please change the Building Type '
                'Field parameter in the IF Option.'
            ) % self.exposure_class_attribute
            raise GetDataError(message)

        # If target_field does not exist, add it:
        if exposure_fields.indexFromName(self.target_field) == -1:
            exposure_provider.addAttributes(
                [QgsField(self.target_field, QVariant.Int)])
        target_field_index = exposure_provider.fieldNameIndex(
            self.target_field)
        exposure_fields = exposure_provider.fields()

        # Create layer to store the buildings from E and extent
        buildings_are_points = is_point_layer(self.exposure.layer)
        if buildings_are_points:
            building_layer = QgsVectorLayer(
                'Point?crs=' + srs, 'impact_buildings', 'memory')
        else:
            building_layer = QgsVectorLayer(
                'Polygon?crs=' + srs, 'impact_buildings', 'memory')
        building_provider = building_layer.dataProvider()

        # Set attributes
        building_provider.addAttributes(exposure_fields.toList())
        building_layer.startEditing()
        building_layer.commitChanges()

        # Filter geometry and data using the requested extent
        requested_extent = QgsRectangle(*self.requested_extent)

        # This is a hack - we should be setting the extent CRS
        # in the IF base class via safe/engine/core.py:calculate_impact
        # for now we assume the extent is in 4326 because it
        # is set to that from geo_extent
        # See issue #1857
        transform = QgsCoordinateTransform(
            self.requested_extent_crs, self.hazard.crs())
        projected_extent = transform.transformBoundingBox(requested_extent)
        request = QgsFeatureRequest()
        request.setFilterRect(projected_extent)

        # Split building_layer by H and save as result:
        #   1) Filter from H inundated features
        #   2) Mark buildings as inundated (1) or not inundated (0)

        # make spatial index of affected polygons
        hazard_index = QgsSpatialIndex()
        hazard_geometries = {}  # key = feature id, value = geometry
        has_hazard_objects = False
        for feature in self.hazard.layer.getFeatures(request):
            value = feature[affected_field_index]
            if value not in self.hazard_class_mapping[self.wet]:
                continue
            hazard_index.insertFeature(feature)
            hazard_geometries[feature.id()] = QgsGeometry(feature.geometry())
            has_hazard_objects = True

        if not has_hazard_objects:
            message = tr(
                'There are no objects in the hazard layer with %s '
                'value in %s. Please check your data or use another '
                'attribute.') % (
                    self.hazard_class_attribute,
                    ', '.join(self.hazard_class_mapping[self.wet]))
            raise GetDataError(message)

        # Filter out just those EXPOSURE features in the analysis extents
        transform = QgsCoordinateTransform(
            self.requested_extent_crs, self.exposure.layer.crs())
        projected_extent = transform.transformBoundingBox(requested_extent)
        request = QgsFeatureRequest()
        request.setFilterRect(projected_extent)

        # We will use this transform to project each exposure feature into
        # the CRS of the Hazard.
        transform = QgsCoordinateTransform(
            self.exposure.crs(), self.hazard.crs())
        features = []
        for feature in self.exposure.layer.getFeatures(request):
            # Make a deep copy as the geometry is passed by reference
            # If we don't do this, subsequent operations will affect the
            # original feature geometry as well as the copy TS
            building_geom = QgsGeometry(feature.geometry())
            # Project the building geometry to hazard CRS
            building_bounds = transform.transform(building_geom.boundingBox())
            affected = False
            # get tentative list of intersecting hazard features
            # only based on intersection of bounding boxes
            ids = hazard_index.intersects(building_bounds)
            for fid in ids:
                # run (slow) exact intersection test
                building_geom.transform(transform)
                if hazard_geometries[fid].intersects(building_geom):
                    affected = True
                    break
            new_feature = QgsFeature()
            # We write out the original feature geom, not the projected one
            new_feature.setGeometry(feature.geometry())
            new_feature.setAttributes(feature.attributes())
            new_feature[target_field_index] = 1 if affected else 0
            features.append(new_feature)

            # every once in a while commit the created features
            # to the output layer
            if len(features) == 1000:
                (_, __) = building_provider.addFeatures(features)
                features = []

        (_, __) = building_provider.addFeatures(features)
        building_layer.updateExtents()

        # Generate simple impact report
        hazard_classes = [tr('Flooded')]
        self.init_report_var(hazard_classes)

        buildings_data = building_layer.getFeatures()
        building_type_field_index = building_layer.fieldNameIndex(
            self.exposure_class_attribute)
        for building in buildings_data:
            record = building.attributes()

            usage = record[building_type_field_index]
            usage = main_type(usage, exposure_value_mapping)

            affected = False
            if record[target_field_index] == 1:
                affected = True

            self.classify_feature(hazard_classes[0], usage, affected)

        self.reorder_dictionaries()

        style_classes = [
            dict(label=tr('Not Inundated'), value=0, colour='#1EFC7C',
                 transparency=0, size=0.5),
            dict(label=tr('Inundated'), value=1, colour='#F31A1C',
                 transparency=0, size=0.5)]
        style_info = dict(
            target_field=self.target_field,
            style_classes=style_classes,
            style_type='categorizedSymbol')

        # Convert QgsVectorLayer to inasafe layer and return it.
        if building_layer.featureCount() < 1:
            raise ZeroImpactException(tr(
                'No buildings were impacted by this flood.'))

        impact_data = self.generate_data()

        extra_keywords = {
            'map_title': self.map_title(),
            'legend_title': self.metadata().key('legend_title'),
            'target_field': self.target_field,
            'buildings_total': self.total_buildings,
            'buildings_affected': self.total_affected_buildings
        }

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        impact_layer = Vector(
            data=building_layer,
            name=self.map_title(),
            keywords=impact_layer_keywords,
            style_info=style_info)

        impact_layer.impact_data = impact_data
        self._impact = impact_layer
        return impact_layer
예제 #49
0
class TriangleMesh:

  # 0 - 3
  # | / |
  # 1 - 2

  def __init__(self, xmin, ymin, xmax, ymax, x_segments, y_segments):
    self.vbands = []
    self.hbands = []
    self.vidx = QgsSpatialIndex()
    self.hidx = QgsSpatialIndex()

    xres = (xmax - xmin) / x_segments
    yres = (ymax - ymin) / y_segments
    self.xmin, self.ymax, self.xres, self.yres = xmin, ymax, xres, yres

    def addVBand(idx, geom):
      f = QgsFeature(idx)
      f.setGeometry(geom)
      self.vbands.append(f)
      self.vidx.insertFeature(f)

    def addHBand(idx, geom):
      f = QgsFeature(idx)
      f.setGeometry(geom)
      self.hbands.append(f)
      self.hidx.insertFeature(f)

    for x in range(x_segments):
      addVBand(x, QgsGeometry.fromRect(QgsRectangle(xmin + x * xres, ymin, xmin + (x + 1) * xres, ymax)))

    for y in range(y_segments):
      addHBand(y, QgsGeometry.fromRect(QgsRectangle(xmin, ymax - (y + 1) * yres, xmax, ymax - y * yres)))

  def vSplit(self, geom):
    """split polygon vertically"""
    for idx in self.vidx.intersects(geom.boundingBox()):
      yield idx, geom.intersection(self.vbands[idx].geometry())

  def hIntersects(self, geom):
    """indices of horizontal bands that intersect with geom"""
    for idx in self.hidx.intersects(geom.boundingBox()):
      if geom.intersects(self.hbands[idx].geometry()):
        yield idx

  def splitPolygons(self, geom):
    xmin, ymax, xres, yres = self.xmin, self.ymax, self.xres, self.yres

    for x, vi in self.vSplit(geom):
      for y in self.hIntersects(vi):
        pt0 = QgsPoint(xmin + x * xres, ymax - y * yres)
        pt1 = QgsPoint(xmin + x * xres, ymax - (y + 1) * yres)
        pt2 = QgsPoint(xmin + (x + 1) * xres, ymax - (y + 1) * yres)
        pt3 = QgsPoint(xmin + (x + 1) * xres, ymax - y * yres)
        quad = QgsGeometry.fromPolygon([[pt0, pt1, pt2, pt3, pt0]])
        tris = [[[pt0, pt1, pt3, pt0]], [[pt3, pt1, pt2, pt3]]]

        if geom.contains(quad):
          yield tris[0]
          yield tris[1]
        else:
          for i, tri in enumerate(map(QgsGeometry.fromPolygon, tris)):
            if geom.contains(tri):
              yield tris[i]
            elif geom.intersects(tri):
              poly = geom.intersection(tri)
              if poly.isMultipart():
                for sp in poly.asMultiPolygon():
                  yield sp
              else:
                yield poly.asPolygon()
예제 #50
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        line_source = self.parameterAsSource(parameters, self.LINES, context)

        sameLayer = parameters[self.INPUT] == parameters[self.LINES]

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               source.fields(), QgsWkbTypes.multiType(source.wkbType()), source.sourceCrs())

        spatialIndex = QgsSpatialIndex()
        splitGeoms = {}
        request = QgsFeatureRequest()
        request.setSubsetOfAttributes([])
        request.setDestinationCrs(source.sourceCrs())

        for aSplitFeature in line_source.getFeatures(request):
            if feedback.isCanceled():
                break

            splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry()
            spatialIndex.insertFeature(aSplitFeature)
            # honor the case that user has selection on split layer and has setting "use selection"

        outFeat = QgsFeature()
        features = source.getFeatures()

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

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

            inGeom = inFeatA.geometry()
            attrsA = inFeatA.attributes()
            outFeat.setAttributes(attrsA)

            if inGeom.isMultipart():
                inGeoms = []

                for g in inGeom.asGeometryCollection():
                    inGeoms.append(g)
            else:
                inGeoms = [inGeom]

            lines = spatialIndex.intersects(inGeom.boundingBox())

            if len(lines) > 0:  # has intersection of bounding boxes
                splittingLines = []

                engine = QgsGeometry.createGeometryEngine(inGeom.geometry())
                engine.prepareGeometry()

                for i in lines:
                    try:
                        splitGeom = splitGeoms[i]
                    except:
                        continue

                    # check if trying to self-intersect
                    if sameLayer:
                        if inFeatA.id() == i:
                            continue

                    if engine.intersects(splitGeom.geometry()):
                        splittingLines.append(splitGeom)

                if len(splittingLines) > 0:
                    for splitGeom in splittingLines:
                        splitterPList = None
                        outGeoms = []

                        split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry())
                        split_geom_engine.prepareGeometry()

                        while len(inGeoms) > 0:
                            if feedback.isCanceled():
                                break

                            inGeom = inGeoms.pop()

                            if inGeom.isNull():  # this has been encountered and created a run-time error
                                continue

                            if split_geom_engine.intersects(inGeom.geometry()):
                                inPoints = vector.extractPoints(inGeom)
                                if splitterPList is None:
                                    splitterPList = vector.extractPoints(splitGeom)

                                try:
                                    result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False)
                                except:
                                    feedback.reportError(self.tr('Geometry exception while splitting'))
                                    result = 1

                                # splitGeometry: If there are several intersections
                                # between geometry and splitLine, only the first one is considered.
                                if result == 0:  # split occurred
                                    if inPoints == vector.extractPoints(inGeom):
                                        # bug in splitGeometry: sometimes it returns 0 but
                                        # the geometry is unchanged
                                        outGeoms.append(inGeom)
                                    else:
                                        inGeoms.append(inGeom)

                                        for aNewGeom in newGeometries:
                                            inGeoms.append(aNewGeom)
                                else:
                                    outGeoms.append(inGeom)
                            else:
                                outGeoms.append(inGeom)

                        inGeoms = outGeoms

            parts = []

            for aGeom in inGeoms:
                if feedback.isCanceled():
                    break

                passed = True

                if QgsWkbTypes.geometryType(aGeom.wkbType()) == QgsWkbTypes.LineGeometry:
                    numPoints = aGeom.geometry().numPoints()

                    if numPoints <= 2:
                        if numPoints == 2:
                            passed = not aGeom.geometry().isClosed()  # tests if vertex 0 = vertex 1
                        else:
                            passed = False
                            # sometimes splitting results in lines of zero length

                if passed:
                    parts.append(aGeom)

            if len(parts) > 0:
                outFeat.setGeometry(QgsGeometry.collectGeometry(parts))
                sink.addFeature(outFeat, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))
        return {self.OUTPUT: dest_id}
예제 #51
0
    def processAlgorithm(self, parameters, context, feedback):
        poly_source = self.parameterAsSource(parameters, self.POLYGONS, context)
        point_source = self.parameterAsSource(parameters, self.POINTS, context)

        weight_field = self.parameterAsString(parameters, self.WEIGHT, context)
        weight_field_index = -1
        if weight_field:
            weight_field_index = point_source.fields().lookupField(weight_field)

        class_field = self.parameterAsString(parameters, self.CLASSFIELD, context)
        class_field_index = -1
        if class_field:
            class_field_index = point_source.fields().lookupField(class_field)

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

        fields = poly_source.fields()
        if fields.lookupField(field_name) < 0:
            fields.append(QgsField(field_name, QVariant.Int))
        field_index = fields.lookupField(field_name)

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

        spatialIndex = QgsSpatialIndex(point_source.getFeatures(
            QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())), feedback)

        point_attribute_indices = []
        if weight_field_index >= 0:
            point_attribute_indices.append(weight_field_index)
        if class_field_index >= 0:
            point_attribute_indices.append(class_field_index)

        features = poly_source.getFeatures()
        total = 100.0 / poly_source.featureCount() if poly_source.featureCount() else 0
        for current, polygon_feature in enumerate(features):
            if feedback.isCanceled():
                break

            count = 0
            output_feature = QgsFeature()
            if polygon_feature.hasGeometry():
                geom = polygon_feature.geometry()
                engine = QgsGeometry.createGeometryEngine(geom.constGet())
                engine.prepareGeometry()

                count = 0
                classes = set()

                points = spatialIndex.intersects(geom.boundingBox())
                if len(points) > 0:
                    request = QgsFeatureRequest().setFilterFids(points).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())
                    request.setSubsetOfAttributes(point_attribute_indices)
                    for point_feature in point_source.getFeatures(request):
                        if feedback.isCanceled():
                            break

                        if engine.contains(point_feature.geometry().constGet()):
                            if weight_field_index >= 0:
                                weight = point_feature.attributes()[weight_field_index]
                                try:
                                    count += float(weight)
                                except:
                                    # Ignore fields with non-numeric values
                                    pass
                            elif class_field_index >= 0:
                                point_class = point_feature.attributes()[class_field_index]
                                if point_class not in classes:
                                    classes.add(point_class)
                            else:
                                count += 1

                output_feature.setGeometry(geom)

            attrs = polygon_feature.attributes()

            if class_field_index >= 0:
                score = len(classes)
            else:
                score = count
            if field_index == len(attrs):
                attrs.append(score)
            else:
                attrs[field_index] = score
            output_feature.setAttributes(attrs)
            sink.addFeature(output_feature, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
예제 #52
0
def interpolate_polygon_polygon(source, target, wgs84_extent):
    """ Transfer values from source polygon layer to the target polygon layer.

    This method will do a spatial join: the output layer will contain all
    features from target layer, with the addition of attributes of intersecting
    feature from the source layer. If there is not intersecting source feature,
    the output layer will still contain the target feature, with null
    attributes from the source layer.

    The intersection test considers only centroids of target features
    (not the whole polygon geometry).

    If more features from source layer intersect a target feature, only
    the first intersecting source feature will be used.

    :param source: Source polygon layer
    :type source: QgsVectorLayer

    :param target: Target polygon layer
    :type target: QgsVectorLayer

    :param wgs84_extent: Requested extent for analysis, in WGS84 coordinates
    :type wgs84_extent: QgsRectangle

    :return: output layer
    :rtype: QgsVectorLayer
    """

    source_field_count = source.dataProvider().fields().count()
    target_field_count = target.dataProvider().fields().count()

    # Create new layer for writing resulting features.
    # It will contain attributes of both target and source layers
    result = create_layer(target)
    new_fields = source.dataProvider().fields().toList()
    new_fields.append(QgsField('polygon_id', QVariant.Int))
    result.dataProvider().addAttributes(new_fields)
    result.updateFields()
    result_fields = result.dataProvider().fields()

    # setup transform objects between different CRS
    crs_wgs84 = QgsCoordinateReferenceSystem("EPSG:4326")
    wgs84_to_source = QgsCoordinateTransform(crs_wgs84, source.crs())
    wgs84_to_target = QgsCoordinateTransform(crs_wgs84, target.crs())
    source_to_target = QgsCoordinateTransform(source.crs(), target.crs())

    # compute extents in CRS of layers
    source_extent = wgs84_to_source.transformBoundingBox(wgs84_extent)
    target_extent = wgs84_to_target.transformBoundingBox(wgs84_extent)

    # cache source layer (in CRS of target layer)
    source_index = QgsSpatialIndex()
    source_geometries = {}  # key = feature ID, value = QgsGeometry
    source_attributes = {}
    for f in source.getFeatures(QgsFeatureRequest(source_extent)):
        f.geometry().transform(source_to_target)
        source_index.insertFeature(f)
        source_geometries[f.id()] = QgsGeometry(f.geometry())
        source_attributes[f.id()] = f.attributes()

    # Go through all features in target layer and for each decide
    # whether it is intersected by any source feature
    result_features = []
    for f in target.getFeatures(QgsFeatureRequest(target_extent)):
        # we use just centroids of target polygons
        centroid_geometry = f.geometry().centroid()
        centroid = centroid_geometry.asPoint()
        rect = QgsRectangle(
            centroid.x(), centroid.y(),
            centroid.x(), centroid.y())
        ids = source_index.intersects(rect)

        has_matching_source = False
        for source_id in ids:
            if source_geometries[source_id].intersects(centroid_geometry):
                # we have found intersection between source and target
                f_result = QgsFeature(result_fields)
                f_result.setGeometry(f.geometry())
                for i in xrange(target_field_count):
                    f_result[i] = f[i]
                for i in xrange(source_field_count):
                    f_result[i + target_field_count] = source_attributes[
                        source_id][i]
                f_result['polygon_id'] = source_id
                result_features.append(f_result)
                has_matching_source = True
                break   # assuming just one source for each target feature

        # if there is no intersecting feature from source layer,
        # we will keep the source attributes null
        if not has_matching_source:
            f_result = QgsFeature(result_fields)
            f_result.setGeometry(f.geometry())
            for i in xrange(target_field_count):
                f_result[i] = f[i]
            result_features.append(f_result)

        if len(result_features) == 1000:
            result.dataProvider().addFeatures(result_features)
            result_features = []

    result.dataProvider().addFeatures(result_features)
    return result
예제 #53
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return {self.OUTPUT: dest_id}
예제 #54
0
layer.startEditing()
layer.dataProvider().addAttributes(
        [QgsField('right', QVariant.String),
         QgsField('left', QVariant.String),
         QgsField('above', QVariant.String),
         QgsField('below', QVariant.String)] )
layer.updateFields()

feature_dict = {f.id(): f for f in layer.getFeatures()}
index = QgsSpatialIndex( layer.getFeatures() )

for f in feature_dict.values():
    geom = f.geometry()
    bbox1 = geom.boundingBox().toString(2).replace(" : ",",").split(",")
    intersecting_ids = index.intersects( geom.boundingBox() )

    for intersecting_id in intersecting_ids:
        intersecting_f = feature_dict[intersecting_id]

        if ( f != intersecting_f and not intersecting_f.geometry().disjoint(geom) ):
            bbox2 = intersecting_f.geometry().boundingBox().toString(2).replace(" : ",",").split(",")
            relX = [bbox1[0:3:2].index( c ) for c in bbox1[0:3:2] if c not in bbox2[0:3:2]]
            relY = [bbox1[1:4:2].index( c ) for c in bbox1[1:4:2] if c not in bbox2[1:4:2]]
            if relX == [0] and relY == []:
          	    f['right'] = intersecting_f[Page_number_field]
            elif relX == [] and relY == [0]:
	              f['above'] = intersecting_f[Page_number_field]
            elif relX == [1] and relY == []:
	              f['left'] = intersecting_f[Page_number_field]
            elif relX == [] and relY == [1]:
예제 #55
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

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

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

        features = source.getFeatures()

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

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

        index = QgsSpatialIndex()

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

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

            if not f.hasGeometry():
                continue

            point = f.geometry().asPoint()

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

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

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

            feedback.setProgress(int(current * total))

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

        fullPerimeter = 2 * math.pi

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

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

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

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

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

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

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

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

        return {self.OUTPUT: dest_id}
예제 #56
0
    def run(self):
        """Experimental impact function."""
        self.validate()
        self.prepare()

        self.provenance.append_step(
            'Calculating Step',
            'Impact function is calculating the impact.')

        # Get parameters from layer's keywords
        self.hazard_class_attribute = self.hazard.keyword('field')
        self.hazard_class_mapping = self.hazard.keyword('value_map')
        self.exposure_class_attribute = self.exposure.keyword(
            'structure_class_field')

        # Prepare Hazard Layer
        hazard_provider = self.hazard.layer.dataProvider()

        # Check affected field exists in the hazard layer
        affected_field_index = hazard_provider.fieldNameIndex(
            self.hazard_class_attribute)
        if affected_field_index == -1:
            message = tr(
                'Field "%s" is not present in the attribute table of the '
                'hazard layer. Please change the Affected Field parameter in '
                'the IF Option.') % self.hazard_class_attribute
            raise GetDataError(message)

        srs = self.exposure.layer.crs().toWkt()
        exposure_provider = self.exposure.layer.dataProvider()
        exposure_fields = exposure_provider.fields()

        # Check self.exposure_class_attribute exists in exposure layer
        building_type_field_index = exposure_provider.fieldNameIndex(
            self.exposure_class_attribute)
        if building_type_field_index == -1:
            message = tr(
                'Field "%s" is not present in the attribute table of '
                'the exposure layer. Please change the Building Type '
                'Field parameter in the IF Option.'
            ) % self.exposure_class_attribute
            raise GetDataError(message)

        # If target_field does not exist, add it:
        if exposure_fields.indexFromName(self.target_field) == -1:
            exposure_provider.addAttributes(
                [QgsField(self.target_field, QVariant.Int)])
        target_field_index = exposure_provider.fieldNameIndex(
            self.target_field)
        exposure_fields = exposure_provider.fields()

        # Create layer to store the buildings from E and extent
        buildings_are_points = is_point_layer(self.exposure.layer)
        if buildings_are_points:
            building_layer = QgsVectorLayer(
                'Point?crs=' + srs, 'impact_buildings', 'memory')
        else:
            building_layer = QgsVectorLayer(
                'Polygon?crs=' + srs, 'impact_buildings', 'memory')
        building_provider = building_layer.dataProvider()

        # Set attributes
        building_provider.addAttributes(exposure_fields.toList())
        building_layer.startEditing()
        building_layer.commitChanges()

        # Filter geometry and data using the requested extent
        requested_extent = QgsRectangle(*self.requested_extent)

        # This is a hack - we should be setting the extent CRS
        # in the IF base class via safe/engine/core.py:calculate_impact
        # for now we assume the extent is in 4326 because it
        # is set to that from geo_extent
        # See issue #1857
        transform = QgsCoordinateTransform(
            self.requested_extent_crs, self.hazard.crs())
        projected_extent = transform.transformBoundingBox(requested_extent)
        request = QgsFeatureRequest()
        request.setFilterRect(projected_extent)

        # Split building_layer by H and save as result:
        #   1) Filter from H inundated features
        #   2) Mark buildings as inundated (1) or not inundated (0)

        # make spatial index of affected polygons
        hazard_index = QgsSpatialIndex()
        hazard_geometries = {}  # key = feature id, value = geometry
        has_hazard_objects = False
        for feature in self.hazard.layer.getFeatures(request):
            value = feature[affected_field_index]
            if value not in self.hazard_class_mapping[self.wet]:
                continue
            hazard_index.insertFeature(feature)
            hazard_geometries[feature.id()] = QgsGeometry(feature.geometry())
            has_hazard_objects = True

        if not has_hazard_objects:
            message = tr(
                'There are no objects in the hazard layer with %s '
                'value in %s. Please check your data or use another '
                'attribute.') % (
                    self.hazard_class_attribute,
                    ', '.join(self.hazard_class_mapping[self.wet]))
            raise GetDataError(message)

        # Filter out just those EXPOSURE features in the analysis extents
        transform = QgsCoordinateTransform(
            self.requested_extent_crs, self.exposure.layer.crs())
        projected_extent = transform.transformBoundingBox(requested_extent)
        request = QgsFeatureRequest()
        request.setFilterRect(projected_extent)

        # We will use this transform to project each exposure feature into
        # the CRS of the Hazard.
        transform = QgsCoordinateTransform(
            self.exposure.crs(), self.hazard.crs())
        features = []
        for feature in self.exposure.layer.getFeatures(request):
            # Make a deep copy as the geometry is passed by reference
            # If we don't do this, subsequent operations will affect the
            # original feature geometry as well as the copy TS
            building_geom = QgsGeometry(feature.geometry())
            # Project the building geometry to hazard CRS
            building_bounds = transform.transform(building_geom.boundingBox())
            affected = False
            # get tentative list of intersecting hazard features
            # only based on intersection of bounding boxes
            ids = hazard_index.intersects(building_bounds)
            for fid in ids:
                # run (slow) exact intersection test
                building_geom.transform(transform)
                if hazard_geometries[fid].intersects(building_geom):
                    affected = True
                    break
            new_feature = QgsFeature()
            # We write out the original feature geom, not the projected one
            new_feature.setGeometry(feature.geometry())
            new_feature.setAttributes(feature.attributes())
            new_feature[target_field_index] = 1 if affected else 0
            features.append(new_feature)

            # every once in a while commit the created features
            # to the output layer
            if len(features) == 1000:
                (_, __) = building_provider.addFeatures(features)
                features = []

        (_, __) = building_provider.addFeatures(features)
        building_layer.updateExtents()

        # Generate simple impact report
        self.buildings = {}
        self.affected_buildings = OrderedDict([
            (tr('Flooded'), {})
        ])
        buildings_data = building_layer.getFeatures()
        building_type_field_index = building_layer.fieldNameIndex(
            self.exposure_class_attribute)
        for building in buildings_data:
            record = building.attributes()
            building_type = record[building_type_field_index]
            if building_type in [None, 'NULL', 'null', 'Null']:
                building_type = 'Unknown type'
            if building_type not in self.buildings:
                self.buildings[building_type] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][
                        building_type] = OrderedDict([
                            (tr('Buildings Affected'), 0)])
            self.buildings[building_type] += 1

            if record[target_field_index] == 1:
                self.affected_buildings[tr('Flooded')][building_type][
                    tr('Buildings Affected')] += 1

        # Lump small entries and 'unknown' into 'other' category
        # Building threshold #2468
        postprocessors = self.parameters['postprocessors']
        building_postprocessors = postprocessors['BuildingType'][0]
        self.building_report_threshold = building_postprocessors.value[0].value
        self._consolidate_to_other()

        impact_summary = self.html_report()

        # For printing map purpose
        map_title = tr('Buildings inundated')
        legend_title = tr('Structure inundated status')

        style_classes = [
            dict(label=tr('Not Inundated'), value=0, colour='#1EFC7C',
                 transparency=0, size=0.5),
            dict(label=tr('Inundated'), value=1, colour='#F31A1C',
                 transparency=0, size=0.5)]
        style_info = dict(
            target_field=self.target_field,
            style_classes=style_classes,
            style_type='categorizedSymbol')

        # Convert QgsVectorLayer to inasafe layer and return it.
        if building_layer.featureCount() < 1:
            raise ZeroImpactException(tr(
                'No buildings were impacted by this flood.'))

        extra_keywords = {
            'impact_summary': impact_summary,
            'map_title': map_title,
            'legend_title': legend_title,
            'target_field': self.target_field,
            'buildings_total': self.total_buildings,
            'buildings_affected': self.total_affected_buildings
        }

        self.set_if_provenance()

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        building_layer = Vector(
            data=building_layer,
            name=tr('Flooded buildings'),
            keywords=impact_layer_keywords,
            style_info=style_info)
        self._impact = building_layer
        return building_layer
예제 #57
0
    def run_stats(self):
        self.progressBar_stats.setValue(0)
        self.label_progressStats.setText('')
        # noinspection PyArgumentList
        QApplication.processEvents()

        blurred_layer = self.comboBox_blurredLayer.currentLayer()
        stats_layer = self.comboBox_statsLayer.currentLayer()

        try:

            if not blurred_layer or not stats_layer:
                raise NoLayerProvidedException

            crs_blurred_layer = blurred_layer.crs()
            crs_stats_layer = stats_layer.crs()

            if crs_blurred_layer != crs_stats_layer:
                raise DifferentCrsException(
                    epsg1=crs_blurred_layer.authid(),
                    epsg2=crs_stats_layer.authid())

            if blurred_layer == stats_layer:
                raise NoLayerProvidedException

            if not blurred_layer or not stats_layer:
                raise NoLayerProvidedException

            nb_feature_stats = stats_layer.featureCount()
            nb_feature_blurred = blurred_layer.featureCount()
            features_stats = {}

            label_preparing = tr('Preparing index on the stats layer')
            label_creating = tr('Creating index on the stats layer')
            label_calculating = tr('Calculating')

            if Qgis.QGIS_VERSION_INT < 20700:
                self.label_progressStats.setText('%s 1/3' % label_preparing)

                for i, feature in enumerate(stats_layer.getFeatures()):
                    features_stats[feature.id()] = feature
                    percent = int((i + 1) * 100 / nb_feature_stats)
                    self.progressBar_stats.setValue(percent)
                    # noinspection PyArgumentList
                    QApplication.processEvents()

                self.label_progressStats.setText('%s 2/3' % label_creating)
                # noinspection PyArgumentList
                QApplication.processEvents()
                index = QgsSpatialIndex()
                for i, f in enumerate(stats_layer.getFeatures()):
                    index.insertFeature(f)

                    percent = int((i + 1) * 100 / nb_feature_stats)
                    self.progressBar_stats.setValue(percent)
                    # noinspection PyArgumentList
                    QApplication.processEvents()

                self.label_progressStats.setText('%s 3/3' % label_calculating)

            else:
                # If QGIS >= 2.7, we can speed up the spatial index.
                # From 1 min 15 to 7 seconds on my PC.
                self.label_progressStats.setText('%s 1/2' % label_creating)
                # noinspection PyArgumentList
                QApplication.processEvents()
                index = QgsSpatialIndex(stats_layer.getFeatures())
                self.label_progressStats.setText('%s 2/2' % label_calculating)

            # noinspection PyArgumentList
            QApplication.processEvents()
            self.tab = []
            for i, feature in enumerate(blurred_layer.getFeatures()):
                count = 0
                ids = index.intersects(feature.geometry().boundingBox())
                for unique_id in ids:
                    request = QgsFeatureRequest().setFilterFid(unique_id)
                    f = next(stats_layer.getFeatures(request))

                    if f.geometry().intersects(feature.geometry()):
                        count += 1
                self.tab.append(count)

                percent = int((i + 1) * 100 / nb_feature_blurred)
                self.progressBar_stats.setValue(percent)
                # noinspection PyArgumentList
                QApplication.processEvents()

            stats = Stats(self.tab)

            items_stats = [
                'Count(blurred),%d' % nb_feature_blurred,
                'Count(stats),%d' % nb_feature_stats,
                'Min,%d' % stats.min(),
                'Average,%f' % stats.average(),
                'Max,%d' % stats.max(), 'Median,%f' % stats.median(),
                'Range,%d' % stats.range(),
                'Variance,%f' % stats.variance(),
                'Standard deviation,%f' % stats.standard_deviation()
            ]

            self.tableWidget.clear()
            self.tableWidget.setColumnCount(2)
            labels = ['Parameters', 'Values']
            self.tableWidget.setHorizontalHeaderLabels(labels)
            self.tableWidget.setRowCount(len(items_stats))

            for i, item in enumerate(items_stats):
                s = item.split(',')
                self.tableWidget.setItem(i, 0, QTableWidgetItem(s[0]))
                self.tableWidget.setItem(i, 1, QTableWidgetItem(s[1]))
            self.tableWidget.resizeRowsToContents()

            self.draw_plot(self.tab)

        except GeoPublicHealthException as e:
            self.label_progressStats.setText('')
            display_message_bar(msg=e.msg, level=e.level, duration=e.duration)
예제 #58
0
    def run(self):
        """Experimental impact function."""
        self.validate()
        self.prepare()

        # Get parameters from layer's keywords
        self.hazard_class_attribute = self.hazard.keyword('field')
        self.hazard_class_mapping = self.hazard.keyword('value_map')
        self.exposure_class_attribute = self.exposure.keyword(
            'structure_class_field')

        # Prepare Hazard Layer
        hazard_provider = self.hazard.layer.dataProvider()

        # Check affected field exists in the hazard layer
        affected_field_index = hazard_provider.fieldNameIndex(
            self.hazard_class_attribute)
        if affected_field_index == -1:
            message = tr(
                'Field "%s" is not present in the attribute table of the '
                'hazard layer. Please change the Affected Field parameter in '
                'the IF Option.') % self.hazard_class_attribute
            raise GetDataError(message)

        srs = self.exposure.layer.crs().toWkt()
        exposure_provider = self.exposure.layer.dataProvider()
        exposure_fields = exposure_provider.fields()

        # Check self.exposure_class_attribute exists in exposure layer
        building_type_field_index = exposure_provider.fieldNameIndex(
            self.exposure_class_attribute)
        if building_type_field_index == -1:
            message = tr('Field "%s" is not present in the attribute table of '
                         'the exposure layer. Please change the Building Type '
                         'Field parameter in the IF Option.'
                         ) % self.exposure_class_attribute
            raise GetDataError(message)

        # If target_field does not exist, add it:
        if exposure_fields.indexFromName(self.target_field) == -1:
            exposure_provider.addAttributes(
                [QgsField(self.target_field, QVariant.Int)])
        target_field_index = exposure_provider.fieldNameIndex(
            self.target_field)
        exposure_fields = exposure_provider.fields()

        # Create layer to store the lines from E and extent
        building_layer = QgsVectorLayer('Polygon?crs=' + srs,
                                        'impact_buildings', 'memory')
        building_provider = building_layer.dataProvider()

        # Set attributes
        building_provider.addAttributes(exposure_fields.toList())
        building_layer.startEditing()
        building_layer.commitChanges()

        # Filter geometry and data using the requested extent
        requested_extent = QgsRectangle(*self.requested_extent)

        # This is a hack - we should be setting the extent CRS
        # in the IF base class via safe/engine/core.py:calculate_impact
        # for now we assume the extent is in 4326 because it
        # is set to that from geo_extent
        # See issue #1857
        transform = QgsCoordinateTransform(
            QgsCoordinateReferenceSystem('EPSG:%i' %
                                         self._requested_extent_crs),
            self.hazard.layer.crs())
        projected_extent = transform.transformBoundingBox(requested_extent)
        request = QgsFeatureRequest()
        request.setFilterRect(projected_extent)

        # Split building_layer by H and save as result:
        #   1) Filter from H inundated features
        #   2) Mark buildings as inundated (1) or not inundated (0)

        # make spatial index of affected polygons
        hazard_index = QgsSpatialIndex()
        hazard_geometries = {}  # key = feature id, value = geometry
        has_hazard_objects = False
        for feature in self.hazard.layer.getFeatures(request):
            value = feature[affected_field_index]
            if value not in self.hazard_class_mapping[self.wet]:
                continue
            hazard_index.insertFeature(feature)
            hazard_geometries[feature.id()] = QgsGeometry(feature.geometry())
            has_hazard_objects = True

        if not has_hazard_objects:
            message = tr(
                'There are no objects in the hazard layer with %s '
                'value in %s. Please check your data or use another '
                'attribute.') % (self.hazard_class_attribute, ', '.join(
                    self.hazard_class_mapping[self.wet]))
            raise GetDataError(message)

        features = []
        for feature in self.exposure.layer.getFeatures(request):
            building_geom = feature.geometry()
            affected = False
            # get tentative list of intersecting hazard features
            # only based on intersection of bounding boxes
            ids = hazard_index.intersects(building_geom.boundingBox())
            for fid in ids:
                # run (slow) exact intersection test
                if hazard_geometries[fid].intersects(building_geom):
                    affected = True
                    break
            f = QgsFeature()
            f.setGeometry(building_geom)
            f.setAttributes(feature.attributes())
            f[target_field_index] = 1 if affected else 0
            features.append(f)

            # every once in a while commit the created features
            # to the output layer
            if len(features) == 1000:
                (_, __) = building_provider.addFeatures(features)
                features = []

        (_, __) = building_provider.addFeatures(features)
        building_layer.updateExtents()

        # Generate simple impact report
        self.buildings = {}
        self.affected_buildings = OrderedDict([(tr('Flooded'), {})])
        buildings_data = building_layer.getFeatures()
        building_type_field_index = building_layer.fieldNameIndex(
            self.exposure_class_attribute)
        for building in buildings_data:
            record = building.attributes()
            building_type = record[building_type_field_index]
            if building_type in [None, 'NULL', 'null', 'Null']:
                building_type = 'Unknown type'
            if building_type not in self.buildings:
                self.buildings[building_type] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][
                        building_type] = OrderedDict([
                            (tr('Buildings Affected'), 0)
                        ])
            self.buildings[building_type] += 1

            if record[target_field_index] == 1:
                self.affected_buildings[tr('Flooded')][building_type][tr(
                    'Buildings Affected')] += 1

        # Lump small entries and 'unknown' into 'other' category
        self._consolidate_to_other()

        impact_summary = self.html_report()

        # For printing map purpose
        map_title = tr('Buildings inundated')
        legend_title = tr('Structure inundated status')

        style_classes = [
            dict(label=tr('Not Inundated'),
                 value=0,
                 colour='#1EFC7C',
                 transparency=0,
                 size=0.5),
            dict(label=tr('Inundated'),
                 value=1,
                 colour='#F31A1C',
                 transparency=0,
                 size=0.5)
        ]
        style_info = dict(target_field=self.target_field,
                          style_classes=style_classes,
                          style_type='categorizedSymbol')

        # Convert QgsVectorLayer to inasafe layer and return it.
        if building_layer.featureCount() < 1:
            raise ZeroImpactException(
                tr('No buildings were impacted by this flood.'))
        building_layer = Vector(data=building_layer,
                                name=tr('Flooded buildings'),
                                keywords={
                                    'impact_summary':
                                    impact_summary,
                                    'map_title':
                                    map_title,
                                    'legend_title':
                                    legend_title,
                                    'target_field':
                                    self.target_field,
                                    'buildings_total':
                                    self.total_buildings,
                                    'buildings_affected':
                                    self.total_affected_buildings
                                },
                                style_info=style_info)
        self._impact = building_layer
        return building_layer
예제 #59
0
파일: Union.py 프로젝트: timlinux/QGIS
    def processAlgorithm(self, parameters, context, feedback):
        sourceA = self.parameterAsSource(parameters, self.INPUT, context)
        sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)

        geomType = QgsWkbTypes.multiType(sourceA.wkbType())
        fields = vector.combineFields(sourceA.fields(), sourceB.fields())

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, geomType, sourceA.sourceCrs())

        featA = QgsFeature()
        featB = QgsFeature()
        outFeat = QgsFeature()

        indexA = QgsSpatialIndex(sourceA, feedback)
        indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback)

        total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1
        count = 0

        for featA in sourceA.getFeatures():
            if feedback.isCanceled():
                break

            lstIntersectingB = []
            geom = featA.geometry()
            atMapA = featA.attributes()
            intersects = indexB.intersects(geom.boundingBox())
            if len(intersects) < 1:
                try:
                    outFeat.setGeometry(geom)
                    outFeat.setAttributes(atMapA)
                    sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                except:
                    # This really shouldn't happen, as we haven't
                    # edited the input geom at all
                    feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
            else:
                request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([])
                request.setDestinationCrs(sourceA.sourceCrs())

                engine = QgsGeometry.createGeometryEngine(geom.geometry())
                engine.prepareGeometry()

                for featB in sourceB.getFeatures(request):
                    atMapB = featB.attributes()
                    tmpGeom = featB.geometry()

                    if engine.intersects(tmpGeom.geometry()):
                        int_geom = geom.intersection(tmpGeom)
                        lstIntersectingB.append(tmpGeom)

                        if not int_geom:
                            # There was a problem creating the intersection
                            feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
                            int_geom = QgsGeometry()
                        else:
                            int_geom = QgsGeometry(int_geom)

                        if int_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(int_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection:
                            # Intersection produced different geomety types
                            temp_list = int_geom.asGeometryCollection()
                            for i in temp_list:
                                if i.type() == geom.type():
                                    int_geom = QgsGeometry(i)
                                    try:
                                        outFeat.setGeometry(int_geom)
                                        outFeat.setAttributes(atMapA + atMapB)
                                        sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                                    except:
                                        feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
                        else:
                            # Geometry list: prevents writing error
                            # in geometries of different types
                            # produced by the intersection
                            # fix #3549
                            if int_geom.wkbType() in wkbTypeGroups[wkbTypeGroups[int_geom.wkbType()]]:
                                try:
                                    outFeat.setGeometry(int_geom)
                                    outFeat.setAttributes(atMapA + atMapB)
                                    sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                                except:
                                    feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))

                # the remaining bit of featA's geometry
                # if there is nothing left, this will just silently fail and we're good
                diff_geom = QgsGeometry(geom)
                if len(lstIntersectingB) != 0:
                    intB = QgsGeometry.unaryUnion(lstIntersectingB)
                    diff_geom = diff_geom.difference(intB)

                if diff_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(diff_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection:
                    temp_list = diff_geom.asGeometryCollection()
                    for i in temp_list:
                        if i.type() == geom.type():
                            diff_geom = QgsGeometry(i)
                try:
                    outFeat.setGeometry(diff_geom)
                    outFeat.setAttributes(atMapA)
                    sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                except:
                    feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))

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

        length = len(sourceA.fields())
        atMapA = [None] * length

        for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())):
            if feedback.isCanceled():
                break

            add = False
            geom = featA.geometry()
            diff_geom = QgsGeometry(geom)
            atMap = [None] * length
            atMap.extend(featA.attributes())
            intersects = indexA.intersects(geom.boundingBox())

            if len(intersects) < 1:
                try:
                    outFeat.setGeometry(geom)
                    outFeat.setAttributes(atMap)
                    sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                except:
                    feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
            else:
                request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([])
                request.setDestinationCrs(sourceA.sourceCrs())

                # use prepared geometries for faster intersection tests
                engine = QgsGeometry.createGeometryEngine(diff_geom.geometry())
                engine.prepareGeometry()

                for featB in sourceA.getFeatures(request):
                    atMapB = featB.attributes()
                    tmpGeom = featB.geometry()

                    if engine.intersects(tmpGeom.geometry()):
                        add = True
                        diff_geom = QgsGeometry(diff_geom.difference(tmpGeom))
                    else:
                        try:
                            # Ihis only happens if the bounding box
                            # intersects, but the geometry doesn't
                            outFeat.setGeometry(diff_geom)
                            outFeat.setAttributes(atMap)
                            sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                        except:
                            feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))

            if add:
                try:
                    outFeat.setGeometry(diff_geom)
                    outFeat.setAttributes(atMap)
                    sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
                except:
                    feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))

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

        return {self.OUTPUT: dest_id}
예제 #60
0
def middle(bar, buildings_layer_path, receiver_points_layer_path):

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

    # defines emission_points layer
    receiver_points_fields = QgsFields()
    receiver_points_fields.append(QgsField("id_pt", QVariant.Int))
    receiver_points_fields.append(QgsField("id_bui", QVariant.Int))

    receiver_points_writer = QgsVectorFileWriter(receiver_points_layer_path,
                                                 "System",
                                                 receiver_points_fields,
                                                 QgsWkbTypes.Point,
                                                 buildings_layer.crs(),
                                                 "ESRI Shapefile")

    # gets features from layer
    buildings_feat_all = buildings_layer.dataProvider().getFeatures()

    # creates SpatialIndex
    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

    # defines distanze_point
    distance_point = 0.1

    # re-gets features from layer
    buildings_feat_all = buildings_layer.dataProvider().getFeatures()
    buildings_feat_total = buildings_layer.dataProvider().featureCount()

    pt_id = 0
    buildings_feat_number = 0
    for buildings_feat in buildings_feat_all:

        buildings_feat_number = buildings_feat_number + 1
        barValue = buildings_feat_number / float(buildings_feat_total) * 100
        bar.setValue(barValue)

        building_geom = buildings_feat.geometry()
        if building_geom.isMultipart():
            buildings_pt = building_geom.asMultiPolygon()[0]
            #building_geom.convertToSingleType()
        else:
            buildings_pt = buildings_feat.geometry().asPolygon()

        # creates the search rectangle to match the receiver point in the building and del them

        rect = QgsRectangle()
        rect.setXMinimum(buildings_feat.geometry().boundingBox().xMinimum() -
                         distance_point)
        rect.setXMaximum(buildings_feat.geometry().boundingBox().xMaximum() +
                         distance_point)
        rect.setYMinimum(buildings_feat.geometry().boundingBox().yMinimum() -
                         distance_point)
        rect.setYMaximum(buildings_feat.geometry().boundingBox().yMaximum() +
                         distance_point)

        buildings_selection = buildings_spIndex.intersects(rect)

        if len(buildings_pt) > 0:
            for i in range(0, len(buildings_pt)):

                buildings_pts = buildings_pt[i]

                ####
                # start part to delete pseudo vertex
                # this part it's different from the diffraction delete pseudo vertex part
                pts_index_to_delete_list = []
                m_delta = 0.01

                for ii in range(0, len(buildings_pts) - 1):

                    x1 = buildings_pts[ii - 1][0]
                    x2 = buildings_pts[ii][0]
                    x3 = buildings_pts[ii + 1][0]
                    y1 = buildings_pts[ii - 1][1]
                    y2 = buildings_pts[ii][1]
                    y3 = buildings_pts[ii + 1][1]

                    # particular cases: first point to delete! (remember that the first and the last have the same coordinates)
                    if ii == 0 and (x2 == x1 and y2 == y1):
                        x1 = buildings_pts[ii - 2][0]
                        y1 = buildings_pts[ii - 2][1]

                    # angular coefficient to find pseudo vertex
                    if x2 - x1 != 0 and x3 - x1 != 0:
                        m1 = (y2 - y1) / (x2 - x1)
                        m2 = (y3 - y1) / (x3 - x1)

                        #if round(m1,2) <= round(m2,2) + m_delta and round(m1,2) >= round(m2,2) - m_delta:
                        if m1 <= m2 + m_delta and m1 >= m2 - m_delta:
                            pts_index_to_delete_list.append(ii)

                            # particular cases: first point to delete! (remember that the first and the last have the same coordinates)
                            # here we delete the last and add x3,y3 (buildings_pts[ii+1] - the new last point)
                            if ii == 0:
                                pts_index_to_delete_list.append(
                                    len(buildings_pts) - 1)
                                buildings_pts.append(buildings_pts[ii + 1])

                # del pseudo vertex
                pts_index_to_delete_list = sorted(pts_index_to_delete_list,
                                                  reverse=True)

                for pt_index_to_del in pts_index_to_delete_list:
                    del buildings_pts[pt_index_to_del]

                # end part to delete pseudo vertex

                # for to generate receiver points
                for ii in range(0, len(buildings_pts) - 1):

                    x1 = buildings_pts[ii][0]
                    x2 = buildings_pts[ii + 1][0]
                    y1 = buildings_pts[ii][1]
                    y2 = buildings_pts[ii + 1][1]

                    xm = (x1 + x2) / 2
                    ym = (y1 + y2) / 2

                    if y2 == y1:
                        dx = 0
                        dy = distance_point
                    elif x2 == x1:
                        dx = distance_point
                        dy = 0
                    else:
                        m = (y2 - y1) / (x2 - x1)
                        m_p = -1 / m
                        dx = sqrt((distance_point**2) / (1 + m_p**2))
                        dy = sqrt(
                            ((distance_point**2) * (m_p**2)) / (1 + m_p**2))

                    if (x2 >= x1 and y2 >= y1) or (x2 < x1 and y2 < y1):
                        pt1 = QgsPointXY(xm + dx, ym - dy)
                        pt2 = QgsPointXY(xm - dx, ym + dy)
                    if (x2 >= x1 and y2 < y1) or (x2 < x1 and y2 >= y1):
                        pt1 = QgsPointXY(xm + dx, ym + dy)
                        pt2 = QgsPointXY(xm - dx, ym - dy)

                    pt = QgsFeature()

                    # pt1, check if is in a building and eventually add it
                    pt.setGeometry(QgsGeometry.fromPointXY(pt1))
                    intersect = 0
                    for buildings_id in buildings_selection:
                        if buildings_feat_all_dict[buildings_id].geometry(
                        ).intersects(pt.geometry()) == 1:
                            intersect = 1
                            break

                    if intersect == 0:
                        pt.setAttributes([pt_id, buildings_feat.id()])
                        receiver_points_writer.addFeature(pt)
                        pt_id = pt_id + 1

                    # pt2, check if is in a building and eventually add it
                    pt.setGeometry(QgsGeometry.fromPointXY(pt2))
                    intersect = 0
                    for buildings_id in buildings_selection:
                        if buildings_feat_all_dict[buildings_id].geometry(
                        ).intersects(pt.geometry()) == 1:
                            intersect = 1
                            break

                    if intersect == 0:
                        pt.setAttributes([pt_id, buildings_feat.id()])
                        receiver_points_writer.addFeature(pt)
                        pt_id = pt_id + 1

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

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