Exemple #1
0
class SoilErosionDockWidget(QtGui.QDockWidget, FORM_CLASS):

    closingPlugin = pyqtSignal()

    def __init__(self, parent=None):
        """Constructor."""
        super(SoilErosionDockWidget, self).__init__(parent)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

        self.settings = QSettings("CTU", "Soil_Erosion_Plugin")

        self.iface = iface

        # Define first computation, no results in map window
        self._first_computation = True

        # Read code tables
        self._factors = {}
        self._readFactorCodes()

        # Fill C combobox
        self.combobox_c.clear()
        list = self._factors['C'].list()
        self.combobox_c.addItems(list)

        # Set filters for QgsMapLayerComboBoxes
        self.shp_box_euc.setFilters(QgsMapLayerProxyModel.PolygonLayer)
        self.load_shp_euc.clicked.connect(self.onLoadShapefile)
        
        self.raster_box.setFilters(QgsMapLayerProxyModel.RasterLayer)
        self.load_raster.clicked.connect(self.onLoadRaster)

        self.shp_box_bpej.setFilters(QgsMapLayerProxyModel.PolygonLayer)
        self.load_shp_bpej.clicked.connect(self.onLoadShapefile)

        self.shp_box_lpis.setFilters(QgsMapLayerProxyModel.PolygonLayer)
        self.load_lpis.clicked.connect(self.onLoadShapefile)

        # Set functions for buttons
        self.compute_k_button.clicked.connect(self.onAddKFactor)
        self.compute_c_button.clicked.connect(self.onAddCFactor)

        self.set_button.clicked.connect(self.onCompute)


    def closeEvent(self, event):
        self.closingPlugin.emit()
        event.accept()

    def onLoadRaster(self):
        """Open 'Add raster layer dialog'."""
        sender = '{}-lastUserFilePath'.format(self.sender().objectName())
        lastUsedFilePath = self.settings.value(sender, '')

        fileName = QFileDialog.getOpenFileName(self,self.tr(u'Open Raster'), 
                                               u'{}'.format(lastUsedFilePath),
                                               QgsProviderRegistry.instance().fileRasterFilters())
        if fileName:
            self.iface.addRasterLayer(fileName, QFileInfo(fileName).baseName())
            self.settings.setValue(sender, os.path.dirname(fileName))

    def onLoadShapefile(self):
        """Open 'Add shapefile layer dialog'."""
        sender = '{}-lastUserFilePath'.format(self.sender().objectName())
        lastUsedFilePath = self.settings.value(sender, '')
        
        fileName = QFileDialog.getOpenFileName(self,self.tr(u'Open Shapefile'),
                                               u'{}'.format(lastUsedFilePath),
                                               QgsProviderRegistry.instance().fileVectorFilters())
        if fileName:
            self.iface.addVectorLayer(fileName, QFileInfo(fileName).baseName(), "ogr")
            self.settings.setValue(sender, os.path.dirname(fileName))

    def onAddKFactor(self):
        bpej_layer = self.shp_box_bpej.currentLayer()
        bpej_error = self.tr(u'layer must contain field \'BPEJ\'(format:\'X.XX.XX\' or  value \'99\' for NoData)')
        try:
            if bpej_layer is None:
                self.showError(self.tr(u'You have to choose or load BPEJ layer.\n This ') + bpej_error)
                return
            elif bpej_layer.fieldNameIndex('BPEJ') == -1:
                self.showError(self.tr(u'BPEJ ') + bpej_error)
                return
            else:
                bpej_layer.startEditing()
                self._addColumn(bpej_layer, 'K')
                bpej_layer.commitChanges()
        except:
            bpej_layer.rollBack()
            self.showError(self.tr(u'Error during add \'K\' field, please check BPEJ layer.'))
        try:
            bpej_layer.startEditing()
            idx = bpej_layer.fieldNameIndex('BPEJ')
            for feature in bpej_layer.getFeatures():
                bpej = feature.attributes()[idx]
                fid = feature.id()
                if bpej == '99':
                    k_value = None
                else:
                    k_value = self._factors['K'].value(bpej[2] + bpej[3])
                self.setFieldValue(bpej_layer, 'K', k_value, fid)
            bpej_layer.commitChanges()
            self.setBpejStyle(bpej_layer)
            self.iface.messageBar().pushMessage(self.tr('Soil Erosion Plugin'), self.tr('K factor is computed!'))
        except:
            bpej_layer.rollBack()
            self.showError(self.tr(u'BPEJ ') + bpej_error)
            return

    def onAddCFactor(self):
        lpis_layer = self.shp_box_lpis.currentLayer()
        lpis_error = self.tr(u'layer must contain field \'KULTURAKOD\'\nwith allowed codes for land:\n   R - Arable land\n' \
                     u'   T - Permanent grassland\n   S - Orchard\n   L - Forest\n   V - Vineyard\n   C - Hop-garden\n' \
                     u'At least one feature must have code \'R\'!')
        if lpis_layer is None:
            self.showError(self.tr(u'You have to choose or load LPIS layer.\nThis ') + lpis_error)
            return
        elif lpis_layer.fieldNameIndex('KULTURAKOD') == -1:
            self.showError(self.tr(u'LPIS ') + lpis_error)
            return
        else:
            lpis_layer.startEditing()
            self._addColumn(lpis_layer, 'C')
            lpis_layer.commitChanges()

        try:
            self.setLpisStyle(lpis_layer)
            lpis_layer.startEditing()
            combobox_value = self.combobox_c.currentText()
            idx = lpis_layer.fieldNameIndex('KULTURAKOD')
            for feature in lpis_layer.getFeatures():
                lpis = feature.attributes()[idx]
                fid = feature.id()
                if lpis == 'T':
                    c_value = 0.005
                elif lpis == 'S':
                    c_value = 0.45
                elif lpis == 'L':
                    c_value = 0.003
                elif lpis == 'V':
                    c_value = 0.85
                elif lpis == 'C':
                    c_value = 0.8
                elif lpis == 'R':
                    c_value = self._factors['C'].value(combobox_value)
                self.setFieldValue(lpis_layer, 'C', c_value, fid)
            lpis_layer.commitChanges()
            self.iface.messageBar().pushMessage(self.tr('Soil Erosion Plugin'), self.tr('C factor is computed!'))
        except:
            lpis_layer.rollBack()
            self.showError(self.tr(u'LPIS ') + lpis_error)
            return

    def setFieldValue(self, euc_layer, field_name, value, fid=None):
        index = euc_layer.dataProvider().fieldNameIndex(field_name)
        
        if fid is None:
            for feature in euc_layer.getFeatures():
                fid = feature.id()
                euc_layer.changeAttributeValue(fid, index, value)
        else:
            euc_layer.changeAttributeValue(fid, index, value)
        
    def _readFactorCodes(self):
        for fact in ('K','C'):
            filename = os.path.join(os.path.dirname(__file__), 'code_tables', fact + '_factor.csv')
            self._factors[fact] = ReadCSV(filename)

    def _addColumn(self, layer, name):
        for field in layer.pendingFields():
            if field.name() == name:
                return

        # column does not exists
        # TODO
        # caps & QgsVectorDataProvider.AddAttributes):
        layer.dataProvider().addAttributes(
            [QgsField(name, QVariant.Double)]
        )
        
    # def onIntersectLayers(self):
    #     euc_layer = self.shp_box_euc.currentLayer()
    #     bpej_layer = self.shp_box_bpej.currentLayer()
    #     analyzer = QgsOverlayAnalyzer()
    #     analyzer.intersection(euc_layer, bpej_layer, os.path.join(os.path.dirname(__file__), 'intersect.shp'), False, None)

    def onCompute(self):
        if hasattr(self, "computeThread"):
            return
        # remove results from map window, if it is not first computation
        if self._first_computation == False:
            try:
                QgsMapLayerRegistry.instance().removeMapLayer(self._se_layer.id())
                for field in self._euc_vector.pendingFields():
                    if field.name() == 'G':
                        field_id = self._euc_vector.fieldNameIndex(field.name())
                        fList = list()
                        fList.append(field_id)
                        self._euc_vector.dataProvider().deleteAttributes(fList)
                QgsMapLayerRegistry.instance().removeMapLayer(self._euc_vector.id())
            except:
                self.showError(self.tr(u'Error during deleting layers from previous computation.'))
                return
        # find input layers
        euc_layer = self.shp_box_euc.currentLayer()
        dmt_layer = self.raster_box.currentLayer()
        bpej_layer = self.shp_box_bpej.currentLayer()
        lpis_layer = self.shp_box_lpis.currentLayer()

        # check fields
        self._cancel = False
        self.checkField('BPEJ', bpej_layer, 'K')
        if self._cancel == True:
            return

        self.checkField('LPIS', lpis_layer, 'C')
        if self._cancel == True:
            return

        # check crs of input layers
        euc_crs = euc_layer.crs().authid()
        dmt_crs = dmt_layer.crs().authid()
        bpej_crs = bpej_layer.crs().authid()
        lpis_crs = lpis_layer.crs().authid()
        if euc_crs == dmt_crs == bpej_crs == lpis_crs:
            if not int(euc_crs[5:]) >= 100000:
                epsg = int(euc_crs[5:])
            else:
                self.showError(self.tr(u'It\'s not allow compute in own projection!\n\nSet CRS of'\
                               u' input layers: {}\n\nPlease change CRS of input layers to EPSG Code.').format(euc_crs))
                return
        else:
            self.showError(self.tr(u'All inputs have to be at the same projection!\nEUC: {}\nDMT: {}\nBPEJ: {}\nLPIS: {}\n'\
                           u'Please set only one projections for all input layers.').format(euc_crs, dmt_crs, bpej_crs, lpis_crs))
            return

        # add paths to layers to data
        data = []

        euc_path = euc_layer.dataProvider().dataSourceUri()
        data.append(euc_path[:euc_path.rfind('|')])

        dmt_path = dmt_layer.dataProvider().dataSourceUri()
        data.append(dmt_path)

        bpej_path = bpej_layer.dataProvider().dataSourceUri()
        data.append(bpej_path [:bpej_path.rfind('|')])

        lpis_path = lpis_layer.dataProvider().dataSourceUri()
        data.append(lpis_path [:lpis_path.rfind('|')])

        # find r, p factors and add them to factors
        factors = []
        r_factor = self.r_factor.text()
        p_factor = self.p_factor.text()
        factors.append(r_factor)
        factors.append(p_factor)

        self.progressBar()
        self.computeThread = ComputeThread(data, factors, epsg)
        self.computeThread.computeFinished.connect(lambda: self.importResults(epsg))
        self.computeThread.computeStat.connect(self.setStatus)
        self.computeThread.computeError.connect(self.showError)
        #self.computeThread.computeProgress.connect(self.progressBar)
        if not self.computeThread.isRunning():
            self.computeThread.start()

    def checkField(self, name_lyr, layer, field_name):
        if layer.fieldNameIndex(field_name) == -1:
            self.showError(self.tr(u'{} layer must contain field {}.\nClick on \'Compute {} factor\'')\
                           .format(name_lyr, field_name, field_name))
            self._cancel = True

    def showError(self, text):
        QMessageBox.critical(self, self.tr(u'Soil Erosion Plugin'), u"{}".format(text))

    def importResults(self, epsg):
        # if self.computeThread.aborted:
        #     return

        # Import results to QGIS
        temp_path = self.computeThread.output_path()
        for file in os.listdir(temp_path):
            if file.endswith(".tif"):
                try:
                    self._se_layer = iface.addRasterLayer(os.path.join(temp_path, file),
                                                    self.tr('G Faktor'))
                    crs = self._se_layer.crs()
                    crs.createFromId(epsg)
                    self._se_layer.setCrs(crs)
                    self.layerOnTop(self._se_layer)
                    # # Set style be renderer:
                    # self.setStyle(self._se_layer)
                    # # set style on .gml file:
                    euc = self.shp_box_euc.currentLayer()
                    self._euc_vector = iface.addVectorLayer(euc.source(), "EUC", euc.providerType())
                    self.layerOnTop(self._euc_vector)
                    se_source = self._se_layer.source()
                    self.zonalStat(self._euc_vector, se_source)
                    self.setVectorErosionStyle(self._euc_vector)
                    self._euc_vector.commitChanges()
                # for field in _euc_vector.pendingFields():
                #     if field.name() == 'C':
                #         _euc_vector.startEditing()
                #         oldname = field.name()
                #         field.setName('NewName')
                #         newname = field.name()
                #         self.showError(u'Old name: {},New name: {}'.format(oldname,newname))
                #

                except:
                    self.showError(u'Error during compute zonal statistics.')
        self._first_computation = False

        self.computeThread.cleanup()
        del self.computeThread
        # kill progress bar if it is still on (if computation is still on)
        try:
            self.progress.setParent(None)
            self.iface.messageBar().popWidget(self.progressMessageBar)
        except:
            pass
    def layerOnTop(self, layer):
        root = QgsProject.instance().layerTreeRoot()
        alayer = root.findLayer(layer.id())
        clone = alayer.clone()
        parent = alayer.parent()
        parent.insertChildNode(0, clone)
        parent.removeChildNode(alayer)

    def setBpejStyle(self, layer):
        # define ranges: label, lower value, upper value, hex value of color
        k_values = (
            (self.tr('0.0-0.1'), 0.0, 0.1, '#458b00'),
            (self.tr('0.1-0.2'), 0.1, 0.2, '#bcee68'),
            (self.tr('0.2-0.3'), 0.2, 0.3, '#eedd82'),
            (self.tr('0.3-0.4'), 0.3, 0.4, '#ffa07a'),
            (self.tr('0.4-0.5'), 0.4, 0.5, '#ff4500'),
            (self.tr('0.5 and more'), 0.5, 9999.0, '#8b2500'),
        )

        # create a category for each item in k_values
        ranges = []
        for label, lower, upper, color in k_values:
            symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
            symbol.setColor(QColor(color))
            rng = QgsRendererRangeV2(lower, upper, symbol, label)
            ranges.append(rng)

        # create the renderer and assign it to a layer
        expression = 'K'  # field name
        renderer = QgsGraduatedSymbolRendererV2(expression, ranges)
        layer.setRendererV2(renderer)

    def setLpisStyle(self, layer):
        # define a lookup: value -> (color, label)
        land_types = {
            'L': ('#005900', self.tr('Forest')),
            'R': ('#6d4237', self.tr('Arable land')),
            'S': ('#fb5858', self.tr('Orchard')),
            'V': ('#d875e4', self.tr('Vineyard')),
            'C': ('#f2d773', self.tr('Hop-garden')),
            'T': ('#329932', self.tr('Permanent grassland')),
            '': ('#808080', self.tr('Unknown')),
        }

        # create a category for each item in land_types
        categories = []
        for land_type, (color, label) in land_types.items():
            symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
            symbol.setColor(QColor(color))
            category = QgsRendererCategoryV2(land_type, symbol, label)
            categories.append(category)

        # create the renderer and assign it to a layer
        expression = 'KULTURAKOD'  # field name
        renderer = QgsCategorizedSymbolRendererV2(expression, categories)
        layer.setRendererV2(renderer)

    def setVectorErosionStyle(self, layer):
        # define ranges: label, lower value, upper value, hex value of color
        g_values = (
            (self.tr('Very weakly endangered'), 0.0, 1.0, '#458b00'),
            (self.tr('Weakly endangered'), 1.0, 2.0, '#bcee68'),
            (self.tr('Moderately endangered'), 2.0, 4.0, '#eedd82'),
            (self.tr('Severely endangered'), 4.0, 8.0, '#ffa07a'),
            (self.tr('Very severely endangered'), 8.0, 10.0, '#ff4500'),
            (self.tr('Extremely endangered'), 10.0, 999999.9, '#8b2500'),
        )

        # create a category for each item in g_values
        ranges = []
        for label, lower, upper, color in g_values:
            symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())
            symbol.setColor(QColor(color))
            rng = QgsRendererRangeV2(lower, upper, symbol, label)
            ranges.append(rng)

        # create the renderer and assign it to a layer
        expression = 'G'  # field name
        renderer = QgsGraduatedSymbolRendererV2(expression, ranges)
        layer.setRendererV2(renderer)
    def progressBar(self):
        """Initializing progress bar.

        :text: message to indicate what operation is currently on
        """
        self.progressMessageBar = iface.messageBar().createMessage(self.tr(u'Soil Erosion Plugin:'), self.tr(u' Computing...'))
        self.progress = QProgressBar()
        self.progress.setMaximum(100)
        self.progress.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)

        self.cancelButton = QtGui.QPushButton()
        self.cancelButton.setText(self.tr('Cancel'))
        self.progressMessageBar.layout().addWidget(self.cancelButton)
        self.progressMessageBar.layout().addWidget(self.progress)

        msgBar = self.iface.messageBar()
        msgBar.pushWidget(self.progressMessageBar, iface.messageBar().INFO)
        msgBar.findChildren(QToolButton)[0].setHidden(True)

        self.cancelButton.clicked.connect(self.onCancelButton)

    def onCancelButton(self):
        """Show message box with question on canceling. Cancel computation."""

        reply = QMessageBox.question(self, self.tr(u'Soil Erosion Plugin'),
                                     self.tr(u'Cancel computation?{ls}').format(
                                         ls=2 * os.linesep),
                                     QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
                                     QtGui.QMessageBox.Yes)
        if reply == QMessageBox.Yes:
            self.computeThread.terminate()
            self.computeThread.cleanup()
            del self.computeThread

            # kill progress bar if it is still on (if computation is still on)
            try:
                self.progress.setParent(None)
                self.iface.messageBar().popWidget(self.progressMessageBar)
            except:
                pass

    def setStatus(self, num, text):
        """Update progress status.

        :num: progress percent
        """
        self.progressMessageBar.setText(text)
        self.progress.setFormat('{}%'.format(num))
        self.progress.setValue(num)

    def setStyle(self, layer):
        provider = layer.dataProvider()
        extent = layer.extent()

        stats = provider.bandStatistics(1, QgsRasterBandStats.All, extent, 0)
        if stats.minimumValue < 0:
            min = 0
        else:
            min = stats.minimumValue

        max = stats.maximumValue
        range = max - min
        add = range // 2
        interval = min + add

        colDic = {'red': '#ff0000', 'yellow': '#ffff00', 'blue': '#0000ff'}

        valueList = [min, interval, max]

        lst = [QgsColorRampShader.ColorRampItem(valueList[0], QColor(colDic['red'])),
               QgsColorRampShader.ColorRampItem(valueList[1], QColor(colDic['yellow'])),
               QgsColorRampShader.ColorRampItem(valueList[2], QColor(colDic['blue']))]

        myRasterShader = QgsRasterShader()
        myColorRamp = QgsColorRampShader()

        myColorRamp.setColorRampItemList(lst)
        myColorRamp.setColorRampType(QgsColorRampShader.INTERPOLATED)
        # TODO: add classificationMode=Continuos
        # myColorRamp.setClassificationMode(QgsColorRampShader.ClassificationMode(1))
        # AttributeError: type object 'QgsColorRampShader' has no attribute 'setClassificationMode'
        myRasterShader.setRasterShaderFunction(myColorRamp)

        myPseudoRenderer = QgsSingleBandPseudoColorRenderer(layer.dataProvider(),
                                                            layer.type(),
                                                            myRasterShader)

        layer.setRenderer(myPseudoRenderer)

        layer.triggerRepaint()

    def zonalStat(self, vlayer, rlayer_source):
        prefix = 'Erosion_G_'
        zonalstats = QgsZonalStatistics(vlayer, rlayer_source, prefix, stats=QgsZonalStatistics.Statistic(4))
        zonalstats.calculateStatistics(None)
        vlayer.startEditing()
        for field in vlayer.pendingFields():
            if field.name() == 'Erosion_G_':
                idx = vlayer.fieldNameIndex(field.name())
                vlayer.renameAttribute(idx, 'G')
        vlayer.commitChanges()