def updateGui(self, feature):
        # The important part: get the feature iterator with an expression
        request = QgsFeatureRequest().setFilterExpression ( u'"pand_id" = \'' + unicode(feature['gebouwnummer']) + '\'' )
        request.setFlags( QgsFeatureRequest.NoGeometry )
        features = self.geomLayer.getFeatures( request )

        atr = None
        fields = self.geomLayer.pendingFields()
        field_names = [field.name() for field in fields]
        for elem in features:
            self.editFeature = elem
            atr = dict(zip(field_names, elem.attributes()))

        if atr is not None:
            atr = {i:j for i,j in atr.items() if j }
            self.newGroupBox.hide()
            self.editGroupBox.show()
            self.verwijderWidget.show()
            self.showValues(feature, atr)
            self.actionButtonBox.setDisabled(True)

        else:
            self.newGroupBox.show()
            self.editGroupBox.hide()
            self.verwijderWidget.hide()

            self.editFeature = None
            self.statusNieuwLabel.setText(unicode(feature['status']))
            self.pandidLabel.setText(unicode(feature['gebouwnummer']))
Example #2
0
    def testGetFeaturesFilterRectTests(self):
        extent = QgsRectangle(-70, 67, -60, 80)
        request = QgsFeatureRequest().setFilterRect(extent)
        features = [f['pk'] for f in self.source.getFeatures(request)]
        all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
        assert set(features) == set([2, 4]), 'Got {} instead'.format(features)
        self.assertTrue(all_valid)

        # test that results match QgsFeatureRequest.acceptFeature
        for f in self.source.getFeatures():
            self.assertEqual(request.acceptFeature(f), f['pk'] in set([2, 4]))

        # test with an empty rectangle
        extent = QgsRectangle()
        request = QgsFeatureRequest().setFilterRect(extent)
        features = [f['pk'] for f in self.source.getFeatures(request)]
        all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
        assert set(features) == set([1, 2, 3, 4, 5]), 'Got {} instead'.format(features)
        self.assertTrue(all_valid)

        # ExactIntersection flag set, but no filter rect set. Should be ignored.
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.ExactIntersect)
        features = [f['pk'] for f in self.source.getFeatures(request)]
        all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
        assert set(features) == set([1, 2, 3, 4, 5]), 'Got {} instead'.format(features)
        self.assertTrue(all_valid)
Example #3
0
 def findFeaturesAt(mapPoint, layerConfig, mapTool):
     """
     To find features from a given position in a given layer
     :param mapPoint: the map position
     :param layerConfig: the layer in which we are looking for features
     :param mapTool: a QgsMapTool instance
     :return: features found in layer
     """
     if layerConfig is None:
         return None
     tolerance = layerConfig.tolerance
     if layerConfig.unit == QgsTolerance.Pixels:
         layTolerance = Finder.calcCanvasTolerance(mapTool.toCanvasCoordinates(mapPoint), layerConfig.layer, mapTool,
                                                   tolerance)
     elif layerConfig.unit == QgsTolerance.ProjectUnits:
         layTolerance = Finder.calcMapTolerance(mapPoint, layerConfig.layer, mapTool, tolerance)
     else:
         layTolerance = tolerance
     layPoint = mapTool.toLayerCoordinates(layerConfig.layer, mapPoint)
     searchRect = QgsRectangle(layPoint.x() - layTolerance, layPoint.y() - layTolerance,
                               layPoint.x() + layTolerance, layPoint.y() + layTolerance)
     request = QgsFeatureRequest()
     request.setFilterRect(searchRect)
     request.setFlags(QgsFeatureRequest.ExactIntersect)
     features = []
     for feature in layerConfig.layer.getFeatures(request):
         if layerConfig.layer.geometryType() == QGis.Polygon:
             dist, nearest, vertex = feature.geometry().closestSegmentWithContext(mapPoint)
             if QgsGeometry.fromPoint(nearest).intersects(searchRect):
                 features.append(QgsFeature(feature))
         else:
             features.append(QgsFeature(feature))
     return features
Example #4
0
def writeTmpLayer(layer, restrictToExtent, iface, extent):
    fields = layer.fields()
    usedFields = []
    for count, field in enumerate(fields):
        fieldIndex = fields.indexFromName(unicode(field.name()))
        editorWidget = layer.editorWidgetSetup(fieldIndex).type()
        addField = False
        try:
            if layer.renderer().classAttribute() == field.name():
                addField = True
        except:
            pass
        if layer.customProperty("labeling/fieldName") == field.name():
            addField = True
        if (editorWidget != 'Hidden'):
            addField = True
        if addField:
            usedFields.append(count)
    uri = TYPE_MAP[layer.wkbType()]
    crs = layer.crs()
    if crs.isValid():
        uri += '?crs=' + crs.authid()
    for field in usedFields:
        fieldIndex = layer.fields().indexFromName(unicode(
            layer.fields().field(field).name()))
        editorWidget = layer.editorWidgetSetup(fieldIndex).type()
        fieldType = layer.fields().field(field).type()
        fieldName = layer.fields().field(field).name()
        if (editorWidget == 'Hidden'):
            fieldName = "q2wHide_" + fieldName
        fieldType = "double" if (fieldType == QVariant.Double or
                                 fieldType == QVariant.Int) else ("string")
        uri += '&field=' + unicode(fieldName) + ":" + fieldType
    newlayer = QgsVectorLayer(uri, layer.name(), 'memory')
    writer = newlayer.dataProvider()
    outFeat = QgsFeature()
    if restrictToExtent and extent == "Canvas extent":
        canvas = iface.mapCanvas()
        extent = canvas.extent()
        canvasCRS = canvas.mapSettings().destinationCrs()
        layerCRS = layer.crs()
        try:
            transform = QgsCoordinateTransform(canvasCRS, layerCRS,
                                               QgsProject.instance())
        except:
            transform = QgsCoordinateTransform(canvasCRS, layerCRS)
        projectedExtent = transform.transformBoundingBox(extent)
        request = QgsFeatureRequest(projectedExtent)
        request.setFlags(QgsFeatureRequest.ExactIntersect)
        features = layer.getFeatures(request)
    else:
        features = layer.getFeatures()
    for feature in features:
        if feature.geometry() is not None:
            outFeat.setGeometry(feature.geometry())
        attrs = [feature[f] for f in usedFields]
        if attrs:
            outFeat.setAttributes(attrs)
        writer.addFeatures([outFeat])
    return newlayer
Example #5
0
def clone_layer(layer, keep_selection=True):
    """Duplicate the layer by taking the same source and copying keywords.

    :param keep_selection: If we should keep the selection. Default to true.
    :type keep_selection: bool

    :param layer: Layer to be duplicated.
    :type layer: QgsMapLayer

    :return: The new QgsMapLayer object.
    :rtype: QgsMapLayer
    """
    if is_vector_layer(layer):
        new_layer = QgsVectorLayer(
            layer.source(), layer.name(), layer.providerType())
        if keep_selection and layer.selectedFeatureCount() > 0:
            request = QgsFeatureRequest()
            request.setFilterFids(layer.selectedFeatureIds())
            request.setFlags(QgsFeatureRequest.NoGeometry)
            iterator = layer.getFeatures(request)
            new_layer.setSelectedFeatures([k.id() for k in iterator])
    else:
        new_layer = QgsRasterLayer(
            layer.source(), layer.name(), layer.providerType())

    new_layer.keywords = copy_layer_keywords(layer.keywords)

    return layer
Example #6
0
 def featureCount(self):
     if not self.subsetString():
         return len(self._features)
     else:
         req = QgsFeatureRequest()
         req.setFlags(QgsFeatureRequest.NoGeometry)
         req.setSubsetOfAttributes([])
         return len([f for f in self.getFeatures(req)])
Example #7
0
 def uniqueValues(self, fieldIndex, limit=1):
     results = set()
     if fieldIndex >= 0 and fieldIndex < self.fields().count():
         req = QgsFeatureRequest()
         req.setFlags(QgsFeatureRequest.NoGeometry)
         req.setSubsetOfAttributes([fieldIndex])
         for f in self.getFeatures(req):
             results.add(f.attributes()[fieldIndex])
     return results
 def getLayerFeature(self):
     layerFeature = QgsFeature()
     featReq = QgsFeatureRequest().setFilterFid(self.layerFeatureId)
     if not self.featureLayer.hasGeometryType():
         featReq.setFlags(QgsFeatureRequest.NoGeometry)
     if self.featureLayer.getFeatures(featReq).nextFeature(layerFeature):
         return layerFeature
     else:
         return None
Example #9
0
    def test_sql2(self):
        l2 = QgsVectorLayer(os.path.join(self.testDataDir, "france_parts.shp"), "france_parts", "ogr", False)
        self.assertEqual(l2.isValid(), True)
        QgsProject.instance().addMapLayer(l2)

        query = toPercent("SELECT * FROM france_parts")
        l4 = QgsVectorLayer("?query=%s&uid=ObjectId" % query, "tt", "virtual")
        self.assertEqual(l4.isValid(), True)

        self.assertEqual(l4.dataProvider().wkbType(), 3)
        self.assertEqual(l4.dataProvider().crs().postgisSrid(), 4326)

        n = 0
        r = QgsFeatureRequest(QgsRectangle(-1.677, 49.624, -0.816, 49.086))
        for f in l4.getFeatures(r):
            self.assertEqual(f.geometry() is not None, True)
            self.assertEqual(f.attributes()[0], 2661)
            n += 1
        self.assertEqual(n, 1)

        # use uid
        query = toPercent("SELECT * FROM france_parts")
        l5 = QgsVectorLayer("?query=%s&geometry=geometry:polygon:4326&uid=ObjectId" % query, "tt", "virtual")
        self.assertEqual(l5.isValid(), True)

        idSum = sum(f.id() for f in l5.getFeatures())
        self.assertEqual(idSum, 10659)

        r = QgsFeatureRequest(2661)
        idSum2 = sum(f.id() for f in l5.getFeatures(r))
        self.assertEqual(idSum2, 2661)

        r = QgsFeatureRequest()
        r.setFilterFids([2661, 2664])
        self.assertEqual(sum(f.id() for f in l5.getFeatures(r)), 2661 + 2664)

        # test attribute subset
        r = QgsFeatureRequest()
        r.setFlags(QgsFeatureRequest.SubsetOfAttributes)
        r.setSubsetOfAttributes([1])
        s = [(f.id(), f.attributes()[1]) for f in l5.getFeatures(r)]
        self.assertEqual(sum([x[0] for x in s]), 10659)
        self.assertEqual(sum([x[1] for x in s]), 3064.0)

        # test NoGeometry
        # by request flag
        r = QgsFeatureRequest()
        r.setFlags(QgsFeatureRequest.NoGeometry)
        self.assertEqual(all([not f.hasGeometry() for f in l5.getFeatures(r)]), True)

        # test subset
        self.assertEqual(l5.dataProvider().featureCount(), 4)
        l5.setSubsetString("ObjectId = 2661")
        idSum2 = sum(f.id() for f in l5.getFeatures(r))
        self.assertEqual(idSum2, 2661)
        self.assertEqual(l5.dataProvider().featureCount(), 1)
    def layerData(self, layer, request={}, offset=0):
        # Retrieve the data for a layer
        first = True
        data = {}
        fields = []
        fieldTypes = []
        fr = QgsFeatureRequest()
        if request:
            if 'exact' in request and request['exact']:
                fr.setFlags(QgsFeatureRequest.ExactIntersect)
            if 'nogeom' in request and request['nogeom']:
                fr.setFlags(QgsFeatureRequest.NoGeometry)
            if 'fid' in request:
                fr.setFilterFid(request['fid'])
            elif 'extents' in request:
                fr.setFilterRect(QgsRectangle(*request['extents']))
            if 'attributes' in request:
                fr.setSubsetOfAttributes(request['attributes'])

        # IMPORTANT - we do not use `for f in layer.getFeatures(fr):` as we need
        # to verify that existing attributes and geometry are correctly cleared
        # from the feature when calling nextFeature()
        it = layer.getFeatures(fr)
        f = QgsFeature()
        while it.nextFeature(f):
            if first:
                first = False
                for field in f.fields():
                    fields.append(str(field.name()))
                    fieldTypes.append(str(field.typeName()))
            if sys.version_info.major == 2:
                fielddata = dict((name, str(f[name])) for name in fields)
            else:
                fielddata = dict((name, str(f[name])) for name in fields)
            g = f.geometry()
            if not g.isEmpty():
                fielddata[geomkey] = str(g.exportToWkt())
            else:
                fielddata[geomkey] = "None"

            fielddata[fidkey] = f.id()
            id = fielddata[fields[0]]
            description = fielddata[fields[1]]
            fielddata['id'] = id
            fielddata['description'] = description
            data[f.id() + offset] = fielddata

        if 'id' not in fields:
            fields.insert(0, 'id')
        if 'description' not in fields:
            fields.insert(1, 'description')
        fields.append(fidkey)
        fields.append(geomkey)
        return fields, fieldTypes, data
    def search(self):
        project = QgsProject.instance()
        layerId, ok = project.readEntry(self.pluginName, 'layerId')
        fieldName, ok = project.readEntry(self.pluginName, 'fieldName')
        pattern = self.searchText.text()

        self.layer = QgsMapLayerRegistry.instance().mapLayer(layerId)
        if self.layer is None:
            self.iface.messageBar().pushMessage(self.name,
                self.tr("Choose a layer first in settings dialog."),
                QgsMessageBar.WARNING, 3)
            self.showSettings()
            return

        if fieldName == "":
            self.iface.messageBar().pushMessage(self.name,
                self.tr("Choose a field first in settings dialog."),
                QgsMessageBar.WARNING, 3)
            self.showSettings()
            return
        fields = self.layer.dataProvider().fields()
        fieldIndex = fields.indexFromName(fieldName)

        # create feature request
        featReq = QgsFeatureRequest()
        featReq.setFlags(QgsFeatureRequest.NoGeometry)
        featReq.setSubsetOfAttributes([fieldIndex])
        iterator = self.layer.getFeatures(featReq)

        # process search
        f = QgsFeature()
        results = []
        self.continueSearch = True
        while iterator.nextFeature(f) and self.continueSearch:
            if self.evaluate(f[fieldName], pattern):
                results.append(f.id())
            QCoreApplication.processEvents()

        # process results
        if self.continueSearch:
            msg = self.tr("{} features found").format(len(results))

            legend = self.iface.legendInterface()
            if (not legend.isLayerVisible(self.layer)):
                msg += self.tr(' - The layer named {} is not visible').format(self.layer.name())

            self.iface.messageBar().pushMessage(self.name,
                msg,
                QgsMessageBar.INFO, 2)

            self.processResults(results)
Example #12
0
 def getFeatIdList(self, currentLayer):
     #getting all features ids
     if self.mFieldExpressionWidget.currentText() == '':
         featIdList = currentLayer.allFeatureIds()
     elif not self.mFieldExpressionWidget.isValidExpression():
         self.iface.messageBar().pushMessage(self.tr('Warning!'), self.tr('Invalid attribute filter!'), level=QgsMessageBar.WARNING, duration=2)
         return []
     else:
         request = QgsFeatureRequest().setFilterExpression(self.mFieldExpressionWidget.asExpression())
         request.setFlags(QgsFeatureRequest.NoGeometry)
         featIdList = [i.id() for i in currentLayer.getFeatures(request)]
     #sort is faster than sorted (but sort is just available for lists)
     featIdList.sort()
     return featIdList
 def removeLastFeatures(self):
   self.isIniEditable = self.layerPolygon.isEditable()
   if not self.isIniEditable:
     self.layerPolygon.startEditing()
   exp = QgsExpression( '"id_add" = {}'.format( self.getMaximumValueAdd() ) )
   request = QgsFeatureRequest( exp )
   request.setFlags( QgsFeatureRequest.NoGeometry )
   it = self.layerPolygon.getFeatures( request )
   for feat in it:
     self.layerPolygon.deleteFeature( feat.id()  )
   self.layerPolygon.commitChanges()
   if self.isIniEditable:
     self.layerPolygon.startEditing()
   self.layerPolygon.updateExtents()
   self.dockWidgetGui.btnRemoveLastFeatures.setEnabled( False )
Example #14
0
    def draw (self, resetStyle = False):
        xField = self.xProperty.currentText()
        yField = self.yProperty.currentText()
        
        #X-Axis limits
        self.xMinTime.setVisible (xField == 'Time')
        self.xMaxTime.setVisible (xField == 'Time')
        self.xMinFloat.setVisible(xField != 'Time')
        self.xMaxFloat.setVisible(xField != 'Time')
        self.xMin = self.xMinFloat if xField != 'Time' else self.xMinTime
        self.xMax = self.xMaxFloat if xField != 'Time' else self.xMaxTime
        #Y-Axis limits
        self.yMinTime.setVisible (yField == 'Time')
        self.yMaxTime.setVisible (yField == 'Time')
        self.yMinFloat.setVisible(yField != 'Time')
        self.yMaxFloat.setVisible(yField != 'Time')
        self.yMin = self.yMinFloat if yField != 'Time' else self.yMinTime
        self.yMax = self.yMaxFloat if yField != 'Time' else self.yMaxTime 
        
        ySorted = self.ySorted.isChecked()
        
        self.plotWidget.clean (xField = xField,
                               yField = yField,
                               resetStyle = resetStyle)

        request = QgsFeatureRequest ()
        request.setFlags(QgsFeatureRequest.NoGeometry)
                
        for foi in self.foiList:
            request.setFilterExpression("foi = '%s'" % foi)
            if ySorted:
                dataSerie = [(f.attribute(yField),f.attribute(xField)) for f in self.layer.getFeatures(request)]
            else:
                dataSerie = [(f.attribute(xField),f.attribute(yField)) for f in self.layer.getFeatures(request)]
            dataSerie.sort()
            
            if len (dataSerie):
                if ySorted:
                    y,x = map(list, zip(*dataSerie))
                else:
                    x,y = map(list, zip(*dataSerie))
                self.plotWidget.plot(x,y, f.attribute('name'))
                self.plotWidget.applyTimeFormat (self.timeFormat.text())
                self.plotWidget.draw ()
        
        styles = self.StylesTable (self.plotWidget.ax.get_lines())
        self.stylesTable.setModel (styles)
        styles.dataChanged.connect(self.plotWidget.draw)
Example #15
0
def is25d(layer, canvas, restrictToExtent, extent):
    if layer.type() != layer.VectorLayer:
        return False
    if layer.geometryType() != QgsWkbTypes.PolygonGeometry:
        return False
    vts = layer.customProperty("VectorTilesReader/vector_tile_source")
    if vts is not None:
        return False
    renderer = layer.renderer()
    if isinstance(renderer, Qgs25DRenderer):
        return True
    symbols = []
    if isinstance(renderer, QgsCategorizedSymbolRenderer):
        categories = renderer.categories()
        for category in categories:
            symbols.append(category.symbol())
    elif isinstance(renderer, QgsGraduatedSymbolRenderer):
        ranges = renderer.ranges()
        for range in ranges:
            symbols.append(range.symbol())
    elif isinstance(renderer, QgsRuleBasedRenderer):
        rules = renderer.rootRule().children()
        for rule in rules:
            symbols.append(rule.symbol())
    else:
        renderContext = QgsRenderContext.fromMapSettings(canvas.mapSettings())
        fields = layer.fields()
        if restrictToExtent and extent == "Canvas extent":
            request = QgsFeatureRequest(canvas.extent())
            request.setFlags(QgsFeatureRequest.ExactIntersect)
            features = layer.getFeatures(request)
        else:
            features = layer.getFeatures()
        renderer.startRender(renderContext, fields)
        for feature in features:
            symbol = renderer.symbolForFeature(feature, renderContext)
            symbols.append(symbol)
        renderer.stopRender(renderContext)
    for sym in symbols:
        sl1 = sym.symbolLayer(1)
        sl2 = sym.symbolLayer(2)
        if (isinstance(sl1, QgsGeometryGeneratorSymbolLayer) and
                isinstance(sl2, QgsGeometryGeneratorSymbolLayer)):
            return True
    return False
Example #16
0
def layerData(layer, request={}, offset=0):
    first = True
    data = {}
    fields = []
    fieldTypes = []
    fr = QgsFeatureRequest()
    if request:
        if 'exact' in request and request['exact']:
            fr.setFlags(QgsFeatureRequest.ExactIntersect)
        if 'nogeom' in request and request['nogeom']:
            fr.setFlags(QgsFeatureRequest.NoGeometry)
        if 'fid' in request:
            fr.setFilterFid(request['fid'])
        elif 'extents' in request:
            fr.setFilterRect(QgsRectangle(*request['extents']))
        if 'attributes' in request:
            fr.setSubsetOfAttributes(request['attributes'])

    for f in layer.getFeatures(fr):
        if first:
            first = False
            for field in f.fields():
                fields.append(str(field.name()))
                fieldTypes.append(str(field.typeName()))
        fielddata = dict((name, unicode(f[name])) for name in fields)
        g = f.geometry()
        if g:
            fielddata[geomkey] = str(g.exportToWkt())
        else:
            fielddata[geomkey] = "None"

        fielddata[fidkey] = f.id()
        id = fielddata[fields[0]]
        description = fielddata[fields[1]]
        fielddata['id'] = id
        fielddata['description'] = description
        data[f.id() + offset] = fielddata
    if 'id' not in fields:
        fields.insert(0, 'id')
    if 'description' not in fields:
        fields.insert(1, 'description')
    fields.append(fidkey)
    fields.append(geomkey)
    return fields, fieldTypes, data
Example #17
0
    def findNearestPointL(self, searchPt, lineLayer, tolerance):
        # given a point, find the nearest point (within the tolerance) within the line layer
        # returns QgsPoint
        QgsMessageLog.logMessage("In findNearestPointL. Checking lineLayer: " +
                                 lineLayer.name() + "; " + searchPt.asWkt(),
                                 tag="TOMs panel")
        searchRect = QgsRectangle(searchPt.x() - tolerance,
                                  searchPt.y() - tolerance,
                                  searchPt.x() + tolerance,
                                  searchPt.y() + tolerance)

        request = QgsFeatureRequest()
        request.setFilterRect(searchRect)
        request.setFlags(QgsFeatureRequest.ExactIntersect)

        shortestDistance = float("inf")
        #nearestPoint = QgsFeature()

        # Loop through all features in the layer to find the closest feature
        for f in lineLayer.getFeatures(request):
            # Add any features that are found should be added to a list

            closestPtOnFeature = f.geometry().nearestPoint(
                QgsGeometry.fromPointXY(searchPt))
            dist = f.geometry().distance(QgsGeometry.fromPointXY(searchPt))
            if dist < shortestDistance:
                shortestDistance = dist
                closestPoint = closestPtOnFeature

        QgsMessageLog.logMessage("In findNearestPointL: shortestDistance: " +
                                 str(shortestDistance),
                                 tag="TOMs panel")

        del request
        del searchRect

        if shortestDistance < float("inf"):
            #nearestPoint = QgsFeature()
            # add the geometry to the feature,
            #nearestPoint.setGeometry(QgsGeometry(closestPtOnFeature))
            #QgsMessageLog.logMessage("findNearestPointL: nearestPoint geom type: " + str(nearestPoint.wkbType()), tag="TOMs panel")
            return closestPoint  # returns a geometry
        else:
            return None
    def assign_district(self, target_ids, new_district):
        """
        Queue up changes
        :param target_ids:
        :param new_district:
        :return:
        """
        staged_log_entries = []
        # first, record the previous districts, before they get changed by the super method
        request = QgsFeatureRequest().setFilterFids(target_ids)
        request.setFlags(QgsFeatureRequest.NoGeometry)
        request.setSubsetOfAttributes(
            [self.target_layer.fields().lookupField(self.target_field), self.meshblock_number_idx])
        for f in self.target_layer.getFeatures(request):
            district = f[self.target_field]
            if district == NULL:
                continue

            if district == new_district:
                target_ids = [t for t in target_ids if t != f.id()]
                continue

            meshblock_number = f[self.meshblock_number_idx]

            if district not in self.pending_affected_districts:
                self.pending_affected_districts[district] = {'ADD': [], 'REMOVE': []}
            self.pending_affected_districts[district]['REMOVE'].append(f.id())

            staged_log_entries.append(self.create_log_entry(meshblock_number=meshblock_number, old_district=district,
                                                            new_district=new_district))

        if not super().assign_district(target_ids, new_district):
            return False

        self.pending_log_entries.extend(staged_log_entries)

        # if assign was successful, then record all districts affected by this operation
        # (that includes the new district and all old districts)

        if new_district not in self.pending_affected_districts:
            self.pending_affected_districts[new_district] = {'ADD': [], 'REMOVE': []}

        self.pending_affected_districts[new_district]['ADD'].extend(target_ids)
        return True
def layerData(layer, request={}, offset=0):
    first = True
    data = {}
    fields = []
    fieldTypes = []
    fr = QgsFeatureRequest()
    if request:
        if 'exact' in request and request['exact']:
            fr.setFlags(QgsFeatureRequest.ExactIntersect)
        if 'nogeom' in request and request['nogeom']:
            fr.setFlags(QgsFeatureRequest.NoGeometry)
        if 'fid' in request:
            fr.setFilterFid(request['fid'])
        elif 'extents' in request:
            fr.setFilterRect(QgsRectangle(*request['extents']))
        if 'attributes' in request:
            fr.setSubsetOfAttributes(request['attributes'])

    for f in layer.getFeatures(fr):
        if first:
            first = False
            for field in f.fields():
                fields.append(str(field.name()))
                fieldTypes.append(str(field.typeName()))
        fielddata = dict((name, unicode(f[name])) for name in fields)
        g = f.geometry()
        if g:
            fielddata[geomkey] = str(g.exportToWkt())
        else:
            fielddata[geomkey] = "None"

        fielddata[fidkey] = f.id()
        id = fielddata[fields[0]]
        description = fielddata[fields[1]]
        fielddata['id'] = id
        fielddata['description'] = description
        data[f.id() + offset] = fielddata
    if 'id' not in fields:
        fields.insert(0, 'id')
    if 'description' not in fields:
        fields.insert(1, 'description')
    fields.append(fidkey)
    fields.append(geomkey)
    return fields, fieldTypes, data
Example #20
0
    def findNearestPointOnLink(self, pos):
        #  def findFeatureAt(self, pos, excludeFeature=None):
        # http://www.lutraconsulting.co.uk/blog/2014/10/17/getting-started-writing-qgis-python-plugins/ - generates "closest feature" function
        """ Find the feature close to the given position.

            'pos' is the position to check, in canvas coordinates.

            if 'excludeFeature' is specified, we ignore this feature when
            finding the clicked-on feature.

            If no feature is close to the given coordinate, we return None.
        """
        mapPt = self.transformCoordinates(pos)

        tolerance = 1.0
        searchRect = QgsRectangle(mapPt.x() - tolerance,
                                  mapPt.y() - tolerance,
                                  mapPt.x() + tolerance,
                                  mapPt.y() + tolerance)

        request = QgsFeatureRequest()
        request.setFilterRect(searchRect)
        request.setFlags(QgsFeatureRequest.ExactIntersect)

        shortestDistance = float("inf")
        closestFeature = None

        # Loop through all features in the layer to find the closest feature
        for f in self.linkLayer.getFeatures(request):

            dist = f.geometry().distance(QgsGeometry.fromPointXY(mapPt))
            if dist < shortestDistance:
                shortestDistance = dist
                closestFeature = f

        if closestFeature:
            # Find distance from start of feature
            distanceAlongLink = closestFeature.geometry().lineLocatePoint(
                QgsGeometry.fromPointXY(mapPt))
            nearestPt = closestFeature.geometry().nearestPoint(
                QgsGeometry.fromPointXY(mapPt))
            return nearestPt, closestFeature, distanceAlongLink

        return mapPt, None, None
Example #21
0
 def findPointFeatures(self, aNodeLayer, topoPoints):
     '''
     identifies a list of point features on aNodeLayer by a list of ids
     '''
     # save start time
     start = time.time()
     
     qFeaturesList = {}
     qNodeFeaturesList = []
     
     for aTopoPoint in topoPoints:
         pointLayerName = aTopoPoint.getTableName()
         pointLayer = None
         start_layer = time.time()
         for aLayer in QgsMapLayerRegistry.instance().mapLayers().values():
             if aLayer.shortName() == pointLayerName:
                 pointLayer = aLayer
                 if pointLayerName not in qFeaturesList:
                     qFeaturesList[pointLayerName] = []
                 break
         # DEBUG:
         #self.showTimeMessage('findPointFeatures - Point Layer', time.time() - start_layer)
         
         if pointLayer:
             start_point = time.time() 
             primaryKey = self.topologyConnector.primaryKey(aTopoPoint.getFullTableName())
             request = QgsFeatureRequest().setFilterExpression(primaryKey + ' = ' + str(aTopoPoint.getSystemId())) #u'"system_id" = '
             request.setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(['id'], pointLayer.fields())
             for aQsFeature in pointLayer.getFeatures( request ):
                 if aQsFeature.id() not in qFeaturesList[pointLayerName]:
                     qFeaturesList[pointLayerName].append(aQsFeature.id())
             # DEBUG
             #self.showTimeMessage('findPointFeatures - Point Feature', time.time() - start_point) 
         if aNodeLayer:
             start_node = time.time() 
             request = QgsFeatureRequest().setFilterExpression(u'"node_id" = ' + str(aTopoPoint.getNodeId()))
             request.setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes(['id'], aNodeLayer.fields())
             for aQsNodeFeature in aNodeLayer.getFeatures( request ):
                 qNodeFeaturesList.append(aQsNodeFeature.id())
             #DEBUG
             #self.showTimeMessage('findPointFeatures - Node Feature', time.time() - start_node) 
         
     self.showTimeMessage('findPointFeatures', time.time() - start)        
     return {'pointFeaturesList': qFeaturesList, 'nodeFeaturesList': qNodeFeaturesList}
    def district_list(self):
        """
        Returns a complete list of districts available for redistricting to
        """
        field_index = self.source_layer.fields().lookupField(self.source_field)

        request = QgsFeatureRequest()
        self.modify_district_request(request)
        request.setFlags(QgsFeatureRequest.NoGeometry)
        request.setSubsetOfAttributes([field_index])

        districts = [f[field_index]
                     for f in self.source_layer.getFeatures(request)
                     if f[field_index] != NULL]
        # we want an ordered list of unique values!
        d = OrderedDict()
        for x in districts:
            d[x] = True
        return list(d.keys())
Example #23
0
 def getFeatIdList(self, currentLayer):
     #getting all features ids
     if self.mFieldExpressionWidget.currentText() == '':
         featIdList = currentLayer.allFeatureIds()
     elif not self.mFieldExpressionWidget.isValidExpression():
         self.iface.messageBar().pushMessage(
             self.tr('Warning!'),
             self.tr('Invalid attribute filter!'),
             level=QgsMessageBar.WARNING,
             duration=2)
         return []
     else:
         request = QgsFeatureRequest().setFilterExpression(
             self.mFieldExpressionWidget.asExpression())
         request.setFlags(QgsFeatureRequest.NoGeometry)
         featIdList = [i.id() for i in currentLayer.getFeatures(request)]
     #sort is faster than sorted (but sort is just available for lists)
     featIdList.sort()
     return featIdList
Example #24
0
def is25d(layer, canvas, restrictToExtent, extent):
    if layer.geometryType() != QGis.Polygon:
        return False
    try:
        renderer = layer.rendererV2()
        if isinstance(renderer, Qgs25DRenderer):
            return True
        symbols = []
        if isinstance(renderer, QgsCategorizedSymbolRendererV2):
            categories = renderer.categories()
            for category in categories:
                symbols.append(category.symbol())
        elif isinstance(renderer, QgsGraduatedSymbolRendererV2):
            ranges = renderer.ranges()
            for range in ranges:
                symbols.append(range.symbol())
        else:
            renderContext = QgsRenderContext.fromMapSettings(
                canvas.mapSettings())
            fields = layer.pendingFields()
            if restrictToExtent and extent == "Canvas extent":
                request = QgsFeatureRequest(iface.mapCanvas().extent())
                request.setFlags(QgsFeatureRequest.ExactIntersect)
                features = layer.getFeatures(request)
            else:
                features = layer.getFeatures()
            renderer.startRender(renderContext, fields)
            for feature in features:
                symbol = renderer.symbolForFeature2(feature, renderContext)
                symbols.append(symbol)
            renderer.stopRender(renderContext)
        for sym in symbols:
            sl1 = sym.symbolLayer(1)
            sl2 = sym.symbolLayer(2)
            if (isinstance(sl1, QgsGeometryGeneratorSymbolLayerV2)
                    and isinstance(sl2, QgsGeometryGeneratorSymbolLayerV2)):
                return True
        return False
    except:
        QgsMessageLog.logMessage(traceback.format_exc(),
                                 "qgis2web",
                                 level=QgsMessageLog.CRITICAL)
        return False
Example #25
0
    def update_stats_nz_values(self, electorate_id, results: dict):
        """
        Updates an electorate's stored Statistics NZ api calculations
        :param electorate_id: electorate to update
        :param results: results dictionary from stats API
        """

        # get feature ID
        request = QgsFeatureRequest()
        request.setFilterExpression(QgsExpression.createFieldEqualityExpression(self.source_field, electorate_id))
        request.setFlags(QgsFeatureRequest.NoGeometry)
        request.setSubsetOfAttributes([])
        request.setLimit(1)
        f = next(self.source_layer.getFeatures(request))

        self.source_layer.dataProvider().changeAttributeValues(
            {f.id(): {self.stats_nz_pop_field_index: results['currentPopulation'],
                      self.stats_nz_var_20_field_index: results['varianceYear1'],
                      self.stats_nz_var_23_field_index: results['varianceYear2']}})
Example #26
0
    def get_features_by_expression(layer,
                                   expression=None,
                                   with_attributes=False,
                                   with_geometry=False):
        # TODO: It should be possible to pass a list of attributes to retrieve

        if expression is None:
            request = QgsFeatureRequest()
        else:
            request = QgsFeatureRequest(expression)

        if not with_geometry:
            request.setFlags(QgsFeatureRequest.NoGeometry)
        if not with_attributes:
            field_idx = layer.fields().indexFromName(ID_FIELD)
            request.setSubsetOfAttributes([field_idx
                                           ])  # Note: this adds a new flag

        return [feature for feature in layer.getFeatures(request)]
Example #27
0
 def findFeaturesAt(mapPoint, layerConfig, mapTool, closest=False):
     """
     To find features from a given position in a given layer
     :param mapPoint: the map position
     :param layerConfig: the layer in which we are looking for features
     :param mapTool: a QgsMapTool instance
     :param closest: if we only want the one closest feature
     :return: features found in layer
     """
     if layerConfig is None:
         return None
     tolerance = layerConfig.tolerance
     if layerConfig.unit == QgsTolerance.Pixels:
         layTolerance = Finder.calcCanvasTolerance(mapTool.toCanvasCoordinates(mapPoint), layerConfig.layer, mapTool,
                                                   tolerance)
     elif layerConfig.unit == QgsTolerance.ProjectUnits:
         layTolerance = Finder.calcMapTolerance(mapPoint, layerConfig.layer, mapTool, tolerance)
     else:
         layTolerance = tolerance
     layPoint = mapTool.toLayerCoordinates(layerConfig.layer, mapPoint)
     searchRect = QgsRectangle(layPoint.x() - layTolerance, layPoint.y() - layTolerance,
                               layPoint.x() + layTolerance, layPoint.y() + layTolerance)
     request = QgsFeatureRequest()
     request.setFilterRect(searchRect)
     request.setFlags(QgsFeatureRequest.ExactIntersect)
     if closest:
         for feature in layerConfig.layer.getFeatures(request):
             if layerConfig.layer.geometryType() == QGis.Polygon:
                 dist, nearest, vertex = feature.geometry().closestSegmentWithContext(mapPoint)
                 if not QgsGeometry.fromPoint(nearest).intersects(searchRect):
                     return None
             return QgsFeature(feature)
     else:
         features = []
         for feature in layerConfig.layer.getFeatures(request):
             if layerConfig.layer.geometryType() == QGis.Polygon:
                 dist, nearest, vertex = feature.geometry().closestSegmentWithContext(mapPoint)
                 if QgsGeometry.fromPoint(nearest).intersects(searchRect):
                     features.append(QgsFeature(feature))
             else:
                 features.append(QgsFeature(feature))
         return features
    def fill_table(self):
        self.tbl_changes_parcels.clearContents()
        self.tbl_changes_parcels.setRowCount(len(self.parcels_t_ids))

        request = QgsFeatureRequest(QgsExpression('"{}" in ({})'.format(self.utils._db.names.T_ID_F, ",".join(str(t_id) for t_id in self.parcels_t_ids))))
        request.setFlags(QgsFeatureRequest.NoGeometry)
        request.setSubsetOfAttributes([self.utils._db.names.T_ID_F,
                                       self.utils._db.names.LC_PARCEL_T_NUPRE_F,
                                       self.utils._db.names.LC_PARCEL_T_FMI_F,
                                       self.utils._db.names.LC_PARCEL_T_PARCEL_NUMBER_F],
                                      self.utils._layers[self.utils._db.names.LC_PARCEL_T].fields())  # NOTE: this adds a new flag
        parcels = self.utils._layers[self.utils._db.names.LC_PARCEL_T].getFeatures(request)

        if parcels:
            row = 0
            for parcel in parcels:
                self.tbl_changes_parcels.setItem(row, 0, QTableWidgetItem(str(parcel[self.utils._db.names.T_ID_F])))
                self.tbl_changes_parcels.setItem(row, 1, QTableWidgetItem(str(parcel[self.utils._db.names.LC_PARCEL_T_NUPRE_F])))
                self.tbl_changes_parcels.setItem(row, 2, QTableWidgetItem(str(parcel[self.utils._db.names.LC_PARCEL_T_FMI_F])))
                self.tbl_changes_parcels.setItem(row, 3, QTableWidgetItem(str(parcel[self.utils._db.names.LC_PARCEL_T_PARCEL_NUMBER_F])))
                row += 1
Example #29
0
    def findNearestLine(self, searchPt, lineLayer, tolerance):
        # given a point, find the nearest point (within the tolerance) within the line layer
        # returns QgsPoint
        QgsMessageLog.logMessage("In findNearestLine - lineLayer",
                                 tag="TOMs panel")
        searchRect = QgsRectangle(searchPt.x() - tolerance,
                                  searchPt.y() - tolerance,
                                  searchPt.x() + tolerance,
                                  searchPt.y() + tolerance)

        request = QgsFeatureRequest()
        request.setFilterRect(searchRect)
        request.setFlags(QgsFeatureRequest.ExactIntersect)

        shortestDistance = float("inf")

        # Loop through all features in the layer to find the closest feature
        for f in lineLayer.getFeatures(request):
            # Add any features that are found should be added to a list

            # closestPtOnFeature = f.geometry().nearestPoint(QgsGeometry.fromPoint(searchPt))
            dist = f.geometry().distance(QgsGeometry.fromPointXY(searchPt))
            if dist < shortestDistance:
                shortestDistance = dist
                closestLine = f

        QgsMessageLog.logMessage("In findNearestLine: shortestDistance: " +
                                 str(shortestDistance),
                                 tag="TOMs panel")

        del request
        del searchRect

        if shortestDistance < float("inf"):
            """QgsMessageLog.logMessage("In findNearestLine: closestLine {}".format(closestLine.exportToWkt()),
                                     tag="TOMs panel")"""

            return closestLine  # returns a geometry
        else:
            return None
Example #30
0
    def __select(self):
        """
        To select objects in multiples layers inside a selection rectangle
        """
        searchRect = QgsRectangle(self.first, self.last)
        for layer in self.canvas().layers():
            if not self.identified or layer.id() not in self.disabled():
                if layer.type(
                ) == QgsMapLayer.VectorLayer and layer.geometryType(
                ) in self.types:
                    renderer = layer.rendererV2()
                    context = QgsRenderContext()
                    if renderer:
                        renderer.startRender(context, layer.pendingFields())
                        request = QgsFeatureRequest()
                        request.setFilterRect(searchRect)
                        request.setFlags(QgsFeatureRequest.ExactIntersect)
                        fIds = []
                        for feature in layer.getFeatures(request):
                            try:
                                will = renderer.willRenderFeature(
                                    feature, context)
                            except:
                                try:
                                    will = renderer.willRenderFeature(feature)
                                except:
                                    self.__iface.messageBar().pushMessage(
                                        QCoreApplication.translate(
                                            "VDLTools", "Error"),
                                        "will renderer still not working",
                                        level=QgsMessageBar.CRITICAL,
                                        duration=0)
                                    return
                            if will:
                                fIds.append(feature.id())
                        renderer.stopRender(context)
                        layer.selectByIds(fIds)

        self.selectedSignal.emit()
    def redraw(self, handler):
        """
        Forces a redraw of the cached image
        """
        self.image = None

        if not self.original_populations:
            # first run, get initial estimates
            request = QgsFeatureRequest()
            request.setFilterExpression(QgsExpression.createFieldEqualityExpression('type', self.task))
            request.setFlags(QgsFeatureRequest.NoGeometry)
            for f in self.electorate_layer.getFeatures(request):
                estimated_pop = f.attribute(handler.stats_nz_pop_field_index)
                if estimated_pop is None or estimated_pop == NULL:
                    # otherwise just use existing estimated pop as starting point
                    estimated_pop = f.attribute(handler.estimated_pop_idx)
                self.original_populations[f.id()] = estimated_pop

        # step 1: get all electorate features corresponding to affected electorates
        electorate_features = {f[handler.electorate_layer_field]: f for f in
                               handler.get_affected_districts(
                                   [handler.electorate_layer_field, handler.stats_nz_pop_field, 'estimated_pop'],
                                   needs_geometry=False)}

        self.new_populations = {}

        for district in handler.pending_affected_districts.keys():  # pylint: disable=consider-iterating-dictionary
            # use stats nz pop as initial estimate, if available
            estimated_pop = electorate_features[district].attribute(handler.stats_nz_pop_field_index)
            if estimated_pop is None or estimated_pop == NULL:
                # otherwise just use existing estimated pop as starting point
                estimated_pop = electorate_features[district].attribute(handler.estimated_pop_idx)
            # add new bits
            estimated_pop = handler.grow_population_with_added_meshblocks(district, estimated_pop)
            # minus lost bits
            estimated_pop = handler.shrink_population_by_removed_meshblocks(district, estimated_pop)

            self.new_populations[electorate_features[district].id()] = estimated_pop
    def _filter_plots_ids_from_expresion(db, parcel_layer, ue_baunit_layer,
                                         expression_select_parcels):
        """
        Get plots ids depending of a defined filter expression
        """
        # Only required field in parcel layer
        fields_idx = list()
        for field in [
                db.names.T_ID_F, db.names.LC_PARCEL_T_FMI_F,
                db.names.LC_PARCEL_T_PARCEL_NUMBER_F,
                db.names.LC_PARCEL_T_PREVIOUS_PARCEL_NUMBER_F
        ]:
            fields_idx.append(parcel_layer.fields().indexFromName(field))

        request = QgsFeatureRequest(expression_select_parcels)
        request.setFlags(QgsFeatureRequest.NoGeometry)
        request.setSubsetOfAttributes(fields_idx)  # Note: this adds a new flag
        parcels = parcel_layer.getFeatures(request)
        parcel_ids = [
            select_parcel[db.names.T_ID_F] for select_parcel in parcels
        ]

        # Only required field in ue baunit layer
        field_idx = parcel_layer.fields().indexFromName(
            db.names.COL_UE_BAUNIT_T_LC_PLOT_F)
        expression_select_plots = QgsExpression(
            '{} in ({}) AND {} IS NOT NULL'.format(
                db.names.COL_UE_BAUNIT_T_PARCEL_F,
                ','.join([str(parcel_id) for parcel_id in parcel_ids]),
                db.names.COL_UE_BAUNIT_T_LC_PLOT_F))
        request = QgsFeatureRequest(expression_select_plots)
        request.setFlags(QgsFeatureRequest.NoGeometry)
        request.setSubsetOfAttributes([field_idx
                                       ])  # Note: this adds a new flag
        plots = ue_baunit_layer.getFeatures(request)

        return [plot[db.names.COL_UE_BAUNIT_T_LC_PLOT_F]
                for plot in plots]  # Plot ids
    def read(self, feature_type, bbox = None, attributes = None, geometry=True, feature_filter=None):
        if not isinstance(feature_type, FeatureType):
            raise TypeError()
        lyr = self._connectlayer(feature_type)

        request = None
        if bbox or attributes is not None or not geometry or feature_filter:
            request = QgsFeatureRequest()
            if bbox:
                rect = QgsRectangle(*bbox)
                request.setFilterRect(rect)
            if attributes:
                request.setSubsetOfAttributes(attributes, lyr.pendingFields())
            if not geometry:
                request.setFlags(QgsFeatureRequest.NoGeometry)
            if feature_filter:
                request.setFilterExpression(feature_filter)
                #lyr.setSubsetString(feature_filter)

        # return listoffeatures
        # filter is maybe a QgsFeatureRequest
        # http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/vector.html#iterating-over-a-subset-of-features
        return list(lyr.getFeatures(request) if request else lyr.getFeatures())
def _add_default_exposure_class(layer):
    """The layer doesn't have an exposure class, we need to add it.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer
    """
    layer.startEditing()

    field = create_field_from_definition(exposure_class_field)
    layer.keywords['inasafe_fields'][exposure_class_field['key']] = (
        exposure_class_field['field_name'])
    layer.addAttribute(field)

    index = layer.fieldNameIndex(exposure_class_field['field_name'])

    exposure = layer.keywords['exposure']

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for feature in layer.getFeatures(request):
        layer.changeAttributeValue(feature.id(), index, exposure)

    layer.commitChanges()
    return
Example #35
0
def _add_default_exposure_class(layer):
    """The layer doesn't have an exposure class, we need to add it.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer
    """
    layer.startEditing()

    field = create_field_from_definition(exposure_class_field)
    layer.keywords['inasafe_fields'][exposure_class_field['key']] = (
        exposure_class_field['field_name'])
    layer.addAttribute(field)

    index = layer.fieldNameIndex(exposure_class_field['field_name'])

    exposure = layer.keywords['exposure']

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for feature in layer.getFeatures(request):
        layer.changeAttributeValue(feature.id(), index, exposure)

    layer.commitChanges()
    return
Example #36
0
def zonal_stats(raster, vector):
    """Reclassify a continuous raster layer.

    Issue https://github.com/inasafe/inasafe/issues/3190

    The algorithm will take care about projections.
    We don't want to reproject the raster layer.
    So if CRS are different, we reproject the vector layer and then we do a
    lookup from the reprojected layer to the original vector layer.

    :param raster: The raster layer.
    :type raster: QgsRasterLayer

    :param vector: The vector layer.
    :type vector: QgsVectorLayer

    :return: The output of the zonal stats.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = zonal_stats_steps['output_layer_name']

    exposure = raster.keywords['exposure']
    if raster.crs().authid() != vector.crs().authid():
        layer = reproject(vector, raster.crs())

        # We prepare the copy
        output_layer = create_memory_layer(
            output_layer_name,
            vector.geometryType(),
            vector.crs(),
            vector.fields()
        )
        copy_layer(vector, output_layer)
    else:
        layer = create_memory_layer(
            output_layer_name,
            vector.geometryType(),
            vector.crs(),
            vector.fields()
        )
        copy_layer(vector, layer)

    input_band = layer.keywords.get('active_band', 1)
    analysis = QgsZonalStatistics(
        layer,
        raster,
        'exposure_',
        input_band,
        QgsZonalStatistics.Sum)
    result = analysis.calculateStatistics(None)
    LOGGER.debug(tr('Zonal stats on %s : %s' % (raster.source(), result)))

    output_field = exposure_count_field['field_name'] % exposure
    if raster.crs().authid() != vector.crs().authid():
        output_layer.startEditing()
        field = create_field_from_definition(
            exposure_count_field, exposure)

        output_layer.addAttribute(field)
        new_index = output_layer.fields().lookupField(field.name())
        old_index = layer.fields().lookupField('exposure_sum')
        for feature_input, feature_output in zip(
                layer.getFeatures(), output_layer.getFeatures()):
            output_layer.changeAttributeValue(
                feature_input.id(), new_index, feature_input[old_index])
        output_layer.commitChanges()
        layer = output_layer
    else:
        fields_to_rename = {
            'exposure_sum': output_field
        }
        if qgis_version() >= 21600:
            rename_fields(layer, fields_to_rename)
        else:
            copy_fields(layer, fields_to_rename)
            remove_fields(layer, list(fields_to_rename.keys()))
        layer.commitChanges()

    # The zonal stats is producing some None values. We need to fill these
    # with 0. See issue : #3778
    # We should start a new editing session as previous fields need to be
    # committed first.
    layer.startEditing()
    request = QgsFeatureRequest()
    expression = '\"%s\" is None' % output_field
    request.setFilterExpression(expression)
    request.setFlags(QgsFeatureRequest.NoGeometry)
    index = layer.fields().lookupField(output_field)
    for feature in layer.getFeatures():
        if feature[output_field] is None:
            layer.changeAttributeValue(feature.id(), index, 0)
    layer.commitChanges()

    layer.keywords = raster.keywords.copy()
    layer.keywords['inasafe_fields'] = vector.keywords['inasafe_fields'].copy()
    layer.keywords['inasafe_default_values'] = (
        raster.keywords['inasafe_default_values'].copy())

    key = exposure_count_field['key'] % raster.keywords['exposure']

    # Special case here, one field is the exposure count and the total.
    layer.keywords['inasafe_fields'][key] = output_field
    layer.keywords['inasafe_fields'][total_field['key']] = output_field

    layer.keywords['exposure_keywords'] = raster.keywords.copy()
    layer.keywords['hazard_keywords'] = vector.keywords[
        'hazard_keywords'].copy()
    layer.keywords['aggregation_keywords'] = (
        vector.keywords['aggregation_keywords'])
    layer.keywords['layer_purpose'] = (
        layer_purpose_aggregate_hazard_impacted['key'])

    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Example #37
0
    def run(self):
        """Run any post processors requested by the impact function.
        """
        try:
            requested_postprocessors = self.function_parameters[
                'postprocessors']
            postprocessors = get_postprocessors(
                requested_postprocessors, self.aggregator.aoi_mode)
        except (TypeError, KeyError):
            # TypeError is for when function_parameters is none
            # KeyError is for when ['postprocessors'] is unavailable
            postprocessors = {}
        LOGGER.debug('Running this postprocessors: ' + str(postprocessors))

        feature_names_attribute = self.aggregator.attributes[
            self.aggregator.get_default_keyword('AGGR_ATTR_KEY')]
        if feature_names_attribute is None:
            self.attribute_title = self.tr('Aggregation unit')
        else:
            self.attribute_title = feature_names_attribute

        name_filed_index = self.aggregator.layer.fieldNameIndex(
            self.attribute_title)
        sum_field_index = self.aggregator.layer.fieldNameIndex(
            self._sum_field_name())

        user_defined_female_ratio = False
        female_ratio_field_index = None
        female_ratio = None
        user_defined_age_ratios = False
        youth_ratio_field_index = None
        youth_ratio = None
        adult_ratio_field_index = None
        adult_ratio = None
        elderly_ratio_field_index = None
        elderly_ratio = None

        if 'Gender' in postprocessors:
            # look if we need to look for a variable female ratio in a layer
            try:
                female_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'FEMALE_RATIO_ATTR_KEY')]
                female_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(female_ratio_field)

                # something went wrong finding the female ratio field,
                # use defaults from below except block
                if female_ratio_field_index == -1:
                    raise KeyError

                user_defined_female_ratio = True

            except KeyError:
                try:
                    female_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'FEMALE_RATIO_KEY'))
                except KeywordNotFoundError:
                    female_ratio = \
                        self.aggregator.get_default_keyword('FEMALE_RATIO')

        if 'Age' in postprocessors:
            # look if we need to look for a variable age ratio in a layer
            try:
                youth_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'YOUTH_RATIO_ATTR_KEY')]
                youth_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(youth_ratio_field)
                adult_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'ADULT_RATIO_ATTR_KEY')]
                adult_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(adult_ratio_field)
                elderly_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'ELDERLY_RATIO_ATTR_KEY')]
                elderly_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(elderly_ratio_field)
                # something went wrong finding the youth ratio field,
                # use defaults from below except block
                if (youth_ratio_field_index == -1 or
                        adult_ratio_field_index == -1 or
                        elderly_ratio_field_index == -1):
                    raise KeyError

                user_defined_age_ratios = True

            except KeyError:
                try:
                    youth_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'YOUTH_RATIO_KEY'))
                    adult_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'ADULT_RATIO_KEY'))
                    elderly_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'ELDERLY_RATIO_KEY'))

                except KeywordNotFoundError:
                    youth_ratio = \
                        self.aggregator.get_default_keyword('YOUTH_RATIO')
                    adult_ratio = \
                        self.aggregator.get_default_keyword('ADULT_RATIO')
                    elderly_ratio = \
                        self.aggregator.get_default_keyword('ELDERLY_RATIO')

        if 'BuildingType' or 'RoadType' in postprocessors:
            try:
                key_attribute = self.keyword_io.read_keywords(
                    self.aggregator.exposure_layer, 'key_attribute')
            except KeywordNotFoundError:
                # use 'type' as default
                key_attribute = 'type'

        # iterate zone features
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.NoGeometry)
        provider = self.aggregator.layer.dataProvider()
        # start data retrieval: fetch no geometry and all attributes for each
        # feature
        polygon_index = 0
        for feature in provider.getFeatures(request):
            # if a feature has no field called
            if name_filed_index == -1:
                zone_name = str(feature.id())
            else:
                zone_name = feature[name_filed_index]

            # create dictionary of attributes to pass to postprocessor
            general_params = {
                'target_field': self.aggregator.target_field,
                'function_params': self.function_parameters}

            if self.aggregator.statistics_type == 'class_count':
                general_params['impact_classes'] = (
                    self.aggregator.statistics_classes)
            elif self.aggregator.statistics_type == 'sum':
                impact_total = feature[sum_field_index]
                general_params['impact_total'] = impact_total

            try:
                general_params['impact_attrs'] = (
                    self.aggregator.impact_layer_attributes[polygon_index])
            except IndexError:
                # rasters and attributeless vectors have no attributes
                general_params['impact_attrs'] = None

            for key, value in postprocessors.iteritems():
                parameters = general_params
                try:
                    # look if params are available for this postprocessor
                    parameters.update(
                        self.function_parameters[
                            'postprocessors'][key]['params'])
                except KeyError:
                    pass

                if key == 'Gender':
                    if user_defined_female_ratio:
                        female_ratio = feature[female_ratio_field_index]
                        if female_ratio is None:
                            female_ratio = self.aggregator.defaults[
                                'FEMALE_RATIO']
                            LOGGER.warning('Data Driven Female ratio '
                                           'incomplete, using defaults for'
                                           ' aggregation unit'
                                           ' %s' % feature.id)

                    parameters['female_ratio'] = female_ratio

                if key == 'Age':
                    if user_defined_age_ratios:
                        youth_ratio = feature[youth_ratio_field_index]
                        adult_ratio = feature[adult_ratio_field_index]
                        elderly_ratio = feature[elderly_ratio_field_index]
                        if (youth_ratio is None or
                                adult_ratio is None or
                                elderly_ratio is None):
                            youth_ratio = self.aggregator.defaults[
                                'YOUTH_RATIO']
                            adult_ratio = self.aggregator.defaults[
                                'ADULT_RATIO']
                            elderly_ratio = self.aggregator.defaults[
                                'ELDERLY_RATIO']
                            LOGGER.warning('Data Driven Age ratios '
                                           'incomplete, using defaults for'
                                           ' aggregation unit'
                                           ' %s' % feature.id)

                    parameters['youth_ratio'] = youth_ratio
                    parameters['adult_ratio'] = adult_ratio
                    parameters['elderly_ratio'] = elderly_ratio

                if key == 'BuildingType' or key == 'RoadType':
                    # TODO: Fix this might be referenced before assignment
                    parameters['key_attribute'] = key_attribute

                try:
                    value.setup(parameters)
                    value.process()
                    results = value.results()
                    value.clear()
                    # LOGGER.debug(results)

                    # this can raise a KeyError
                    self.output[key].append(
                        (zone_name, results))

                except PostProcessorError as e:
                    message = m.Message(
                        m.Heading(self.tr('%s postprocessor problem' % key),
                                  **styles.DETAILS_STYLE),
                        m.Paragraph(self.tr(str(e))))
                    self.error_message = message

                except KeyError:
                    self.output[key] = []
                    # TODO: Fix this might be referenced before assignment
                    self.output[key].append((zone_name, results))
            # increment the index
            polygon_index += 1
Example #38
0
    def processAlgorithm(self, parameters, context, feedback):
        network = self.parameterAsSource(parameters, self.INPUT, context)
        startPoints = self.parameterAsSource(parameters, self.START_POINTS,
                                             context)
        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
        travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST,
                                            context)

        directionFieldName = self.parameterAsString(parameters,
                                                    self.DIRECTION_FIELD,
                                                    context)
        forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD,
                                              context)
        backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD,
                                               context)
        bothValue = self.parameterAsString(parameters, self.VALUE_BOTH,
                                           context)
        defaultDirection = self.parameterAsEnum(parameters,
                                                self.DEFAULT_DIRECTION,
                                                context)
        speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD,
                                                context)
        defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED,
                                              context)
        tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)

        fields = QgsFields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('start', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        directionField = -1
        if directionFieldName:
            directionField = network.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName:
            speedField = network.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(network, directionField,
                                          forwardValue, backwardValue,
                                          bothValue, defaultDirection)

        distUnit = context.project().crs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(
            distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField, defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(network.sourceCrs(), True, tolerance)

        feedback.pushInfo(self.tr('Loading start points...'))
        request = QgsFeatureRequest()
        request.setFlags(request.flags()
                         ^ QgsFeatureRequest.SubsetOfAttributes)
        request.setDestinationCrs(network.sourceCrs())
        features = startPoints.getFeatures(request)
        total = 100.0 / startPoints.featureCount() if startPoints.featureCount(
        ) else 0

        points = []
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            points.append(f.geometry().asPoint())
            feedback.setProgress(int(current * total))

        feedback.pushInfo(self.tr('Building graph...'))
        snappedPoints = director.makeGraph(builder, points, feedback)

        feedback.pushInfo(self.tr('Calculating service areas...'))
        graph = builder.graph()

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, fields,
                                               QgsWkbTypes.MultiPoint,
                                               network.sourceCrs())

        vertices = []
        upperBoundary = []
        lowerBoundary = []
        total = 100.0 / len(snappedPoints) if snappedPoints else 1
        for i, p in enumerate(snappedPoints):
            if feedback.isCanceled():
                break

            idxStart = graph.findVertex(snappedPoints[i])
            origPoint = points[i].toString()

            tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
            for j, v in enumerate(cost):
                if v > travelCost and tree[j] != -1:
                    vertexId = graph.edge(tree[j]).outVertex()
                    if cost[vertexId] <= travelCost:
                        vertices.append(j)

            for j in vertices:
                upperBoundary.append(
                    graph.vertex(graph.edge(tree[j]).inVertex()).point())
                lowerBoundary.append(
                    graph.vertex(graph.edge(tree[j]).outVertex()).point())

            geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
            geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)

            feat.setGeometry(geomUpper)
            feat['type'] = 'upper'
            feat['start'] = origPoint
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

            feat.setGeometry(geomLower)
            feat['type'] = 'lower'
            feat['start'] = origPoint
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

            vertices[:] = []
            upperBoundary[:] = []
            lowerBoundary[:] = []

            feedback.setProgress(int(i * total))

        return {self.OUTPUT: dest_id}
Example #39
0
def analysis_summary(aggregate_hazard, analysis, callback=None):
    """Compute the summary from the aggregate hazard to analysis.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | analysis_id |

    Output layer :
    | analysis_id | count_hazard_class | affected_count | total |

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param analysis: The target vector layer where to write statistics.
    :type analysis: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int), 'maximum' (int) and 'step' (str).
        Defaults to None.
    :type callback: function

    :return: The new target layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_3_analysis_steps['output_layer_name']
    processing_step = summary_3_analysis_steps['step_name']

    source_fields = aggregate_hazard.keywords['inasafe_fields']
    target_fields = analysis.keywords['inasafe_fields']

    target_compulsory_fields = [
        analysis_id_field,
        analysis_name_field,
    ]
    check_inputs(target_compulsory_fields, target_fields)

    source_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field,
        total_field
    ]
    check_inputs(source_compulsory_fields, source_fields)

    absolute_values = create_absolute_values_structure(
        aggregate_hazard, ['all'])

    hazard_class = source_fields[hazard_class_field['key']]
    hazard_class_index = aggregate_hazard.fieldNameIndex(hazard_class)
    unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index)

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    classification = hazard_keywords['classification']

    total = source_fields[total_field['key']]

    flat_table = FlatTable('hazard_class')

    # First loop over the aggregate_hazard layer
    request = QgsFeatureRequest()
    request.setSubsetOfAttributes(
        [hazard_class, total], aggregate_hazard.fields())
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregate_hazard.getFeatures():
        hazard_value = area[hazard_class_index]
        value = area[total]
        if not value or isinstance(value, QPyNullVariant) or isnan(value):
            # For isnan, see ticket #3812
            value = 0
        if not hazard_value or isinstance(hazard_value, QPyNullVariant):
            hazard_value = 'NULL'
        flat_table.add_value(
            value,
            hazard_class=hazard_value
        )

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = area[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(
                value,
                all='all'
            )

    analysis.startEditing()

    shift = analysis.fields().count()

    counts = [
        total_affected_field,
        total_not_affected_field,
        total_not_exposed_field,
        total_field]

    add_fields(
        analysis,
        absolute_values,
        counts,
        unique_hazard,
        hazard_count_field)

    affected_sum = 0
    not_affected_sum = 0
    not_exposed_sum = 0

    for area in analysis.getFeatures(request):
        total = 0
        for i, val in enumerate(unique_hazard):
            if not val or isinstance(val, QPyNullVariant):
                val = 'NULL'
            sum = flat_table.get_value(hazard_class=val)
            total += sum
            analysis.changeAttributeValue(area.id(), shift + i, sum)

            affected = post_processor_affected_function(
                    classification=classification, hazard_class=val)
            if affected == not_exposed_class['key']:
                not_exposed_sum += sum
            elif affected:
                affected_sum += sum
            else:
                not_affected_sum += sum

        # Affected field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard), affected_sum)

        # Not affected field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard) + 1, not_affected_sum)

        # Not exposed field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard) + 2, not_exposed_sum)

        # Total field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard) + 3, total)

        # Any absolute postprocessors
        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(
                all='all'
            )
            analysis.changeAttributeValue(
                area.id(), shift + len(unique_hazard) + 4 + i, value)

    # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is not
    # enough. ET 13/02/17
    # total_computed = (
    #     affected_sum + not_affected_sum + not_exposed_sum)
    # if not -1 < (total_computed - total) < 1:
    #     raise ComputationError

    analysis.commitChanges()

    analysis.keywords['title'] = layer_purpose_analysis_impacted['name']
    if qgis_version() >= 21600:
        analysis.setName(analysis.keywords['title'])
    else:
        analysis.setLayerName(analysis.keywords['title'])
    analysis.keywords['layer_purpose'] = layer_purpose_analysis_impacted['key']

    check_layer(analysis)
    return analysis
Example #40
0
    def processAlgorithm(self, parameters, context, feedback):
        network = self.parameterAsSource(parameters, self.INPUT, context)
        startPoints = self.parameterAsSource(parameters, self.START_POINTS, context)
        endPoint = self.parameterAsPoint(parameters, self.END_POINT, context, network.sourceCrs())
        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)

        directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context)
        forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context)
        backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context)
        bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context)
        defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context)
        speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context)
        defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context)
        tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)

        fields = QgsFields()
        fields.append(QgsField('start', QVariant.String, '', 254, 0))
        fields.append(QgsField('end', QVariant.String, '', 254, 0))
        fields.append(QgsField('cost', QVariant.Double, '', 20, 7))

        feat = QgsFeature()
        feat.setFields(fields)

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.LineString, network.sourceCrs())

        directionField = -1
        if directionFieldName:
            directionField = network.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName:
            speedField = network.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(network,
                                          directionField,
                                          forwardValue,
                                          backwardValue,
                                          bothValue,
                                          defaultDirection)

        distUnit = context.project().crs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField,
                                               defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)
            multiplier = 3600

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(network.sourceCrs(),
                                  True,
                                  tolerance)

        feedback.pushInfo(self.tr('Loading start points...'))
        request = QgsFeatureRequest()
        request.setFlags(request.flags() ^ QgsFeatureRequest.SubsetOfAttributes)
        request.setDestinationCrs(network.sourceCrs())
        features = startPoints.getFeatures(request)
        total = 100.0 / startPoints.featureCount() if startPoints.featureCount() else 0

        points = [endPoint]
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            points.append(f.geometry().asPoint())
            feedback.setProgress(int(current * total))

        feedback.pushInfo(self.tr('Building graph...'))
        snappedPoints = director.makeGraph(builder, points, feedback)

        feedback.pushInfo(self.tr('Calculating shortest paths...'))
        graph = builder.graph()

        idxEnd = graph.findVertex(snappedPoints[0])
        route = []

        nPoints = len(snappedPoints)
        total = 100.0 / nPoints if nPoints else 1
        for i in range(1, nPoints + 1):
            if feedback.isCanceled():
                break

            idxStart = graph.findVertex(snappedPoints[i])
            tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)

            if tree[idxEnd] == -1:
                msg = self.tr('There is no route from start point ({}) to end point ({}).'.format(points[i].toString(), endPoint.toString()))
                feedback.setProgressText(msg)
                QgsMessageLog.logMessage(msg, self.tr('Processing'), QgsMessageLog.WARNING)
                continue

            cost = 0.0
            current = idxEnd
            while current != idxStart:
                cost += graph.edge(tree[current]).cost(0)
                route.append(graph.vertex(graph.edge(tree[current]).inVertex()).point())
                current = graph.edge(tree[current]).outVertex()

            route.append(snappedPoints[i])
            route.reverse()

            geom = QgsGeometry.fromPolyline(route)
            feat.setGeometry(geom)
            feat['start'] = points[i].toString()
            feat['end'] = endPoint.toString()
            feat['cost'] = cost / multiplier
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

            route[:] = []

            feedback.setProgress(int(i * total))

        return {self.OUTPUT: dest_id}
Example #41
0
def execute_in_place_run(alg, parameters, context=None, feedback=None, raise_exceptions=False):
    """Executes an algorithm modifying features in-place in the input layer.

    :param alg: algorithm to run
    :type alg: QgsProcessingAlgorithm
    :param parameters: parameters of the algorithm
    :type parameters: dict
    :param context: context, defaults to None
    :type context: QgsProcessingContext, optional
    :param feedback: feedback, defaults to None
    :type feedback: QgsProcessingFeedback, optional
    :param raise_exceptions: useful for testing, if True exceptions are raised, normally exceptions will be forwarded to the feedback
    :type raise_exceptions: boo, default to False
    :raises QgsProcessingException: raised when there is no active layer, or it cannot be made editable
    :return: a tuple with true if success and results
    :rtype: tuple
    """

    if feedback is None:
        feedback = QgsProcessingFeedback()
    if context is None:
        context = dataobjects.createContext(feedback)

    # Only feature based algs have sourceFlags
    try:
        if alg.sourceFlags() & QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks:
            context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck)
    except AttributeError:
        pass

    active_layer = parameters['INPUT']

    # Run some checks and prepare the layer for in-place execution by:
    # - getting the active layer and checking that it is a vector
    # - making the layer editable if it was not already
    # - selecting all features if none was selected
    # - checking in-place support for the active layer/alg/parameters
    # If one of the check fails and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        if active_layer is None:
            raise QgsProcessingException(tr("There is not active layer."))

        if not isinstance(active_layer, QgsVectorLayer):
            raise QgsProcessingException(tr("Active layer is not a vector layer."))

        if not active_layer.isEditable():
            if not active_layer.startEditing():
                raise QgsProcessingException(tr("Active layer is not editable (and editing could not be turned on)."))

        if not alg.supportInPlaceEdit(active_layer):
            raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
    except QgsProcessingException as e:
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)
        return False, {}

    if not active_layer.selectedFeatureIds():
        active_layer.selectAll()

    # Make sure we are working on selected features only
    parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(active_layer.id(), True)
    parameters['OUTPUT'] = 'memory:'

    req = QgsFeatureRequest(QgsExpression(r"$id < 0"))
    req.setFlags(QgsFeatureRequest.NoGeometry)
    req.setSubsetOfAttributes([])

    # Start the execution
    # If anything goes wrong and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        new_feature_ids = []

        active_layer.beginEditCommand(alg.displayName())

        # Checks whether the algorithm has a processFeature method
        if hasattr(alg, 'processFeature'):  # in-place feature editing
            # Make a clone or it will crash the second time the dialog
            # is opened and run
            alg = alg.create()
            if not alg.prepare(parameters, context, feedback):
                raise QgsProcessingException(tr("Could not prepare selected algorithm."))
            # Check again for compatibility after prepare
            if not alg.supportInPlaceEdit(active_layer):
                raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
            field_idxs = range(len(active_layer.fields()))
            iterator_req = QgsFeatureRequest(active_layer.selectedFeatureIds())
            iterator_req.setInvalidGeometryCheck(context.invalidGeometryCheck())
            feature_iterator = active_layer.getFeatures(iterator_req)
            step = 100 / len(active_layer.selectedFeatureIds()) if active_layer.selectedFeatureIds() else 1
            for current, f in enumerate(feature_iterator):
                feedback.setProgress(current * step)
                if feedback.isCanceled():
                    break

                # need a deep copy, because python processFeature implementations may return
                # a shallow copy from processFeature
                input_feature = QgsFeature(f)
                new_features = alg.processFeature(input_feature, context, feedback)
                new_features = QgsVectorLayerUtils.makeFeaturesCompatible(new_features, active_layer)

                if len(new_features) == 0:
                    active_layer.deleteFeature(f.id())
                elif len(new_features) == 1:
                    new_f = new_features[0]
                    if not f.geometry().equals(new_f.geometry()):
                        active_layer.changeGeometry(f.id(), new_f.geometry())
                    if f.attributes() != new_f.attributes():
                        active_layer.changeAttributeValues(f.id(), dict(zip(field_idxs, new_f.attributes())), dict(zip(field_idxs, f.attributes())))
                    new_feature_ids.append(f.id())
                else:
                    active_layer.deleteFeature(f.id())
                    # Get the new ids
                    old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                    if not active_layer.addFeatures(new_features):
                        raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                    new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                    new_feature_ids += list(new_ids - old_ids)

            results, ok = {}, True

        else:  # Traditional 'run' with delete and add features cycle

            # There is no way to know if some features have been skipped
            # due to invalid geometries
            if context.invalidGeometryCheck() == QgsFeatureRequest.GeometrySkipInvalid:
                selected_ids = active_layer.selectedFeatureIds()
            else:
                selected_ids = []

            results, ok = alg.run(parameters, context, feedback)

            if ok:
                result_layer = QgsProcessingUtils.mapLayerFromString(results['OUTPUT'], context)
                # TODO: check if features have changed before delete/add cycle

                new_features = []

                # Check if there are any skipped features
                if context.invalidGeometryCheck() == QgsFeatureRequest.GeometrySkipInvalid:
                    missing_ids = list(set(selected_ids) - set(result_layer.allFeatureIds()))
                    if missing_ids:
                        for f in active_layer.getFeatures(QgsFeatureRequest(missing_ids)):
                            if not f.geometry().isGeosValid():
                                new_features.append(f)

                active_layer.deleteFeatures(active_layer.selectedFeatureIds())

                for f in result_layer.getFeatures():
                    new_features.extend(QgsVectorLayerUtils.
                                        makeFeaturesCompatible([f], active_layer))

                # Get the new ids
                old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                if not active_layer.addFeatures(new_features):
                    raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                new_feature_ids += list(new_ids - old_ids)

        active_layer.endEditCommand()

        if ok and new_feature_ids:
            active_layer.selectByIds(new_feature_ids)
        elif not ok:
            active_layer.rollBack()

        return ok, results

    except QgsProcessingException as e:
        active_layer.endEditCommand()
        active_layer.rollBack()
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)

    return False, {}
Example #42
0
    def test_sql2(self):
        l2 = QgsVectorLayer(os.path.join(self.testDataDir, "france_parts.shp"),
                            "france_parts", "ogr",
                            QgsVectorLayer.LayerOptions(False))
        self.assertEqual(l2.isValid(), True)
        QgsProject.instance().addMapLayer(l2)

        query = toPercent("SELECT * FROM france_parts")
        l4 = QgsVectorLayer("?query=%s&uid=ObjectId" % query, "tt", "virtual")
        self.assertEqual(l4.isValid(), True)

        self.assertEqual(l4.dataProvider().wkbType(), 6)
        self.assertEqual(l4.dataProvider().crs().postgisSrid(), 4326)

        n = 0
        r = QgsFeatureRequest(QgsRectangle(-1.677, 49.624, -0.816, 49.086))
        for f in l4.getFeatures(r):
            self.assertEqual(f.geometry() is not None, True)
            self.assertEqual(f.attributes()[0], 2661)
            n += 1
        self.assertEqual(n, 1)

        # use uid
        query = toPercent("SELECT * FROM france_parts")
        l5 = QgsVectorLayer(
            "?query=%s&geometry=geometry:polygon:4326&uid=ObjectId" % query,
            "tt", "virtual")
        self.assertEqual(l5.isValid(), True)

        idSum = sum(f.id() for f in l5.getFeatures())
        self.assertEqual(idSum, 10659)

        r = QgsFeatureRequest(2661)
        idSum2 = sum(f.id() for f in l5.getFeatures(r))
        self.assertEqual(idSum2, 2661)

        r = QgsFeatureRequest()
        r.setFilterFids([2661, 2664])
        self.assertEqual(sum(f.id() for f in l5.getFeatures(r)), 2661 + 2664)

        # test attribute subset
        r = QgsFeatureRequest()
        r.setFlags(QgsFeatureRequest.SubsetOfAttributes)
        r.setSubsetOfAttributes([1])
        s = [(f.id(), f.attributes()[1]) for f in l5.getFeatures(r)]
        self.assertEqual(sum([x[0] for x in s]), 10659)
        self.assertEqual(sum([x[1] for x in s]), 3064.0)

        # test NoGeometry
        # by request flag
        r = QgsFeatureRequest()
        r.setFlags(QgsFeatureRequest.NoGeometry)
        self.assertEqual(all([not f.hasGeometry() for f in l5.getFeatures(r)]),
                         True)

        # test subset
        self.assertEqual(l5.dataProvider().featureCount(), 4)
        l5.setSubsetString("ObjectId = 2661")
        idSum2 = sum(f.id() for f in l5.getFeatures(r))
        self.assertEqual(idSum2, 2661)
        self.assertEqual(l5.dataProvider().featureCount(), 1)
Example #43
0
    def processAlgorithm(self, progress):
        layer = dataobjects.getObjectFromUri(
            self.getParameterValue(self.INPUT_VECTOR))
        startPoint = self.getParameterValue(self.START_POINT)
        endPoints = dataobjects.getObjectFromUri(
            self.getParameterValue(self.END_POINTS))
        strategy = self.getParameterValue(self.STRATEGY)

        directionFieldName = self.getParameterValue(self.DIRECTION_FIELD)
        forwardValue = self.getParameterValue(self.VALUE_FORWARD)
        backwardValue = self.getParameterValue(self.VALUE_BACKWARD)
        bothValue = self.getParameterValue(self.VALUE_BOTH)
        defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
        bothValue = self.getParameterValue(self.VALUE_BOTH)
        defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
        speedFieldName = self.getParameterValue(self.SPEED_FIELD)
        defaultSpeed = self.getParameterValue(self.DEFAULT_SPEED)
        tolerance = self.getParameterValue(self.TOLERANCE)

        fields = QgsFields()
        fields.append(QgsField('start', QVariant.String, '', 254, 0))
        fields.append(QgsField('end', QVariant.String, '', 254, 0))
        fields.append(QgsField('cost', QVariant.Double, '', 20, 7))

        feat = QgsFeature()
        feat.setFields(fields)

        writer = self.getOutputFromName(
            self.OUTPUT_LAYER).getVectorWriter(
                fields.toList(),
                QgsWkbTypes.LineString,
                layer.crs())

        tmp = startPoint.split(',')
        startPoint = QgsPoint(float(tmp[0]), float(tmp[1]))

        directionField = -1
        if directionFieldName is not None:
            directionField = layer.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName is not None:
            speedField = layer.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(layer,
                                          directionField,
                                          forwardValue,
                                          backwardValue,
                                          bothValue,
                                          defaultDirection)

        distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField,
                                               defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)
            multiplier = 3600

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(iface.mapCanvas().mapSettings().destinationCrs(),
                                  iface.mapCanvas().hasCrsTransformEnabled(),
                                  tolerance)

        progress.setInfo(self.tr('Loading end points...'))
        request = QgsFeatureRequest()
        request.setFlags(request.flags() ^ QgsFeatureRequest.SubsetOfAttributes)
        features = vector.features(endPoints, request)
        count = len(features)

        points = [startPoint]
        for f in features:
            points.append(f.geometry().asPoint())

        progress.setInfo(self.tr('Building graph...'))
        snappedPoints = director.makeGraph(builder, points)

        progress.setInfo(self.tr('Calculating shortest paths...'))
        graph = builder.graph()

        idxStart = graph.findVertex(snappedPoints[0])
        tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
        route = []

        total = 100.0 / count
        for i in range(1, count + 1):
            idxEnd = graph.findVertex(snappedPoints[i])

            if tree[idxEnd] == -1:
                msg = self.tr('There is no route from start point ({}) to end point ({}).'.format(startPoint.toString(), points[i].toString()))
                progress.setText(msg)
                ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, msg)
                continue

            cost = 0.0
            current = idxEnd
            while current != idxStart:
                cost += graph.edge(tree[current]).cost(0)
                route.append(graph.vertex(graph.edge(tree[current]).inVertex()).point())
                current = graph.edge(tree[current]).outVertex()

            route.append(snappedPoints[0])
            route.reverse()

            geom = QgsGeometry.fromPolyline(route)
            feat.setGeometry(geom)
            feat['start'] = startPoint.toString()
            feat['end'] = points[i].toString()
            feat['cost'] = cost / multiplier
            writer.addFeature(feat)

            route[:] = []

            progress.setPercentage(int(i * total))

        del writer
Example #44
0
    def processAlgorithm(self, parameters, context, feedback):
        network = self.parameterAsSource(parameters, self.INPUT, context)
        startPoints = self.parameterAsSource(parameters, self.START_POINTS, context)
        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
        travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST, context)

        directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context)
        forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context)
        backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context)
        bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context)
        defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context)
        speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context)
        defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context)
        tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)

        fields = QgsFields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('start', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        directionField = -1
        if directionFieldName:
            directionField = network.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName:
            speedField = network.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(network,
                                          directionField,
                                          forwardValue,
                                          backwardValue,
                                          bothValue,
                                          defaultDirection)

        distUnit = context.project().crs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField,
                                               defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(network.sourceCrs(),
                                  True,
                                  tolerance)

        feedback.pushInfo(self.tr('Loading start points...'))
        request = QgsFeatureRequest()
        request.setFlags(request.flags() ^ QgsFeatureRequest.SubsetOfAttributes)
        request.setDestinationCrs(network.sourceCrs())
        features = startPoints.getFeatures(request)
        total = 100.0 / startPoints.featureCount() if startPoints.featureCount() else 0

        points = []
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            points.append(f.geometry().asPoint())
            feedback.setProgress(int(current * total))

        feedback.pushInfo(self.tr('Building graph...'))
        snappedPoints = director.makeGraph(builder, points, feedback)

        feedback.pushInfo(self.tr('Calculating service areas...'))
        graph = builder.graph()

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.MultiPoint, network.sourceCrs())

        vertices = []
        upperBoundary = []
        lowerBoundary = []
        total = 100.0 / len(snappedPoints) if snappedPoints else 1
        for i, p in enumerate(snappedPoints):
            if feedback.isCanceled():
                break

            idxStart = graph.findVertex(snappedPoints[i])
            origPoint = points[i].toString()

            tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
            for j, v in enumerate(cost):
                if v > travelCost and tree[j] != -1:
                    vertexId = graph.edge(tree[j]).outVertex()
                    if cost[vertexId] <= travelCost:
                        vertices.append(j)

            for j in vertices:
                upperBoundary.append(graph.vertex(graph.edge(tree[j]).inVertex()).point())
                lowerBoundary.append(graph.vertex(graph.edge(tree[j]).outVertex()).point())

            geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
            geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)

            feat.setGeometry(geomUpper)
            feat['type'] = 'upper'
            feat['start'] = origPoint
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

            feat.setGeometry(geomLower)
            feat['type'] = 'lower'
            feat['start'] = origPoint
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

            vertices[:] = []
            upperBoundary[:] = []
            lowerBoundary[:] = []

            feedback.setProgress(int(i * total))

        return {self.OUTPUT: dest_id}
Example #45
0
def execute_in_place_run(alg,
                         parameters,
                         context=None,
                         feedback=None,
                         raise_exceptions=False):
    """Executes an algorithm modifying features in-place in the input layer.

    :param alg: algorithm to run
    :type alg: QgsProcessingAlgorithm
    :param parameters: parameters of the algorithm
    :type parameters: dict
    :param context: context, defaults to None
    :type context: QgsProcessingContext, optional
    :param feedback: feedback, defaults to None
    :type feedback: QgsProcessingFeedback, optional
    :param raise_exceptions: useful for testing, if True exceptions are raised, normally exceptions will be forwarded to the feedback
    :type raise_exceptions: boo, default to False
    :raises QgsProcessingException: raised when there is no active layer, or it cannot be made editable
    :return: a tuple with true if success and results
    :rtype: tuple
    """

    if feedback is None:
        feedback = QgsProcessingFeedback()
    if context is None:
        context = dataobjects.createContext(feedback)

    # Only feature based algs have sourceFlags
    try:
        if alg.sourceFlags(
        ) & QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks:
            context.setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck)
    except AttributeError:
        pass

    active_layer = parameters['INPUT']

    # Run some checks and prepare the layer for in-place execution by:
    # - getting the active layer and checking that it is a vector
    # - making the layer editable if it was not already
    # - selecting all features if none was selected
    # - checking in-place support for the active layer/alg/parameters
    # If one of the check fails and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        if active_layer is None:
            raise QgsProcessingException(tr("There is not active layer."))

        if not isinstance(active_layer, QgsVectorLayer):
            raise QgsProcessingException(
                tr("Active layer is not a vector layer."))

        if not active_layer.isEditable():
            if not active_layer.startEditing():
                raise QgsProcessingException(
                    tr("Active layer is not editable (and editing could not be turned on)."
                       ))

        if not alg.supportInPlaceEdit(active_layer):
            raise QgsProcessingException(
                tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."
                   ))
    except QgsProcessingException as e:
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing',
                                 Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)
        return False, {}

    if not active_layer.selectedFeatureIds():
        active_layer.selectAll()

    # Make sure we are working on selected features only
    parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(
        active_layer.id(), True)
    parameters['OUTPUT'] = 'memory:'

    req = QgsFeatureRequest(QgsExpression(r"$id < 0"))
    req.setFlags(QgsFeatureRequest.NoGeometry)
    req.setSubsetOfAttributes([])

    # Start the execution
    # If anything goes wrong and raise_exceptions is True an exception
    # is raised, else the execution is aborted and the error reported in
    # the feedback
    try:
        new_feature_ids = []

        active_layer.beginEditCommand(alg.displayName())

        # Checks whether the algorithm has a processFeature method
        if hasattr(alg, 'processFeature'):  # in-place feature editing
            # Make a clone or it will crash the second time the dialog
            # is opened and run
            alg = alg.create()
            if not alg.prepare(parameters, context, feedback):
                raise QgsProcessingException(
                    tr("Could not prepare selected algorithm."))
            # Check again for compatibility after prepare
            if not alg.supportInPlaceEdit(active_layer):
                raise QgsProcessingException(
                    tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."
                       ))
            field_idxs = range(len(active_layer.fields()))
            iterator_req = QgsFeatureRequest(active_layer.selectedFeatureIds())
            iterator_req.setInvalidGeometryCheck(
                context.invalidGeometryCheck())
            feature_iterator = active_layer.getFeatures(iterator_req)
            step = 100 / len(active_layer.selectedFeatureIds()
                             ) if active_layer.selectedFeatureIds() else 1
            for current, f in enumerate(feature_iterator):
                feedback.setProgress(current * step)
                if feedback.isCanceled():
                    break

                # need a deep copy, because python processFeature implementations may return
                # a shallow copy from processFeature
                input_feature = QgsFeature(f)
                new_features = alg.processFeature(input_feature, context,
                                                  feedback)
                new_features = QgsVectorLayerUtils.makeFeaturesCompatible(
                    new_features, active_layer)

                if len(new_features) == 0:
                    active_layer.deleteFeature(f.id())
                elif len(new_features) == 1:
                    new_f = new_features[0]
                    if not f.geometry().equals(new_f.geometry()):
                        active_layer.changeGeometry(f.id(), new_f.geometry())
                    if f.attributes() != new_f.attributes():
                        active_layer.changeAttributeValues(
                            f.id(), dict(zip(field_idxs, new_f.attributes())),
                            dict(zip(field_idxs, f.attributes())))
                    new_feature_ids.append(f.id())
                else:
                    active_layer.deleteFeature(f.id())
                    # Get the new ids
                    old_ids = set(
                        [f.id() for f in active_layer.getFeatures(req)])
                    if not active_layer.addFeatures(new_features):
                        raise QgsProcessingException(
                            tr("Error adding processed features back into the layer."
                               ))
                    new_ids = set(
                        [f.id() for f in active_layer.getFeatures(req)])
                    new_feature_ids += list(new_ids - old_ids)

            results, ok = {}, True

        else:  # Traditional 'run' with delete and add features cycle

            # There is no way to know if some features have been skipped
            # due to invalid geometries
            if context.invalidGeometryCheck(
            ) == QgsFeatureRequest.GeometrySkipInvalid:
                selected_ids = active_layer.selectedFeatureIds()
            else:
                selected_ids = []

            results, ok = alg.run(parameters, context, feedback)

            if ok:
                result_layer = QgsProcessingUtils.mapLayerFromString(
                    results['OUTPUT'], context)
                # TODO: check if features have changed before delete/add cycle

                new_features = []

                # Check if there are any skipped features
                if context.invalidGeometryCheck(
                ) == QgsFeatureRequest.GeometrySkipInvalid:
                    missing_ids = list(
                        set(selected_ids) - set(result_layer.allFeatureIds()))
                    if missing_ids:
                        for f in active_layer.getFeatures(
                                QgsFeatureRequest(missing_ids)):
                            if not f.geometry().isGeosValid():
                                new_features.append(f)

                active_layer.deleteFeatures(active_layer.selectedFeatureIds())

                for f in result_layer.getFeatures():
                    new_features.extend(
                        QgsVectorLayerUtils.makeFeaturesCompatible(
                            [f], active_layer))

                # Get the new ids
                old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                if not active_layer.addFeatures(new_features):
                    raise QgsProcessingException(
                        tr("Error adding processed features back into the layer."
                           ))
                new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                new_feature_ids += list(new_ids - old_ids)

        active_layer.endEditCommand()

        if ok and new_feature_ids:
            active_layer.selectByIds(new_feature_ids)
        elif not ok:
            active_layer.rollBack()

        return ok, results

    except QgsProcessingException as e:
        active_layer.endEditCommand()
        active_layer.rollBack()
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing',
                                 Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)

    return False, {}
Example #46
0
def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=None, raise_exceptions=False):
    """Executes an algorithm modifying features in-place in the input layer.

    The input layer must be editable or an exception is raised.

    :param alg: algorithm to run
    :type alg: QgsProcessingAlgorithm
    :param active_layer: the editable layer
    :type active_layer: QgsVectoLayer
    :param parameters: parameters of the algorithm
    :type parameters: dict
    :param context: context, defaults to None
    :param context: QgsProcessingContext, optional
    :param feedback: feedback, defaults to None
    :param feedback: QgsProcessingFeedback, optional
    :raises QgsProcessingException: raised when the layer is not editable or the layer cannot be found in the current project
    :return: a tuple with true if success and results
    :rtype: tuple
    """

    if feedback is None:
        feedback = QgsProcessingFeedback()
    if context is None:
        context = dataobjects.createContext(feedback)

    if active_layer is None or not active_layer.isEditable():
        raise QgsProcessingException(tr("Layer is not editable or layer is None."))

    if not alg.supportInPlaceEdit(active_layer):
        raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))

    parameters['OUTPUT'] = 'memory:'

    try:
        new_feature_ids = []

        active_layer.beginEditCommand(alg.name())

        req = QgsFeatureRequest(QgsExpression(r"$id < 0"))
        req.setFlags(QgsFeatureRequest.NoGeometry)
        req.setSubsetOfAttributes([])

        # Checks whether the algorithm has a processFeature method
        if hasattr(alg, 'processFeature'):  # in-place feature editing
            # Make a clone or it will crash the second time the dialog
            # is opened and run
            alg = alg.create()
            if not alg.prepare(parameters, context, feedback):
                raise QgsProcessingException(tr("Could not prepare selected algorithm."))
            # Check again for compatibility after prepare
            if not alg.supportInPlaceEdit(active_layer):
                raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
            field_idxs = range(len(active_layer.fields()))
            feature_iterator = active_layer.getFeatures(QgsFeatureRequest(active_layer.selectedFeatureIds())) if parameters['INPUT'].selectedFeaturesOnly else active_layer.getFeatures()
            for f in feature_iterator:
                new_features = alg.processFeature(f, context, feedback)
                new_features = make_features_compatible(new_features, active_layer)
                if len(new_features) == 0:
                    active_layer.deleteFeature(f.id())
                elif len(new_features) == 1:
                    new_f = new_features[0]
                    if not f.geometry().equals(new_f.geometry()):
                        active_layer.changeGeometry(f.id(), new_f.geometry())
                    if f.attributes() != new_f.attributes():
                        active_layer.changeAttributeValues(f.id(), dict(zip(field_idxs, new_f.attributes())), dict(zip(field_idxs, f.attributes())))
                    new_feature_ids.append(f.id())
                else:
                    active_layer.deleteFeature(f.id())
                    # Get the new ids
                    old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                    if not active_layer.addFeatures(new_features):
                        raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                    new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                    new_feature_ids += list(new_ids - old_ids)

            results, ok = {}, True

        else:  # Traditional 'run' with delete and add features cycle
            results, ok = alg.run(parameters, context, feedback)

            if ok:
                result_layer = QgsProcessingUtils.mapLayerFromString(results['OUTPUT'], context)
                # TODO: check if features have changed before delete/add cycle
                active_layer.deleteFeatures(active_layer.selectedFeatureIds())
                new_features = []
                for f in result_layer.getFeatures():
                    new_features.extend(make_features_compatible([f], active_layer))

                # Get the new ids
                old_ids = set([f.id() for f in active_layer.getFeatures(req)])
                if not active_layer.addFeatures(new_features):
                    raise QgsProcessingException(tr("Error adding processed features back into the layer."))
                new_ids = set([f.id() for f in active_layer.getFeatures(req)])
                new_feature_ids += list(new_ids - old_ids)

        active_layer.endEditCommand()

        if ok and new_feature_ids:
            active_layer.selectByIds(new_feature_ids)
        elif not ok:
            active_layer.rollBack()

        return ok, results

    except QgsProcessingException as e:
        active_layer.endEditCommand()
        active_layer.rollBack()
        if raise_exceptions:
            raise e
        QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
        if feedback is not None:
            feedback.reportError(getattr(e, 'msg', str(e)))

    return False, {}
    def virtualFields(params: Dict[str, str], response: QgsServerResponse, project: QgsProject) -> None:
        """ Get virtual fields for features
        In parameters:
            LAYER=wms-layer-name
            VIRTUALS={"key1": "first expression", "key2": "second expression"}
            // optionals
            FILTER=An expression to filter layer
            FIELDS=list of requested field separated by comma
            WITH_GEOMETRY=False
        """
        layer_name = params.get('LAYER', '')
        if not layer_name:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'VirtualFields' REQUEST: LAYER parameter is mandatory",
                400)

        # get layer
        layer = find_vector_layer(layer_name, project)
        # layer not found
        if not layer:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid LAYER parameter for 'VirtualFields': {} provided".format(layer_name),
                400)

        # get virtuals
        virtuals = params.get('VIRTUALS', '')
        if not virtuals:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'VirtualFields' REQUEST: VIRTUALS parameter is mandatory",
                400)

        # try to load virtuals dict
        try:
            vir_json = json.loads(virtuals)
        except Exception:
            QgsMessageLog.logMessage(
                "JSON loads virtuals '{}' exception:\n{}".format(virtuals, traceback.format_exc()),
                "lizmap", Qgis.Critical)
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'VirtualFields' REQUEST: VIRTUALS '{}' are not well formed".format(virtuals),
                400)

        if not isinstance(vir_json, dict):
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'VirtualFields' REQUEST: VIRTUALS '{}' are not well formed".format(virtuals),
                400)

        # create expression context
        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(QgsExpressionContextUtils.projectScope(project))
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

        # create distance area context
        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs(), project.transformContext())
        da.setEllipsoid(project.ellipsoid())

        # parse virtuals
        exp_map = {}
        exp_parser_errors = []
        for k, e in vir_json.items():
            exp = QgsExpression(e)
            exp.setGeomCalculator(da)
            exp.setDistanceUnits(project.distanceUnits())
            exp.setAreaUnits(project.areaUnits())

            if exp.hasParserError():
                exp_parser_errors.append('Error "{}": {}'.format(e, exp.parserErrorString()))
                continue

            if not exp.isValid():
                exp_parser_errors.append('Expression not valid "{}"'.format(e))
                continue

            exp.prepare(exp_context)
            exp_map[k] = exp

        # expression parser errors found
        if exp_parser_errors:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid VIRTUALS for 'VirtualFields':\n{}".format('\n'.join(exp_parser_errors)),
                400)

        req = QgsFeatureRequest()

        # get filter
        req_filter = params.get('FILTER', '')
        if req_filter:
            req_exp = QgsExpression(req_filter)
            req_exp.setGeomCalculator(da)
            req_exp.setDistanceUnits(project.distanceUnits())
            req_exp.setAreaUnits(project.areaUnits())

            if req_exp.hasParserError():
                raise ExpressionServiceError(
                    "Bad request error",
                    "Invalid FILTER for 'VirtualFields' Error \"{}\": {}".format(
                        req_filter, req_exp.parserErrorString()),
                    400)

            if not req_exp.isValid():
                raise ExpressionServiceError(
                    "Bad request error",
                    "Invalid FILTER for 'VirtualFields' Expression not valid \"{}\"".format(req_filter),
                    400)

            req_exp.prepare(exp_context)
            req = QgsFeatureRequest(req_exp, exp_context)

        # With geometry
        with_geom = params.get('WITH_GEOMETRY', '').lower() in ['true', '1', 't']
        if not with_geom:
            req.setFlags(QgsFeatureRequest.NoGeometry)

        # Fields
        pk_attributes = layer.primaryKeyAttributes()
        attribute_list = [i for i in pk_attributes]
        fields = layer.fields()
        r_fields = [f.strip() for f in params.get('FIELDS', '').split(',') if f]
        for f in r_fields:
            attribute_list.append(fields.indexOf(f))

        # response
        response.setStatusCode(200)
        response.setHeader("Content-Type", "application/json")
        response.write('{ "type": "FeatureCollection","features":[')
        response.flush()

        json_exporter = QgsJsonExporter(layer)
        if attribute_list:
            json_exporter.setAttributes(attribute_list)

        separator = ''
        for feat in layer.getFeatures(req):
            fid = layer_name + '.' + get_server_fid(feat, pk_attributes)

            extra = {}

            # Update context
            exp_context.setFeature(feat)
            exp_context.setFields(feat.fields())

            # Evaluate expressions for virtual fields
            errors = {}
            for k, exp in exp_map.items():
                value = exp.evaluate(exp_context)
                if exp.hasEvalError():
                    extra[k] = None
                    errors[k] = exp.evalErrorString()
                else:
                    extra[k] = json.loads(QgsJsonUtils.encodeValue(value))
                    errors[k] = exp.expression()

            response.write(separator + json_exporter.exportFeature(feat, extra, fid))
            response.flush()
            separator = ',\n'
        response.write(']}')
        return
    def get_feature_with_form_scope(params: Dict[str, str], response: QgsServerResponse, project: QgsProject) -> None:
        """ Get filtered features with a form scope
        In parameters:
            LAYER=wms-layer-name
            FILTER=An expression to filter layer
            FORM_FEATURE={"type": "Feature", "geometry": {}, "properties": {}}
            // optionals
            FIELDS=list of requested field separated by comma
            WITH_GEOMETRY=False
        """
        layer_name = params.get('LAYER', '')
        if not layer_name:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: LAYER parameter is mandatory",
                400)

        # get layer
        layer = find_vector_layer(layer_name, project)
        # layer not found
        if not layer:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid LAYER parameter for 'VirtualField': {} provided".format(layer_name),
                400)

        # get filter
        exp_filter = params.get('FILTER', '')
        if not exp_filter:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: FILTER parameter is mandatory",
                400)

        # get form feature
        form_feature = params.get('FORM_FEATURE', '')
        if not form_feature:
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE parameter is mandatory",
                400)

        # Check features
        try:
            geojson = json.loads(form_feature)
        except Exception:
            QgsMessageLog.logMessage(
                "JSON loads form feature '{}' exception:\n{}".format(form_feature, traceback.format_exc()),
                "lizmap", Qgis.Critical)
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE '{}' are not well formed".format(form_feature),
                400)

        if not geojson or not isinstance(geojson, dict):
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE '{}' are not well formed".format(form_feature),
                400)

        if ('type' not in geojson) or geojson['type'] != 'Feature':
            raise ExpressionServiceError(
                "Bad request error",
                ("Invalid 'GetFeatureWithFormScope' REQUEST: FORM_FEATURE '{}' are not well formed: type not defined "
                 "or not Feature.").format(form_feature),
                400)

        # try to load form feature
        # read fields
        form_feature_fields = QgsJsonUtils.stringToFields(
            form_feature,
            QTextCodec.codecForName("UTF-8"))
        # read features
        form_feature_list = QgsJsonUtils.stringToFeatureList(
            form_feature,
            form_feature_fields,
            QTextCodec.codecForName("UTF-8"))

        # features not well formed
        if not form_feature_list:
            raise ExpressionServiceError(
                "Bad request error",
                ("Invalid FORM_FEATURE for 'GetFeatureWithFormScope': not GeoJSON feature provided\n"
                 "{}").format(form_feature),
                400)

        if len(form_feature_list) != 1:
            raise ExpressionServiceError(
                "Bad request error",
                ("Invalid FORM_FEATURE for 'GetFeatureWithFormScope': not GeoJSON feature provided\n"
                 "{}").format(form_feature),
                400)

        # Get the form feature
        form_feat = form_feature_list[0]

        # create expression context
        exp_context = QgsExpressionContext()
        exp_context.appendScope(QgsExpressionContextUtils.globalScope())
        exp_context.appendScope(QgsExpressionContextUtils.projectScope(project))
        exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))
        exp_context.appendScope(QgsExpressionContextUtils.formScope(form_feat))

        # create distance area context
        da = QgsDistanceArea()
        da.setSourceCrs(layer.crs(), project.transformContext())
        da.setEllipsoid(project.ellipsoid())

        # Get filter expression
        exp_f = QgsExpression(exp_filter)
        exp_f.setGeomCalculator(da)
        exp_f.setDistanceUnits(project.distanceUnits())
        exp_f.setAreaUnits(project.areaUnits())

        if exp_f.hasParserError():
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid FILTER for 'GetFeatureWithFormScope': Error \"{}\": {}".format(
                    exp_filter, exp_f.parserErrorString()),
                400)

        if not exp_f.isValid():
            raise ExpressionServiceError(
                "Bad request error",
                "Invalid FILTER for 'GetFeatureWithFormScope': Expression not valid \"{}\"".format(exp_filter),
                400)

        exp_f.prepare(exp_context)

        req = QgsFeatureRequest(exp_f, exp_context)

        # With geometry
        with_geom = params.get('WITH_GEOMETRY', '').lower() in ['true', '1', 't']
        if not with_geom:
            req.setFlags(QgsFeatureRequest.NoGeometry)

        # Fields
        pk_attributes = layer.primaryKeyAttributes()
        attribute_list = [i for i in pk_attributes]
        fields = layer.fields()
        r_fields = [f.strip() for f in params.get('FIELDS', '').split(',') if f]
        for f in r_fields:
            attribute_list.append(fields.indexOf(f))

        # response
        response.setStatusCode(200)
        response.setHeader("Content-Type", "application/json")
        response.write('{ "type": "FeatureCollection","features":[')
        response.flush()

        json_exporter = QgsJsonExporter(layer)
        if attribute_list:
            json_exporter.setAttributes(attribute_list)

        separator = ''
        for feat in layer.getFeatures(req):
            fid = layer_name + '.' + get_server_fid(feat, pk_attributes)
            response.write(separator + json_exporter.exportFeature(feat, {}, fid))
            response.flush()
            separator = ',\n'
        response.write(']}')
        return
Example #49
0
    def processAlgorithm(self, feedback):
        layer = dataobjects.getObjectFromUri(
            self.getParameterValue(self.INPUT_VECTOR))
        startPoints = dataobjects.getObjectFromUri(
            self.getParameterValue(self.START_POINTS))
        strategy = self.getParameterValue(self.STRATEGY)
        travelCost = self.getParameterValue(self.TRAVEL_COST)

        directionFieldName = self.getParameterValue(self.DIRECTION_FIELD)
        forwardValue = self.getParameterValue(self.VALUE_FORWARD)
        backwardValue = self.getParameterValue(self.VALUE_BACKWARD)
        bothValue = self.getParameterValue(self.VALUE_BOTH)
        defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
        bothValue = self.getParameterValue(self.VALUE_BOTH)
        defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
        speedFieldName = self.getParameterValue(self.SPEED_FIELD)
        defaultSpeed = self.getParameterValue(self.DEFAULT_SPEED)
        tolerance = self.getParameterValue(self.TOLERANCE)

        fields = QgsFields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('start', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        writerPoints = self.getOutputFromName(
            self.OUTPUT_POINTS).getVectorWriter(fields, QgsWkbTypes.MultiPoint,
                                                layer.crs())

        writerPolygons = self.getOutputFromName(
            self.OUTPUT_POLYGON).getVectorWriter(fields, QgsWkbTypes.Polygon,
                                                 layer.crs())

        directionField = -1
        if directionFieldName is not None:
            directionField = layer.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName is not None:
            speedField = layer.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(layer, directionField, forwardValue,
                                          backwardValue, bothValue,
                                          defaultDirection)

        distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(
            distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField, defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(
            iface.mapCanvas().mapSettings().destinationCrs(),
            iface.mapCanvas().hasCrsTransformEnabled(), tolerance)

        feedback.pushInfo(self.tr('Loading start points...'))
        request = QgsFeatureRequest()
        request.setFlags(request.flags()
                         ^ QgsFeatureRequest.SubsetOfAttributes)
        features = vector.features(startPoints, request)
        points = []
        for f in features:
            points.append(f.geometry().asPoint())

        feedback.pushInfo(self.tr('Building graph...'))
        snappedPoints = director.makeGraph(builder, points)

        feedback.pushInfo(self.tr('Calculating service areas...'))
        graph = builder.graph()

        vertices = []
        upperBoundary = []
        lowerBoundary = []
        total = 100.0 / len(snappedPoints)
        for i, p in enumerate(snappedPoints):
            idxStart = graph.findVertex(snappedPoints[i])
            origPoint = points[i].toString()

            tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
            for j, v in enumerate(cost):
                if v > travelCost and tree[j] != -1:
                    vertexId = graph.edge(tree[j]).outVertex()
                    if cost[vertexId] <= travelCost:
                        vertices.append(j)

            for j in vertices:
                upperBoundary.append(
                    graph.vertex(graph.edge(tree[j]).inVertex()).point())
                lowerBoundary.append(
                    graph.vertex(graph.edge(tree[j]).outVertex()).point())

            geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
            geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)

            feat.setGeometry(geomUpper)
            feat['type'] = 'upper'
            feat['start'] = origPoint
            writerPoints.addFeature(feat)

            feat.setGeometry(geomLower)
            feat['type'] = 'lower'
            feat['start'] = origPoint
            writerPoints.addFeature(feat)

            upperBoundary.append(startPoint)
            lowerBoundary.append(startPoint)
            geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
            geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)

            geom = geomUpper.convexHull()
            feat.setGeometry(geom)
            feat['type'] = 'upper'
            feat['start'] = origPoint
            writerPolygons.addFeature(feat)

            geom = geomLower.convexHull()
            feat.setGeometry(geom)
            feat['type'] = 'lower'
            feat['start'] = origPoint
            writerPolygons.addFeature(feat)

            vertices[:] = []
            upperBoundary[:] = []
            lowerBoundary[:] = []

            feedback.setProgress(int(i * total))

        del writerPoints
        del writerPolygons
    def run(self):
        """Run any post processors requested by the impact function.
        """
        try:
            requested_postprocessors = self.functionParams['postprocessors']
            postprocessors = get_postprocessors(requested_postprocessors)
        except (TypeError, KeyError):
            # TypeError is for when function_parameters is none
            # KeyError is for when ['postprocessors'] is unavailable
            postprocessors = {}
        LOGGER.debug('Running this postprocessors: ' + str(postprocessors))

        feature_names_attribute = self.aggregator.attributes[
            self.aggregator.defaults['AGGR_ATTR_KEY']]
        if feature_names_attribute is None:
            self.attribute_title = self.tr('Aggregation unit')
        else:
            self.attribute_title = feature_names_attribute

        name_filed_index = self.aggregator.layer.fieldNameIndex(
            self.attribute_title)
        sum_field_index = self.aggregator.layer.fieldNameIndex(
            self._sum_field_name())

        user_defined_female_ratio = False
        female_ratio_field_index = None
        female_ratio = None

        if 'Gender' in postprocessors:
            # look if we need to look for a variable female ratio in a layer
            try:
                female_ration_field = self.aggregator.attributes[
                    self.aggregator.defaults['FEM_RATIO_ATTR_KEY']]
                female_ratio_field_index = self.aggregator.layer.fieldNameIndex(
                    female_ration_field)

                # something went wrong finding the female ratio field,
                # use defaults from below except block
                if female_ratio_field_index == -1:
                    raise KeyError

                user_defined_female_ratio = True

            except KeyError:
                try:
                    female_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.defaults['FEM_RATIO_KEY'])
                except KeywordNotFoundError:
                    female_ratio = self.aggregator.defaults['FEM_RATIO']

        # iterate zone features
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.NoGeometry)
        provider = self.aggregator.layer.dataProvider()
        # start data retrieval: fetch no geometry and all attributes for each
        # feature
        polygon_index = 0
        for feature in provider.getFeatures(request):
            # if a feature has no field called
            if name_filed_index == -1:
                zone_name = str(feature.id())
            else:
                zone_name = feature[name_filed_index]

            # create dictionary of attributes to pass to postprocessor
            general_params = {
                'target_field': self.aggregator.target_field,
                'function_params': self.functionParams}

            if self.aggregator.statistics_type == 'class_count':
                general_params['impact_classes'] = (
                    self.aggregator.statistics_classes)
            elif self.aggregator.statistics_type == 'sum':
                impact_total = feature[sum_field_index]
                general_params['impact_total'] = impact_total

            try:
                general_params['impact_attrs'] = (
                    self.aggregator.impact_layer_attributes[polygon_index])
            except IndexError:
                # rasters and attributeless vectors have no attributes
                general_params['impact_attrs'] = None

            for key, value in postprocessors.iteritems():
                parameters = general_params
                try:
                    # look if params are available for this postprocessor
                    parameters.update(
                        self.functionParams['postprocessors'][key]['params'])
                except KeyError:
                    pass

                if key == 'Gender':
                    if user_defined_female_ratio:
                        female_ratio = feature[female_ratio_field_index]
                        if female_ratio is None:
                            female_ratio = self.aggregator.defaults[
                                'FEM_RATIO']
                        LOGGER.debug(female_ratio)
                    parameters['female_ratio'] = female_ratio

                value.setup(parameters)
                value.process()
                results = value.results()
                value.clear()
#                LOGGER.debug(results)
                try:
                    self.output[key].append(
                        (zone_name, results))
                except KeyError:
                    self.output[key] = []
                    self.output[key].append(
                        (zone_name, results))
            # increment the index
            polygon_index += 1
Example #51
0
def _clip_vector_layer(
        layer,
        extent,
        extra_keywords=None,
        explode_flag=True,
        hard_clip_flag=False,
        explode_attribute=None):
    """Clip a Hazard or Exposure layer to the extents provided.

    The layer must be a vector layer or an exception will be thrown.

    The output layer will always be in WGS84/Geographic.

    :param layer: A valid QGIS vector or raster layer
    :type layer:

    :param extent: Either an array representing the exposure layer extents
        in the form [xmin, ymin, xmax, ymax]. It is assumed that the
        coordinates are in EPSG:4326 although currently no checks are made to
        enforce this.
        or:
        A QgsGeometry of type polygon.
        **Polygon clipping is currently only supported for vector datasets.**
    :type extent: list(float, float, float, float)

    :param extra_keywords: Optional keywords dictionary to be added to
        output layer.
    :type extra_keywords: dict

    :param explode_flag: A bool specifying whether multipart features
        should be 'exploded' into singleparts.
        **This parameter is ignored for raster layer clipping.**
    :type explode_flag: bool

    :param hard_clip_flag: A bool specifying whether line and polygon
        features that extend beyond the extents should be clipped such that
        they are reduced in size to the part of the geometry that intersects
        the extent only. Default is False.
        **This parameter is ignored for raster layer clipping.**
    :type hard_clip_flag: bool

    :param explode_attribute: A str specifying to which attribute #1,
        #2 and so on will be added in case of explode_flag being true. The
        attribute is modified only if there are at least 2 parts.
    :type explode_attribute: str

    :returns: Clipped layer (placed in the system temp dir). The output layer
        will be reprojected to EPSG:4326 if needed.
    :rtype: QgsVectorLayer

    """
    if not layer or not extent:
        myMessage = tr('Layer or Extent passed to clip is None.')
        raise InvalidParameterError(myMessage)

    if layer.type() != QgsMapLayer.VectorLayer:
        myMessage = tr('Expected a vector layer but received a %s.' %
                       str(layer.type()))
        raise InvalidParameterError(myMessage)

    #myHandle, myFilename = tempfile.mkstemp('.sqlite', 'clip_',
    #    temp_dir())
    myHandle, myFilename = tempfile.mkstemp('.shp', 'clip_',
                                            temp_dir())

    # Ensure the file is deleted before we try to write to it
    # fixes windows specific issue where you get a message like this
    # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory.
    # This is because mkstemp creates the file handle and leaves
    # the file open.
    os.close(myHandle)
    os.remove(myFilename)

    # Get the clip extents in the layer's native CRS
    myGeoCrs = QgsCoordinateReferenceSystem()
    myGeoCrs.createFromSrid(4326)
    myXForm = QgsCoordinateTransform(myGeoCrs, layer.crs())
    myAllowedClipTypes = [QGis.WKBPolygon, QGis.WKBPolygon25D]
    if type(extent) is list:
        myRect = QgsRectangle(
            extent[0], extent[1],
            extent[2], extent[3])
        # noinspection PyCallByClass
        myClipPolygon = QgsGeometry.fromRect(myRect)
    elif (type(extent) is QgsGeometry and
          extent.wkbType in myAllowedClipTypes):
        myRect = extent.boundingBox().toRectF()
        myClipPolygon = extent
    else:
        raise InvalidClipGeometryError(
            tr(
                'Clip geometry must be an extent or a single part'
                'polygon based geometry.'))

    myProjectedExtent = myXForm.transformBoundingBox(myRect)

    # Get vector layer
    myProvider = layer.dataProvider()
    if myProvider is None:
        myMessage = tr('Could not obtain data provider from '
                       'layer "%s"' % layer.source())
        raise Exception(myMessage)

    # Get the layer field list, select by our extent then write to disk
    # .. todo:: FIXME - for different geometry types we should implement
    #    different clipping behaviour e.g. reject polygons that
    #    intersect the edge of the bbox. Tim
    myRequest = QgsFeatureRequest()
    if not myProjectedExtent.isEmpty():
        myRequest.setFilterRect(myProjectedExtent)
        myRequest.setFlags(QgsFeatureRequest.ExactIntersect)

    myFieldList = myProvider.fields()

    myWriter = QgsVectorFileWriter(
        myFilename,
        'UTF-8',
        myFieldList,
        layer.wkbType(),
        myGeoCrs,
        #'SQLite')  # FIXME (Ole): This works but is far too slow
        'ESRI Shapefile')
    if myWriter.hasError() != QgsVectorFileWriter.NoError:
        myMessage = tr('Error when creating shapefile: <br>Filename:'
                       '%s<br>Error: %s' %
                       (myFilename, myWriter.hasError()))
        raise Exception(myMessage)

    # Reverse the coordinate xform now so that we can convert
    # geometries from layer crs to geocrs.
    myXForm = QgsCoordinateTransform(layer.crs(), myGeoCrs)
    # Retrieve every feature with its geometry and attributes
    myCount = 0
    myHasMultipart = False

    for myFeature in myProvider.getFeatures(myRequest):
        myGeometry = myFeature.geometry()

        # Loop through the parts adding them to the output file
        # we write out single part features unless explode_flag is False
        if explode_flag:
            myGeometryList = explode_multipart_geometry(myGeometry)
        else:
            myGeometryList = [myGeometry]

        for myPartIndex, myPart in enumerate(myGeometryList):
            myPart.transform(myXForm)
            if hard_clip_flag:
                # Remove any dangling bits so only intersecting area is
                # kept.
                myPart = clip_geometry(myClipPolygon, myPart)
            if myPart is None:
                continue

            myFeature.setGeometry(myPart)
            # There are multiple parts and we want to show it in the
            # explode_attribute
            if myPartIndex > 0 and explode_attribute is not None:
                myHasMultipart = True

            myWriter.addFeature(myFeature)
        myCount += 1
    del myWriter  # Flush to disk

    if myCount < 1:
        myMessage = tr(
            'No features fall within the clip extents. Try panning / zooming '
            'to an area containing data and then try to run your analysis '
            'again. If hazard and exposure data doesn\'t overlap at all, it '
            'is not possible to do an analysis. Another possibility is that '
            'the layers do overlap but because they may have different '
            'spatial references, they appear to be disjointed. If this is the '
            'case, try to turn on reproject on-the-fly in QGIS.')
        raise NoFeaturesInExtentError(myMessage)

    myKeywordIO = KeywordIO()
    if extra_keywords is None:
        extra_keywords = {}
    extra_keywords['had multipart polygon'] = myHasMultipart
    myKeywordIO.copy_keywords(
        layer, myFilename, extra_keywords=extra_keywords)
    myBaseName = '%s clipped' % layer.name()
    myLayer = QgsVectorLayer(myFilename, myBaseName, 'ogr')

    return myLayer
Example #52
0
    def processAlgorithm(self, parameters, context, feedback):
        layer = QgsProcessingUtils.mapLayerFromString(
            self.getParameterValue(self.INPUT_VECTOR), context)
        startPoint = self.getParameterValue(self.START_POINT)
        endPoints = QgsProcessingUtils.mapLayerFromString(
            self.getParameterValue(self.END_POINTS), context)
        strategy = self.getParameterValue(self.STRATEGY)

        directionFieldName = self.getParameterValue(self.DIRECTION_FIELD)
        forwardValue = self.getParameterValue(self.VALUE_FORWARD)
        backwardValue = self.getParameterValue(self.VALUE_BACKWARD)
        bothValue = self.getParameterValue(self.VALUE_BOTH)
        defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
        bothValue = self.getParameterValue(self.VALUE_BOTH)
        defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
        speedFieldName = self.getParameterValue(self.SPEED_FIELD)
        defaultSpeed = self.getParameterValue(self.DEFAULT_SPEED)
        tolerance = self.getParameterValue(self.TOLERANCE)

        fields = QgsFields()
        fields.append(QgsField('start', QVariant.String, '', 254, 0))
        fields.append(QgsField('end', QVariant.String, '', 254, 0))
        fields.append(QgsField('cost', QVariant.Double, '', 20, 7))

        feat = QgsFeature()
        feat.setFields(fields)

        writer = self.getOutputFromName(self.OUTPUT_LAYER).getVectorWriter(
            fields, QgsWkbTypes.LineString, layer.crs(), context)

        tmp = startPoint.split(',')
        startPoint = QgsPointXY(float(tmp[0]), float(tmp[1]))

        directionField = -1
        if directionFieldName is not None:
            directionField = layer.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName is not None:
            speedField = layer.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(layer, directionField, forwardValue,
                                          backwardValue, bothValue,
                                          defaultDirection)

        distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(
            distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField, defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)
            multiplier = 3600

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(
            iface.mapCanvas().mapSettings().destinationCrs(), True, tolerance)

        feedback.pushInfo(self.tr('Loading end points...'))
        request = QgsFeatureRequest()
        request.setFlags(request.flags()
                         ^ QgsFeatureRequest.SubsetOfAttributes)
        features = QgsProcessingUtils.getFeatures(endPoints, context, request)
        count = QgsProcessingUtils.featureCount(endPoints, context)

        points = [startPoint]
        for f in features:
            points.append(f.geometry().asPoint())

        feedback.pushInfo(self.tr('Building graph...'))
        snappedPoints = director.makeGraph(builder, points)

        feedback.pushInfo(self.tr('Calculating shortest paths...'))
        graph = builder.graph()

        idxStart = graph.findVertex(snappedPoints[0])
        tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
        route = []

        total = 100.0 / count
        for i in range(1, count + 1):
            idxEnd = graph.findVertex(snappedPoints[i])

            if tree[idxEnd] == -1:
                msg = self.tr(
                    'There is no route from start point ({}) to end point ({}).'
                    .format(startPoint.toString(), points[i].toString()))
                feedback.setProgressText(msg)
                QgsMessageLog.logMessage(msg, self.tr('Processing'),
                                         QgsMessageLog.WARNING)
                continue

            cost = 0.0
            current = idxEnd
            while current != idxStart:
                cost += graph.edge(tree[current]).cost(0)
                route.append(
                    graph.vertex(graph.edge(tree[current]).inVertex()).point())
                current = graph.edge(tree[current]).outVertex()

            route.append(snappedPoints[0])
            route.reverse()

            geom = QgsGeometry.fromPolyline(route)
            feat.setGeometry(geom)
            feat['start'] = startPoint.toString()
            feat['end'] = points[i].toString()
            feat['cost'] = cost / multiplier
            writer.addFeature(feat)

            route[:] = []

            feedback.setProgress(int(i * total))

        del writer
    def run(self):
        """Run any post processors requested by the impact function.

        Args:
            None

        Returns:
            None

        Raises:
            None
        """
        try:
            myRequestedPostProcessors = self.functionParams['postprocessors']
            myPostProcessors = get_postprocessors(myRequestedPostProcessors)
        except (TypeError, KeyError):
            # TypeError is for when functionParams is none
            # KeyError is for when ['postprocessors'] is unavailable
            myPostProcessors = {}
        LOGGER.debug('Running this postprocessors: ' + str(myPostProcessors))

        myFeatureNameAttribute = self.aggregator.attributes[
            self.aggregator.defaults['AGGR_ATTR_KEY']]
        if myFeatureNameAttribute is None:
            self.attributeTitle = self.tr('Aggregation unit')
        else:
            self.attributeTitle = myFeatureNameAttribute

        myNameFieldIndex = self.aggregator.layer.fieldNameIndex(
            self.attributeTitle)
        mySumFieldIndex = self.aggregator.layer.fieldNameIndex(
            self._sumFieldName())

        myFemaleRatioIsVariable = False
        myFemRatioFieldIndex = None
        myFemaleRatio = None

        if 'Gender' in myPostProcessors:
            # look if we need to look for a variable female ratio in a layer
            try:
                myFemRatioField = self.aggregator.attributes[
                    self.aggregator.defaults['FEM_RATIO_ATTR_KEY']]
                myFemRatioFieldIndex = self.aggregator.layer.fieldNameIndex(
                    myFemRatioField)

                # something went wrong finding the female ratio field,
                # use defaults from below except block
                if myFemRatioFieldIndex == -1:
                    raise KeyError

                myFemaleRatioIsVariable = True

            except KeyError:
                try:
                    myFemaleRatio = self.keywordIO.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.defaults['FEM_RATIO_KEY'])
                except KeywordNotFoundError:
                    myFemaleRatio = self.aggregator.defaults['FEM_RATIO']

        # iterate zone features
        myRequest = QgsFeatureRequest()
        myRequest.setFlags(QgsFeatureRequest.NoGeometry)
        myProvider = self.aggregator.layer.dataProvider()
        # start data retrieval: fetch no geometry and all attributes for each
        # feature
        myPolygonIndex = 0
        for myFeature in myProvider.getFeatures(myRequest):
            # if a feature has no field called
            if myNameFieldIndex == -1:
                myZoneName = str(myFeature.id())
            else:
                myZoneName = myFeature[myNameFieldIndex]

            # create dictionary of attributes to pass to postprocessor
            myGeneralParams = {'target_field': self.aggregator.targetField,
                               'function_params': self.functionParams}

            if self.aggregator.statisticsType == 'class_count':
                myGeneralParams['impact_classes'] = (
                    self.aggregator.statisticsClasses)
            elif self.aggregator.statisticsType == 'sum':
                myImpactTotal = myFeature[mySumFieldIndex]
                myGeneralParams['impact_total'] = myImpactTotal

            try:
                myGeneralParams['impact_attrs'] = (
                    self.aggregator.impactLayerAttributes[myPolygonIndex])
            except IndexError:
                # rasters and attributeless vectors have no attributes
                myGeneralParams['impact_attrs'] = None

            for myKey, myValue in myPostProcessors.iteritems():
                myParameters = myGeneralParams
                try:
                    # look if params are available for this postprocessor
                    myParameters.update(
                        self.functionParams['postprocessors'][myKey]['params'])
                except KeyError:
                    pass

                if myKey == 'Gender':
                    if myFemaleRatioIsVariable:
                        myFemaleRatio = myFeature[myFemRatioFieldIndex]
                        if myFemaleRatio is None:
                            myFemaleRatio = self.aggregator.defaults[
                                'FEM_RATIO']
                        LOGGER.debug(myFemaleRatio)
                    myParameters['female_ratio'] = myFemaleRatio

                myValue.setup(myParameters)
                myValue.process()
                myResults = myValue.results()
                myValue.clear()
#                LOGGER.debug(myResults)
                try:
                    self.postProcessingOutput[myKey].append(
                        (myZoneName, myResults))
                except KeyError:
                    self.postProcessingOutput[myKey] = []
                    self.postProcessingOutput[myKey].append(
                        (myZoneName, myResults))
            # increment the index
            myPolygonIndex += 1
Example #54
0
    def run(self):
        """Run any post processors requested by the impact function.
        """
        try:
            requested_postprocessors = self.function_parameters["postprocessors"]
            postprocessors = get_postprocessors(requested_postprocessors)
        except (TypeError, KeyError):
            # TypeError is for when function_parameters is none
            # KeyError is for when ['postprocessors'] is unavailable
            postprocessors = {}

        feature_names_attribute = self.aggregator.attributes[self.aggregator.get_default_keyword("AGGR_ATTR_KEY")]
        if feature_names_attribute is None:
            self.attribute_title = self.tr("Aggregation unit")
        else:
            self.attribute_title = feature_names_attribute

        name_filed_index = self.aggregator.layer.fieldNameIndex(self.attribute_title)
        sum_field_index = self.aggregator.layer.fieldNameIndex(self._sum_field_name())

        user_defined_female_ratio = False
        female_ratio_field_index = None
        female_ratio = None
        user_defined_age_ratios = False
        youth_ratio_field_index = None
        youth_ratio = None
        adult_ratio_field_index = None
        adult_ratio = None
        elderly_ratio_field_index = None
        elderly_ratio = None

        if "Gender" in postprocessors:
            # look if we need to look for a variable female ratio in a layer
            try:
                female_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword("FEMALE_RATIO_ATTR_KEY")
                ]
                female_ratio_field_index = self.aggregator.layer.fieldNameIndex(female_ratio_field)

                # something went wrong finding the female ratio field,
                # use defaults from below except block
                if female_ratio_field_index == -1:
                    raise KeyError

                user_defined_female_ratio = True

            except KeyError:
                try:
                    female_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer, self.aggregator.get_default_keyword("FEMALE_RATIO_KEY")
                    )
                except KeywordNotFoundError:
                    female_ratio = self.aggregator.get_default_keyword("FEMALE_RATIO")

        if "Age" in postprocessors:
            # look if we need to look for a variable age ratio in a layer
            try:
                youth_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword("YOUTH_RATIO_ATTR_KEY")
                ]
                youth_ratio_field_index = self.aggregator.layer.fieldNameIndex(youth_ratio_field)
                adult_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword("ADULT_RATIO_ATTR_KEY")
                ]
                adult_ratio_field_index = self.aggregator.layer.fieldNameIndex(adult_ratio_field)
                elderly_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword("ELDERLY_RATIO_ATTR_KEY")
                ]
                elderly_ratio_field_index = self.aggregator.layer.fieldNameIndex(elderly_ratio_field)
                # something went wrong finding the youth ratio field,
                # use defaults from below except block
                if youth_ratio_field_index == -1 or adult_ratio_field_index == -1 or elderly_ratio_field_index == -1:
                    raise KeyError

                user_defined_age_ratios = True

            except KeyError:
                try:
                    youth_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer, self.aggregator.get_default_keyword("YOUTH_RATIO_KEY")
                    )
                    adult_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer, self.aggregator.get_default_keyword("ADULT_RATIO_KEY")
                    )
                    elderly_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer, self.aggregator.get_default_keyword("ELDERLY_RATIO_KEY")
                    )

                except KeywordNotFoundError:
                    youth_ratio = self.aggregator.get_default_keyword("YOUTH_RATIO")
                    adult_ratio = self.aggregator.get_default_keyword("ADULT_RATIO")
                    elderly_ratio = self.aggregator.get_default_keyword("ELDERLY_RATIO")

        # iterate zone features
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.NoGeometry)
        provider = self.aggregator.layer.dataProvider()
        # start data retrieval: fetch no geometry and all attributes for each
        # feature
        polygon_index = 0
        for feature in provider.getFeatures(request):
            # if a feature has no field called
            if name_filed_index == -1:
                zone_name = str(feature.id())
            else:
                zone_name = feature[name_filed_index]
            if isinstance(zone_name, QPyNullVariant):
                # proper format for i186
                zone_name = tr("Unnamed Area %(feature_id)s") % {"feature_id": str(feature.id())}

            # create dictionary of attributes to pass to postprocessor
            general_params = {"target_field": self.aggregator.target_field, "function_params": self.function_parameters}

            impact_total = feature[sum_field_index]
            general_params["impact_total"] = impact_total

            try:
                general_params["impact_attrs"] = self.aggregator.impact_layer_attributes[polygon_index]
            except IndexError:
                # rasters and attributeless vectors have no attributes
                general_params["impact_attrs"] = None
            for key, value in postprocessors.iteritems():
                parameters = general_params
                user_parameters = self.function_parameters["postprocessors"][key]
                user_parameters = dict(
                    [(user_parameter.name, user_parameter.value) for user_parameter in user_parameters]
                )
                try:
                    # user parameters override default parameters
                    parameters.update(user_parameters)
                except KeyError:
                    pass

                if key == "Gender":
                    if user_defined_female_ratio:
                        female_ratio = feature[female_ratio_field_index]
                        if female_ratio is None:
                            female_ratio = self.aggregator.defaults["FEMALE_RATIO"]
                            LOGGER.warning(
                                "Data Driven Female ratio "
                                "incomplete, using defaults for"
                                " aggregation unit"
                                " %s" % feature.id
                            )

                    parameters["female_ratio"] = female_ratio

                if key == "Age":
                    if user_defined_age_ratios:
                        youth_ratio = feature[youth_ratio_field_index]
                        adult_ratio = feature[adult_ratio_field_index]
                        elderly_ratio = feature[elderly_ratio_field_index]
                        if youth_ratio is None or adult_ratio is None or elderly_ratio is None:
                            LOGGER.debug("--- only default age ratios used ---")
                            youth_ratio = self.aggregator.defaults["YOUTH_RATIO"]
                            adult_ratio = self.aggregator.defaults["ADULT_RATIO"]
                            elderly_ratio = self.aggregator.defaults["ELDERLY_RATIO"]
                            LOGGER.warning(
                                "Data Driven Age ratios "
                                "incomplete, using defaults for"
                                " aggregation unit"
                                " %s" % feature.id
                            )

                    parameters["youth_ratio"] = youth_ratio
                    parameters["adult_ratio"] = adult_ratio
                    parameters["elderly_ratio"] = elderly_ratio

                if key == "BuildingType" or key == "RoadType":
                    if key == "BuildingType":
                        key_attribute = self.keyword_io.read_keywords(
                            self.aggregator.exposure_layer, "structure_class_field"
                        )
                    elif key == "RoadType":
                        key_attribute = self.keyword_io.read_keywords(
                            self.aggregator.exposure_layer, "road_class_field"
                        )
                    else:
                        try:
                            key_attribute = self.keyword_io.read_keywords(
                                self.aggregator.exposure_layer, "key_attribute"
                            )
                        except KeywordNotFoundError:
                            # use 'type' as default
                            key_attribute = "type"

                    parameters["key_attribute"] = key_attribute
                    LOGGER.debug("key_attribute: %s", key_attribute)

                    value_map = self.keyword_io.read_keywords(self.aggregator.exposure_layer, "value_mapping")
                    parameters["value_mapping"] = value_map

                try:
                    value.setup(parameters)
                    value.process()
                    results = value.results()
                    value.clear()
                    if key not in self.output:
                        self.output[key] = []
                    self.output[key].append([zone_name, results])

                except PostProcessorError as e:
                    message = m.Message(
                        m.Heading(self.tr("%s postprocessor problem" % key), **styles.DETAILS_STYLE),
                        m.Paragraph(self.tr(str(e))),
                    )
                    self.error_message = message
            # increment the index
            polygon_index += 1
        self.remove_empty_columns()
        self.remove_empty_lines()
Example #55
0
    def getMatchingFeatures(self, geometry, contains, singleSelect):
        newFeatures = []
        if geometry.type() != QgsWkbTypes.PolygonGeometry:
            return newFeatures

        layer = self.canvas.currentLayer()
        if layer is None:
            return newFeatures

        selectGeomTrans = QgsGeometry(geometry)
        try:
            ct = QgsCoordinateTransform(self.canvas.mapSettings().destinationCrs(), layer.crs(), QgsProject.instance())
            if not ct.isShortCircuited() and selectGeomTrans.type() == QgsWkbTypes.PolygonGeometry:
                poly = selectGeomTrans.asPolygon()
                if len(poly) == 1 and len(poly[0]) == 5:
                    ringIn = poly[0]
                    ringOut = []
                    ringOut.append(ringIn[0])
                    i = 1
                    for j in range(1, 5):
                        v = QgsVector((ringIn[j] - ringIn[j - 1]) / 10.0)
                        for k in range(9):
                            ringOut.append(ringOut[i - 1] + v)
                            i += 1
                        ringOut.append(ringIn[j])
                        i += 1
                    selectGeomTrans = QgsGeometry.fromPolygonXY([ringOut])

            selectGeomTrans.transform(ct)
        except QgsCsException as e:
            QgsMessageLog.logMessage("Selection extends beyond layer's coordinate system")
            return newFeatures

        context = QgsRenderContext.fromMapSettings(self.canvas.mapSettings())
        context.expressionContext().appendScope(QgsExpressionContextUtils.layerScope(layer))

        r = None
        if layer.renderer():
            r = layer.renderer().clone()
            r.startRender(context, layer.fields())

        request = QgsFeatureRequest()
        request.setFilterRect(selectGeomTrans.boundingBox())
        request.setFlags(QgsFeatureRequest.ExactIntersect)

        if r:
            request.setSubsetOfAttributes(r.usedAttributes(context), layer.fields())
        else:
            request.setSubsetOfAttributes([])

        closestFeatureId = 0
        foundSingleFeature = False
        closestFeatureDist = sys.float_info.max
        for f in layer.getFeatures(request):
            context.expressionContext().setFeature(f)
            if r and  not r.willRenderFeature(f, context):
                continue

            g = f.geometry()
            if contains:
                if not selectGeomTrans.contains(g):
                    continue

            else:
                if not selectGeomTrans.intersects(g):
                    continue

            if singleSelect:
                foundSingleFeature = True
                distance = g.distance(selectGeomTrans)
                if distance <= closestFeatureDist:
                    closestFeatureDist = distance
                    closestFeatureId = f.id()
            else:
                newFeatures.append(f.id())

        if singleSelect and foundSingleFeature:
            newFeatures.append(closestFeatureId)

        if r:
            r.stopRender(context)

        return newFeatures
def aggregation_summary(aggregate_hazard, aggregation, callback=None):
    """Compute the summary from the aggregate hazard to the analysis layer.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | aggr_id | aggr_name |

    Output layer :
    | aggr_id | aggr_name | count of affected features per exposure type

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param aggregation: The aggregation vector layer where to write statistics.
    :type aggregation: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int), 'maximum' (int) and 'step' (str).
        Defaults to None.
    :type callback: function

    :return: The new aggregation layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_2_aggregation_steps['output_layer_name']
    processing_step = summary_2_aggregation_steps['step_name']

    source_fields = aggregate_hazard.keywords['inasafe_fields']
    target_fields = aggregation.keywords['inasafe_fields']

    target_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
    ]
    check_inputs(target_compulsory_fields, target_fields)

    # Missing exposure_count_field
    source_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field,
        affected_field,
    ]
    check_inputs(source_compulsory_fields, source_fields)

    pattern = exposure_count_field['key']
    pattern = pattern.replace('%s', '')
    unique_exposure = read_dynamic_inasafe_field(
        source_fields, exposure_count_field)

    absolute_values = create_absolute_values_structure(
        aggregate_hazard, ['aggregation_id'])

    flat_table = FlatTable('aggregation_id', 'exposure_class')

    aggregation_index = source_fields[aggregation_id_field['key']]

    # We want to loop over affected features only.
    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    expression = '\"%s\" = \'%s\'' % (
        affected_field['field_name'], tr('True'))
    request.setFilterExpression(expression)
    for area in aggregate_hazard.getFeatures(request):

        for key, name_field in source_fields.iteritems():
            if key.endswith(pattern):
                aggregation_id = area[aggregation_index]
                exposure_class = key.replace(pattern, '')
                value = area[name_field]
                flat_table.add_value(
                    value,
                    aggregation_id=aggregation_id,
                    exposure_class=exposure_class
                )

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = area[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(
                value,
                aggregation_id=area[aggregation_index],
            )

    shift = aggregation.fields().count()

    aggregation.startEditing()

    add_fields(
        aggregation,
        absolute_values,
        [total_affected_field],
        unique_exposure,
        affected_exposure_count_field)

    aggregation_index = target_fields[aggregation_id_field['key']]

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregation.getFeatures(request):
        aggregation_value = area[aggregation_index]
        total = 0
        for i, val in enumerate(unique_exposure):
            sum = flat_table.get_value(
                aggregation_id=aggregation_value,
                exposure_class=val
            )
            total += sum
            aggregation.changeAttributeValue(area.id(), shift + i, sum)

        aggregation.changeAttributeValue(
            area.id(), shift + len(unique_exposure), total)

        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(
                aggregation_id=aggregation_value,
            )
            target_index = shift + len(unique_exposure) + 1 + i
            aggregation.changeAttributeValue(
                area.id(), target_index, value)

    aggregation.commitChanges()

    aggregation.keywords['title'] = layer_purpose_aggregation_summary['name']
    if qgis_version() >= 21800:
        aggregation.setName(aggregation.keywords['title'])
    else:
        aggregation.setLayerName(aggregation.keywords['title'])
    aggregation.keywords['layer_purpose'] = (
        layer_purpose_aggregation_summary['key'])

    check_layer(aggregation)
    return aggregation
Example #57
0
    def run(self):
        """Run any post processors requested by the impact function.
        """
        try:
            requested_postprocessors = self.function_parameters[
                'postprocessors']
            postprocessors = get_postprocessors(requested_postprocessors)
        except (TypeError, KeyError):
            # TypeError is for when function_parameters is none
            # KeyError is for when ['postprocessors'] is unavailable
            postprocessors = {}

        feature_names_attribute = self.aggregator.attributes[
            self.aggregator.get_default_keyword('AGGR_ATTR_KEY')]
        if feature_names_attribute is None:
            self.attribute_title = self.tr('Aggregation unit')
        else:
            self.attribute_title = feature_names_attribute

        name_filed_index = self.aggregator.layer.fieldNameIndex(
            self.attribute_title)
        sum_field_index = self.aggregator.layer.fieldNameIndex(
            self._sum_field_name())

        user_defined_female_ratio = False
        female_ratio_field_index = None
        female_ratio = None
        user_defined_age_ratios = False
        youth_ratio_field_index = None
        youth_ratio = None
        adult_ratio_field_index = None
        adult_ratio = None
        elderly_ratio_field_index = None
        elderly_ratio = None

        if 'Gender' in postprocessors:
            # look if we need to look for a variable female ratio in a layer
            try:
                female_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'FEMALE_RATIO_ATTR_KEY')]
                female_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(female_ratio_field)

                # something went wrong finding the female ratio field,
                # use defaults from below except block
                if female_ratio_field_index == -1:
                    raise KeyError

                user_defined_female_ratio = True

            except KeyError:
                try:
                    female_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'FEMALE_RATIO_KEY'))
                except KeywordNotFoundError:
                    female_ratio = \
                        self.aggregator.get_default_keyword('FEMALE_RATIO')

        if 'Age' in postprocessors:
            # look if we need to look for a variable age ratio in a layer
            try:
                youth_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'YOUTH_RATIO_ATTR_KEY')]
                youth_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(youth_ratio_field)
                adult_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'ADULT_RATIO_ATTR_KEY')]
                adult_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(adult_ratio_field)
                elderly_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'ELDERLY_RATIO_ATTR_KEY')]
                elderly_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(elderly_ratio_field)
                # something went wrong finding the youth ratio field,
                # use defaults from below except block
                if (youth_ratio_field_index == -1
                        or adult_ratio_field_index == -1
                        or elderly_ratio_field_index == -1):
                    raise KeyError

                user_defined_age_ratios = True

            except KeyError:
                try:
                    youth_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword('YOUTH_RATIO_KEY'))
                    adult_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword('ADULT_RATIO_KEY'))
                    elderly_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'ELDERLY_RATIO_KEY'))

                except KeywordNotFoundError:
                    youth_ratio = \
                        self.aggregator.get_default_keyword('YOUTH_RATIO')
                    adult_ratio = \
                        self.aggregator.get_default_keyword('ADULT_RATIO')
                    elderly_ratio = \
                        self.aggregator.get_default_keyword('ELDERLY_RATIO')

        # iterate zone features
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.NoGeometry)
        provider = self.aggregator.layer.dataProvider()
        # start data retrieval: fetch no geometry and all attributes for each
        # feature
        polygon_index = 0
        for feature in provider.getFeatures(request):
            # if a feature has no field called
            if name_filed_index == -1:
                zone_name = str(feature.id())
            else:
                zone_name = feature[name_filed_index]
            if isinstance(zone_name, QPyNullVariant):
                # proper format for i186
                zone_name = tr('Unnamed Area %(feature_id)s') % {
                    'feature_id': str(feature.id())
                }

            # create dictionary of attributes to pass to postprocessor
            general_params = {
                'target_field': self.aggregator.target_field,
                'function_params': self.function_parameters
            }

            impact_total = feature[sum_field_index]
            general_params['impact_total'] = impact_total

            try:
                general_params['impact_attrs'] = (
                    self.aggregator.impact_layer_attributes[polygon_index])
            except IndexError:
                # rasters and attributeless vectors have no attributes
                general_params['impact_attrs'] = None
            for key, value in postprocessors.iteritems():
                parameters = general_params
                user_parameters = self.function_parameters['postprocessors'][
                    key]
                user_parameters = dict([(user_parameter.name,
                                         user_parameter.value)
                                        for user_parameter in user_parameters])
                try:
                    # user parameters override default parameters
                    parameters.update(user_parameters)
                except KeyError:
                    pass

                if key == 'Gender':
                    if user_defined_female_ratio:
                        female_ratio = feature[female_ratio_field_index]
                        if female_ratio is None:
                            female_ratio = self.aggregator.defaults[
                                'FEMALE_RATIO']
                            LOGGER.warning('Data Driven Female ratio '
                                           'incomplete, using defaults for'
                                           ' aggregation unit'
                                           ' %s' % feature.id)

                    parameters['female_ratio'] = female_ratio

                if key == 'Age':
                    if user_defined_age_ratios:
                        youth_ratio = feature[youth_ratio_field_index]
                        adult_ratio = feature[adult_ratio_field_index]
                        elderly_ratio = feature[elderly_ratio_field_index]
                        if (youth_ratio is None or adult_ratio is None
                                or elderly_ratio is None):
                            LOGGER.debug(
                                '--- only default age ratios used ---')
                            youth_ratio = self.aggregator.defaults[
                                'YOUTH_RATIO']
                            adult_ratio = self.aggregator.defaults[
                                'ADULT_RATIO']
                            elderly_ratio = self.aggregator.defaults[
                                'ELDERLY_RATIO']
                            LOGGER.warning('Data Driven Age ratios '
                                           'incomplete, using defaults for'
                                           ' aggregation unit'
                                           ' %s' % feature.id)

                    parameters['youth_ratio'] = youth_ratio
                    parameters['adult_ratio'] = adult_ratio
                    parameters['elderly_ratio'] = elderly_ratio

                if key == 'BuildingType' or key == 'RoadType':
                    if key == 'BuildingType':
                        key_attribute = self.keyword_io.read_keywords(
                            self.aggregator.exposure_layer,
                            'structure_class_field')
                    elif key == 'RoadType':
                        key_attribute = self.keyword_io.read_keywords(
                            self.aggregator.exposure_layer, 'road_class_field')
                    else:
                        try:
                            key_attribute = self.keyword_io.read_keywords(
                                self.aggregator.exposure_layer,
                                'key_attribute')
                        except KeywordNotFoundError:
                            # use 'type' as default
                            key_attribute = 'type'

                    parameters['key_attribute'] = key_attribute
                    LOGGER.debug('key_attribute: %s', key_attribute)

                    value_map = self.keyword_io.read_keywords(
                        self.aggregator.exposure_layer, 'value_mapping')
                    parameters['value_mapping'] = value_map

                try:
                    value.setup(parameters)
                    value.process()
                    results = value.results()
                    value.clear()
                    if key not in self.output:
                        self.output[key] = []
                    self.output[key].append([zone_name, results])

                except PostProcessorError as e:
                    message = m.Message(
                        m.Heading(self.tr('%s postprocessor problem' % key),
                                  **styles.DETAILS_STYLE),
                        m.Paragraph(self.tr(str(e))))
                    self.error_message = message
            # increment the index
            polygon_index += 1
        self.remove_empty_columns()
        self.remove_empty_lines()
    def run(self):
        """Run any post processors requested by the impact function.
        """
        try:
            requested_postprocessors = self.function_parameters[
                'postprocessors']
            postprocessors = get_postprocessors(requested_postprocessors)
        except (TypeError, KeyError):
            # TypeError is for when function_parameters is none
            # KeyError is for when ['postprocessors'] is unavailable
            postprocessors = {}

        feature_names_attribute = self.aggregator.attributes[
            self.aggregator.get_default_keyword('AGGR_ATTR_KEY')]
        if feature_names_attribute is None:
            self.attribute_title = self.tr('Aggregation unit')
        else:
            self.attribute_title = feature_names_attribute

        name_filed_index = self.aggregator.layer.fieldNameIndex(
            self.attribute_title)
        sum_field_index = self.aggregator.layer.fieldNameIndex(
            self._sum_field_name())

        user_defined_female_ratio = False
        female_ratio_field_index = None
        female_ratio = None
        user_defined_age_ratios = False
        youth_ratio_field_index = None
        youth_ratio = None
        adult_ratio_field_index = None
        adult_ratio = None
        elderly_ratio_field_index = None
        elderly_ratio = None

        if 'Gender' in postprocessors:
            # look if we need to look for a variable female ratio in a layer
            try:
                female_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'FEMALE_RATIO_ATTR_KEY')]
                female_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(female_ratio_field)

                # something went wrong finding the female ratio field,
                # use defaults from below except block
                if female_ratio_field_index == -1:
                    raise KeyError

                user_defined_female_ratio = True

            except KeyError:
                try:
                    female_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'FEMALE_RATIO_KEY'))
                except KeywordNotFoundError:
                    female_ratio = \
                        self.aggregator.get_default_keyword('FEMALE_RATIO')

        if 'Age' in postprocessors:
            # look if we need to look for a variable age ratio in a layer
            try:
                youth_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'YOUTH_RATIO_ATTR_KEY')]
                youth_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(youth_ratio_field)
                adult_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'ADULT_RATIO_ATTR_KEY')]
                adult_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(adult_ratio_field)
                elderly_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'ELDERLY_RATIO_ATTR_KEY')]
                elderly_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(elderly_ratio_field)
                # something went wrong finding the youth ratio field,
                # use defaults from below except block
                if (youth_ratio_field_index == -1 or
                        adult_ratio_field_index == -1 or
                        elderly_ratio_field_index == -1):
                    raise KeyError

                user_defined_age_ratios = True

            except KeyError:
                try:
                    youth_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'YOUTH_RATIO_KEY'))
                    adult_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'ADULT_RATIO_KEY'))
                    elderly_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'ELDERLY_RATIO_KEY'))

                except KeywordNotFoundError:
                    youth_ratio = \
                        self.aggregator.get_default_keyword('YOUTH_RATIO')
                    adult_ratio = \
                        self.aggregator.get_default_keyword('ADULT_RATIO')
                    elderly_ratio = \
                        self.aggregator.get_default_keyword('ELDERLY_RATIO')

        if 'BuildingType' or 'RoadType' in postprocessors:
            try:
                key_attribute = self.keyword_io.read_keywords(
                    self.aggregator.exposure_layer, 'key_attribute')
            except KeywordNotFoundError:
                # use 'type' as default
                key_attribute = 'type'

        # iterate zone features
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.NoGeometry)
        provider = self.aggregator.layer.dataProvider()
        # start data retrieval: fetch no geometry and all attributes for each
        # feature
        polygon_index = 0
        for feature in provider.getFeatures(request):
            # if a feature has no field called
            if name_filed_index == -1:
                zone_name = str(feature.id())
            else:
                zone_name = feature[name_filed_index]
            if isinstance(zone_name, QPyNullVariant):
                zone_name = 'Unnamed Area %s' % str(feature.id())

            # create dictionary of attributes to pass to postprocessor
            general_params = {
                'target_field': self.aggregator.target_field,
                'function_params': self.function_parameters}

            if self.aggregator.statistics_type == 'class_count':
                general_params['impact_classes'] = (
                    self.aggregator.statistics_classes)
            elif self.aggregator.statistics_type == 'sum':
                impact_total = feature[sum_field_index]
                general_params['impact_total'] = impact_total

            try:
                general_params['impact_attrs'] = (
                    self.aggregator.impact_layer_attributes[polygon_index])
            except IndexError:
                # rasters and attributeless vectors have no attributes
                general_params['impact_attrs'] = None
            for key, value in postprocessors.iteritems():
                parameters = general_params
                user_parameters = self.function_parameters[
                    'postprocessors'][key]
                user_parameters = dict(
                    [(user_parameter.name, user_parameter.value) for
                     user_parameter in user_parameters])
                try:
                    # user parameters override default parameters
                    parameters.update(user_parameters)
                except KeyError:
                    pass

                if key == 'Gender':
                    if user_defined_female_ratio:
                        female_ratio = feature[female_ratio_field_index]
                        if female_ratio is None:
                            female_ratio = self.aggregator.defaults[
                                'FEMALE_RATIO']
                            LOGGER.warning('Data Driven Female ratio '
                                           'incomplete, using defaults for'
                                           ' aggregation unit'
                                           ' %s' % feature.id)

                    parameters['female_ratio'] = female_ratio

                if key == 'Age':
                    if user_defined_age_ratios:
                        youth_ratio = feature[youth_ratio_field_index]
                        adult_ratio = feature[adult_ratio_field_index]
                        elderly_ratio = feature[elderly_ratio_field_index]
                        if (youth_ratio is None or
                                adult_ratio is None or
                                elderly_ratio is None):
                            LOGGER.debug(
                                '--- only default age ratios used ---')
                            youth_ratio = self.aggregator.defaults[
                                'YOUTH_RATIO']
                            adult_ratio = self.aggregator.defaults[
                                'ADULT_RATIO']
                            elderly_ratio = self.aggregator.defaults[
                                'ELDERLY_RATIO']
                            LOGGER.warning('Data Driven Age ratios '
                                           'incomplete, using defaults for'
                                           ' aggregation unit'
                                           ' %s' % feature.id)

                    parameters['youth_ratio'] = youth_ratio
                    parameters['adult_ratio'] = adult_ratio
                    parameters['elderly_ratio'] = elderly_ratio

                if key == 'BuildingType' or key == 'RoadType':
                    # TODO: Fix this might be referenced before assignment
                    parameters['key_attribute'] = key_attribute
                try:
                    value.setup(parameters)
                    value.process()
                    results = value.results()
                    value.clear()
                    if key not in self.output:
                        self.output[key] = []
                    self.output[key].append(
                        (zone_name, results))

                except PostProcessorError as e:
                    message = m.Message(
                        m.Heading(self.tr('%s postprocessor problem' % key),
                                  **styles.DETAILS_STYLE),
                        m.Paragraph(self.tr(str(e))))
                    self.error_message = message
            # increment the index
            polygon_index += 1
        self.remove_empty_columns()
Example #59
0
    def fetch_values_from_layer(self):  # pylint: disable=too-many-locals, too-many-branches, too-many-statements
        """
        (Re)fetches plot values from the source layer.
        """

        # Note: we keep things nice and efficient and only iterate a single time over the layer!

        if not self.context_generator:
            context = QgsExpressionContext()
            context.appendScopes(
                QgsExpressionContextUtils.globalProjectLayerScopes(
                    self.source_layer))
        else:
            context = self.context_generator.createExpressionContext()
            # add a new scope corresponding to the source layer -- this will potentially overwrite any other
            # layer scopes which may be present in the context (e.g. from atlas layers), but we need to ensure
            # that source layer fields and attributes are present in the context
            context.appendScope(
                self.source_layer.createExpressionContextScope())

        self.settings.data_defined_properties.prepare(context)

        self.fetch_layout_properties(context)

        def add_source_field_or_expression(field_or_expression):
            field_index = self.source_layer.fields().lookupField(
                field_or_expression)
            if field_index == -1:
                expression = QgsExpression(field_or_expression)
                if not expression.hasParserError():
                    expression.prepare(context)
                return expression, expression.needsGeometry(
                ), expression.referencedColumns()

            return None, False, {field_or_expression}

        x_expression, x_needs_geom, x_attrs = add_source_field_or_expression(self.settings.properties['x_name']) if \
            self.settings.properties[
                'x_name'] else (None, False, set())
        y_expression, y_needs_geom, y_attrs = add_source_field_or_expression(self.settings.properties['y_name']) if \
            self.settings.properties[
                'y_name'] else (None, False, set())
        z_expression, z_needs_geom, z_attrs = add_source_field_or_expression(self.settings.properties['z_name']) if \
            self.settings.properties[
                'z_name'] else (None, False, set())
        additional_info_expression, additional_needs_geom, additional_attrs = add_source_field_or_expression(
            self.settings.layout['additional_info_expression']
        ) if self.settings.layout['additional_info_expression'] else (None,
                                                                      False,
                                                                      set())

        attrs = set().union(
            self.settings.data_defined_properties.referencedFields(), x_attrs,
            y_attrs, z_attrs, additional_attrs)

        request = QgsFeatureRequest()

        if self.settings.data_defined_properties.property(
                PlotSettings.PROPERTY_FILTER).isActive():
            expression = self.settings.data_defined_properties.property(
                PlotSettings.PROPERTY_FILTER).asExpression()
            request.setFilterExpression(expression)
            request.setExpressionContext(context)

        request.setSubsetOfAttributes(attrs, self.source_layer.fields())

        if not x_needs_geom and not y_needs_geom and not z_needs_geom and not additional_needs_geom and not self.settings.data_defined_properties.hasActiveProperties(
        ):
            request.setFlags(QgsFeatureRequest.NoGeometry)

        visible_geom_engine = None
        if self.visible_features_only and self.visible_region is not None:
            ct = QgsCoordinateTransform(
                self.visible_region.crs(), self.source_layer.crs(),
                QgsProject.instance().transformContext())
            try:
                rect = ct.transformBoundingBox(self.visible_region)
                request.setFilterRect(rect)
            except QgsCsException:
                pass
        elif self.visible_features_only and self.polygon_filter is not None:
            ct = QgsCoordinateTransform(
                self.polygon_filter.crs(), self.source_layer.crs(),
                QgsProject.instance().transformContext())
            try:
                rect = ct.transformBoundingBox(
                    self.polygon_filter.geometry.boundingBox())
                request.setFilterRect(rect)
                g = self.polygon_filter.geometry
                g.transform(ct)

                visible_geom_engine = QgsGeometry.createGeometryEngine(
                    g.constGet())
                visible_geom_engine.prepareGeometry()
            except QgsCsException:
                pass

        if self.selected_features_only:
            it = self.source_layer.getSelectedFeatures(request)
        else:
            it = self.source_layer.getFeatures(request)

        # Some plot types don't draw individual glyphs for each feature, but aggregate them instead.
        # In that case it doesn't make sense to evaluate expressions for settings like marker size or color for each
        # feature. Instead, the evaluation should be executed only once for these settings.
        aggregating = self.settings.plot_type in ['box', 'histogram']
        executed = False

        xx = []
        yy = []
        zz = []
        additional_hover_text = []
        marker_sizes = []
        colors = []
        stroke_colors = []
        stroke_widths = []
        for f in it:
            if visible_geom_engine and not visible_geom_engine.intersects(
                    f.geometry().constGet()):
                continue

            self.settings.feature_ids.append(f.id())
            context.setFeature(f)

            x = None
            if x_expression:
                x = x_expression.evaluate(context)
                if x == NULL or x is None:
                    continue
            elif self.settings.properties['x_name']:
                x = f[self.settings.properties['x_name']]
                if x == NULL or x is None:
                    continue

            y = None
            if y_expression:
                y = y_expression.evaluate(context)
                if y == NULL or y is None:
                    continue
            elif self.settings.properties['y_name']:
                y = f[self.settings.properties['y_name']]
                if y == NULL or y is None:
                    continue

            z = None
            if z_expression:
                z = z_expression.evaluate(context)
                if z == NULL or z is None:
                    continue
            elif self.settings.properties['z_name']:
                z = f[self.settings.properties['z_name']]
                if z == NULL or z is None:
                    continue

            if additional_info_expression:
                additional_hover_text.append(
                    additional_info_expression.evaluate(context))
            elif self.settings.layout['additional_info_expression']:
                additional_hover_text.append(
                    f[self.settings.layout['additional_info_expression']])

            if x is not None:
                xx.append(x)
            if y is not None:
                yy.append(y)
            if z is not None:
                zz.append(z)

            if self.settings.data_defined_properties.isActive(
                    PlotSettings.PROPERTY_MARKER_SIZE):
                default_value = self.settings.properties['marker_size']
                context.setOriginalValueVariable(default_value)
                value, _ = self.settings.data_defined_properties.valueAsDouble(
                    PlotSettings.PROPERTY_MARKER_SIZE, context, default_value)
                marker_sizes.append(value)
            if self.settings.data_defined_properties.isActive(
                    PlotSettings.PROPERTY_STROKE_WIDTH):
                default_value = self.settings.properties['marker_width']
                context.setOriginalValueVariable(default_value)
                value, _ = self.settings.data_defined_properties.valueAsDouble(
                    PlotSettings.PROPERTY_STROKE_WIDTH, context, default_value)
                stroke_widths.append(value)

            if self.settings.data_defined_properties.isActive(
                    PlotSettings.PROPERTY_COLOR) and (not aggregating
                                                      or not executed):
                default_value = QColor(self.settings.properties['in_color'])
                value, conversion_success = self.settings.data_defined_properties.valueAsColor(
                    PlotSettings.PROPERTY_COLOR, context, default_value)
                if conversion_success:
                    # We were given a valid color specification, use that color
                    colors.append(value.name())
                else:
                    try:
                        # Attempt to interpret the value as a list of color specifications
                        value_list = self.settings.data_defined_properties.value(
                            PlotSettings.PROPERTY_COLOR, context)
                        color_list = [
                            QgsSymbolLayerUtils.decodeColor(item).name()
                            for item in value_list
                        ]
                        colors.extend(color_list)
                    except TypeError:
                        # Not a list of color specifications, use the default color instead
                        colors.append(default_value.name())

            if self.settings.data_defined_properties.isActive(
                    PlotSettings.PROPERTY_STROKE_COLOR) and (not aggregating
                                                             or not executed):
                default_value = QColor(self.settings.properties['out_color'])
                value, conversion_success = self.settings.data_defined_properties.valueAsColor(
                    PlotSettings.PROPERTY_STROKE_COLOR, context, default_value)
                if conversion_success:
                    # We were given a valid color specification, use that color
                    stroke_colors.append(value.name())
                else:
                    try:
                        # Attempt to interpret the value as a list of color specifications
                        value_list = self.settings.data_defined_properties.value(
                            PlotSettings.PROPERTY_STROKE_COLOR, context)
                        color_list = [
                            QgsSymbolLayerUtils.decodeColor(item).name()
                            for item in value_list
                        ]
                        stroke_colors.extend(color_list)
                    except TypeError:
                        # Not a list of color specifications, use the default color instead
                        stroke_colors.append(default_value.name())

            executed = True

        self.settings.additional_hover_text = additional_hover_text
        self.settings.x = xx
        self.settings.y = yy
        self.settings.z = zz
        if marker_sizes:
            self.settings.data_defined_marker_sizes = marker_sizes
        if colors:
            self.settings.data_defined_colors = colors
        if stroke_colors:
            self.settings.data_defined_stroke_colors = stroke_colors
        if stroke_widths:
            self.settings.data_defined_stroke_widths = stroke_widths
Example #60
0
    def processAlgorithm(self, feedback):
        layer = dataobjects.getObjectFromUri(
            self.getParameterValue(self.INPUT_VECTOR))
        startPoints = dataobjects.getObjectFromUri(
            self.getParameterValue(self.START_POINTS))
        strategy = self.getParameterValue(self.STRATEGY)
        travelCost = self.getParameterValue(self.TRAVEL_COST)

        directionFieldName = self.getParameterValue(self.DIRECTION_FIELD)
        forwardValue = self.getParameterValue(self.VALUE_FORWARD)
        backwardValue = self.getParameterValue(self.VALUE_BACKWARD)
        bothValue = self.getParameterValue(self.VALUE_BOTH)
        defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
        bothValue = self.getParameterValue(self.VALUE_BOTH)
        defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
        speedFieldName = self.getParameterValue(self.SPEED_FIELD)
        defaultSpeed = self.getParameterValue(self.DEFAULT_SPEED)
        tolerance = self.getParameterValue(self.TOLERANCE)

        fields = QgsFields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('start', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        writerPoints = self.getOutputFromName(
            self.OUTPUT_POINTS).getVectorWriter(
                fields,
                QgsWkbTypes.MultiPoint,
                layer.crs())

        writerPolygons = self.getOutputFromName(
            self.OUTPUT_POLYGON).getVectorWriter(
                fields,
                QgsWkbTypes.Polygon,
                layer.crs())

        directionField = -1
        if directionFieldName is not None:
            directionField = layer.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName is not None:
            speedField = layer.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(layer,
                                          directionField,
                                          forwardValue,
                                          backwardValue,
                                          bothValue,
                                          defaultDirection)

        distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField,
                                               defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(iface.mapCanvas().mapSettings().destinationCrs(),
                                  iface.mapCanvas().hasCrsTransformEnabled(),
                                  tolerance)

        feedback.pushInfo(self.tr('Loading start points...'))
        request = QgsFeatureRequest()
        request.setFlags(request.flags() ^ QgsFeatureRequest.SubsetOfAttributes)
        features = vector.features(startPoints, request)
        points = []
        for f in features:
            points.append(f.geometry().asPoint())

        feedback.pushInfo(self.tr('Building graph...'))
        snappedPoints = director.makeGraph(builder, points)

        feedback.pushInfo(self.tr('Calculating service areas...'))
        graph = builder.graph()

        vertices = []
        upperBoundary = []
        lowerBoundary = []
        total = 100.0 / len(snappedPoints)
        for i, p in enumerate(snappedPoints):
            idxStart = graph.findVertex(snappedPoints[i])
            origPoint = points[i].toString()

            tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
            for j, v in enumerate(cost):
                if v > travelCost and tree[j] != -1:
                    vertexId = graph.edge(tree[j]).outVertex()
                    if cost[vertexId] <= travelCost:
                        vertices.append(j)

            for j in vertices:
                upperBoundary.append(graph.vertex(graph.edge(tree[j]).inVertex()).point())
                lowerBoundary.append(graph.vertex(graph.edge(tree[j]).outVertex()).point())

            geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
            geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)

            feat.setGeometry(geomUpper)
            feat['type'] = 'upper'
            feat['start'] = origPoint
            writerPoints.addFeature(feat)

            feat.setGeometry(geomLower)
            feat['type'] = 'lower'
            feat['start'] = origPoint
            writerPoints.addFeature(feat)

            upperBoundary.append(startPoint)
            lowerBoundary.append(startPoint)
            geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
            geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)

            geom = geomUpper.convexHull()
            feat.setGeometry(geom)
            feat['type'] = 'upper'
            feat['start'] = origPoint
            writerPolygons.addFeature(feat)

            geom = geomLower.convexHull()
            feat.setGeometry(geom)
            feat['type'] = 'lower'
            feat['start'] = origPoint
            writerPolygons.addFeature(feat)

            vertices[:] = []
            upperBoundary[:] = []
            lowerBoundary[:] = []

            feedback.setProgress(int(i * total))

        del writerPoints
        del writerPolygons