mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-11-03 00:14:12 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			311 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# -*- coding: utf-8 -*-
 | 
						|
 | 
						|
"""
 | 
						|
***************************************************************************
 | 
						|
    RasterCalculatorWidgets.py
 | 
						|
    ---------------------
 | 
						|
    Date                 : November 2016
 | 
						|
    Copyright            : (C) 2016 by Victor Olaya
 | 
						|
    Email                : volayaf at gmail dot com
 | 
						|
***************************************************************************
 | 
						|
*                                                                         *
 | 
						|
*   This program is free software; you can redistribute it and/or modify  *
 | 
						|
*   it under the terms of the GNU General Public License as published by  *
 | 
						|
*   the Free Software Foundation; either version 2 of the License, or     *
 | 
						|
*   (at your option) any later version.                                   *
 | 
						|
*                                                                         *
 | 
						|
***************************************************************************
 | 
						|
"""
 | 
						|
 | 
						|
__author__ = 'Victor Olaya'
 | 
						|
__date__ = 'November 2016'
 | 
						|
__copyright__ = '(C) 2016, Victor Olaya'
 | 
						|
 | 
						|
import os
 | 
						|
from functools import partial
 | 
						|
import re
 | 
						|
import json
 | 
						|
 | 
						|
from qgis.utils import iface
 | 
						|
from qgis.PyQt import uic
 | 
						|
from qgis.PyQt.QtCore import Qt
 | 
						|
from qgis.PyQt.QtGui import QTextCursor
 | 
						|
from qgis.PyQt.QtWidgets import (QLineEdit, QPushButton, QLabel,
 | 
						|
                                 QComboBox, QSpacerItem, QSizePolicy,
 | 
						|
                                 QListWidgetItem)
 | 
						|
 | 
						|
from qgis.core import (QgsProcessingUtils,
 | 
						|
                       QgsProcessingParameterDefinition,
 | 
						|
                       QgsProcessingParameterRasterLayer,
 | 
						|
                       QgsProcessingOutputRasterLayer,
 | 
						|
                       QgsProject)
 | 
						|
 | 
						|
from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD, DIALOG_BATCH
 | 
						|
from processing.gui.BatchInputSelectionPanel import BatchInputSelectionPanel
 | 
						|
from processing.tools import dataobjects
 | 
						|
from processing.tools.system import userFolder
 | 
						|
 | 
						|
from processing.gui.wrappers import InvalidParameterValue
 | 
						|
 | 
						|
from qgis.analysis import QgsRasterCalculatorEntry, QgsRasterCalcNode
 | 
						|
 | 
						|
pluginPath = os.path.dirname(__file__)
 | 
						|
WIDGET_ADD_NEW, BASE_ADD_NEW = uic.loadUiType(
 | 
						|
    os.path.join(pluginPath, 'AddNewExpressionDialog.ui'))
 | 
						|
 | 
						|
 | 
						|
class AddNewExpressionDialog(BASE_ADD_NEW, WIDGET_ADD_NEW):
 | 
						|
 | 
						|
    def __init__(self, expression):
 | 
						|
        super(AddNewExpressionDialog, self).__init__()
 | 
						|
        self.setupUi(self)
 | 
						|
 | 
						|
        self.name = None
 | 
						|
        self.expression = None
 | 
						|
        self.txtExpression.setPlainText(expression)
 | 
						|
        self.buttonBox.rejected.connect(self.cancelPressed)
 | 
						|
        self.buttonBox.accepted.connect(self.okPressed)
 | 
						|
 | 
						|
    def cancelPressed(self):
 | 
						|
        self.close()
 | 
						|
 | 
						|
    def okPressed(self):
 | 
						|
        self.name = self.txtName.text()
 | 
						|
        self.expression = self.txtExpression.toPlainText()
 | 
						|
        self.close()
 | 
						|
 | 
						|
 | 
						|
WIDGET_DLG, BASE_DLG = uic.loadUiType(
 | 
						|
    os.path.join(pluginPath, 'PredefinedExpressionDialog.ui'))
 | 
						|
 | 
						|
 | 
						|
class PredefinedExpressionDialog(BASE_DLG, WIDGET_DLG):
 | 
						|
 | 
						|
    def __init__(self, expression, options):
 | 
						|
        super(PredefinedExpressionDialog, self).__init__()
 | 
						|
        self.setupUi(self)
 | 
						|
 | 
						|
        self.filledExpression = None
 | 
						|
        self.options = options
 | 
						|
        self.expression = expression
 | 
						|
        self.variables = set(re.findall(r'\[.*?\]', expression))
 | 
						|
        self.comboBoxes = {}
 | 
						|
        for variable in self.variables:
 | 
						|
            label = QLabel(variable[1:-1])
 | 
						|
            combo = QComboBox()
 | 
						|
            for opt in self.options.keys():
 | 
						|
                combo.addItem(opt)
 | 
						|
            self.comboBoxes[variable] = combo
 | 
						|
            self.groupBox.layout().addWidget(label)
 | 
						|
            self.groupBox.layout().addWidget(combo)
 | 
						|
 | 
						|
        verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
 | 
						|
        self.groupBox.layout().addItem(verticalSpacer)
 | 
						|
 | 
						|
        self.buttonBox.rejected.connect(self.cancelPressed)
 | 
						|
        self.buttonBox.accepted.connect(self.okPressed)
 | 
						|
 | 
						|
    def cancelPressed(self):
 | 
						|
        self.close()
 | 
						|
 | 
						|
    def okPressed(self):
 | 
						|
        self.filledExpression = self.expression
 | 
						|
        for name, combo in self.comboBoxes.items():
 | 
						|
            self.filledExpression = self.filledExpression.replace(name,
 | 
						|
                                                                  self.options[combo.currentText()])
 | 
						|
        self.close()
 | 
						|
 | 
						|
 | 
						|
WIDGET, BASE = uic.loadUiType(
 | 
						|
    os.path.join(pluginPath, 'RasterCalculatorWidget.ui'))
 | 
						|
 | 
						|
 | 
						|
class ExpressionWidget(BASE, WIDGET):
 | 
						|
 | 
						|
    _expressions = {"NDVI": "([NIR] - [Red]) / ([NIR] + [Red])"}
 | 
						|
 | 
						|
    def __init__(self, options):
 | 
						|
        super(ExpressionWidget, self).__init__(None)
 | 
						|
        self.setupUi(self)
 | 
						|
 | 
						|
        self.setList(options)
 | 
						|
 | 
						|
        def doubleClicked(item):
 | 
						|
            self.text.insertPlainText('"{}"'.format(self.options[item.text()]))
 | 
						|
 | 
						|
        def addButtonText(text):
 | 
						|
            if any(c for c in text if c.islower()):
 | 
						|
                self.text.insertPlainText(" {}()".format(text))
 | 
						|
                self.text.moveCursor(QTextCursor.PreviousCharacter, QTextCursor.MoveAnchor)
 | 
						|
            else:
 | 
						|
                self.text.insertPlainText(" {} ".format(text))
 | 
						|
        buttons = [b for b in self.buttonsGroupBox.children()if isinstance(b, QPushButton)]
 | 
						|
        for button in buttons:
 | 
						|
            button.clicked.connect(partial(addButtonText, button.text()))
 | 
						|
        self.listWidget.itemDoubleClicked.connect(doubleClicked)
 | 
						|
 | 
						|
        self.expressions = {}
 | 
						|
        if os.path.exists(self.expsFile()):
 | 
						|
            with open(self.expsFile()) as f:
 | 
						|
                self.expressions.update(json.load(f))
 | 
						|
        self.expressions.update(self._expressions)
 | 
						|
 | 
						|
        self.fillPredefined()
 | 
						|
        self.buttonAddPredefined.clicked.connect(self.addPredefined)
 | 
						|
 | 
						|
        self.buttonSavePredefined.clicked.connect(self.savePredefined)
 | 
						|
        self.text.textChanged.connect(self.expressionValid)
 | 
						|
 | 
						|
    def expressionValid(self):
 | 
						|
        errorString = ''
 | 
						|
        testNode = QgsRasterCalcNode.parseRasterCalcString(self.text.toPlainText(), errorString)
 | 
						|
 | 
						|
        if not self.text.toPlainText():
 | 
						|
            self.expressionErrorLabel.setText(self.tr('Expression is empty'))
 | 
						|
            self.expressionErrorLabel.setStyleSheet("QLabel { color: black; }")
 | 
						|
            return False
 | 
						|
 | 
						|
        if testNode:
 | 
						|
            self.expressionErrorLabel.setText(self.tr('Expression is valid'))
 | 
						|
            self.expressionErrorLabel.setStyleSheet("QLabel { color: green; font-weight: bold; }")
 | 
						|
            return True
 | 
						|
 | 
						|
        self.expressionErrorLabel.setText(self.tr('Expression is not valid ') + errorString)
 | 
						|
        self.expressionErrorLabel.setStyleSheet("QLabel { color : red; font-weight: bold; }")
 | 
						|
        return False
 | 
						|
 | 
						|
    def expsFile(self):
 | 
						|
        return os.path.join(userFolder(), 'rastercalcexpressions.json')
 | 
						|
 | 
						|
    def addPredefined(self):
 | 
						|
        expression = self.expressions[self.comboPredefined.currentText()]
 | 
						|
        dlg = PredefinedExpressionDialog(expression, self.options)
 | 
						|
        dlg.exec_()
 | 
						|
        if dlg.filledExpression:
 | 
						|
            self.text.setPlainText(dlg.filledExpression)
 | 
						|
 | 
						|
    def savePredefined(self):
 | 
						|
        exp = self.text.toPlainText()
 | 
						|
        used = [v for v in self.options.values() if v in exp]
 | 
						|
 | 
						|
        for i, v in enumerate(used):
 | 
						|
            exp = exp.replace(v, chr(97 + i))
 | 
						|
 | 
						|
        dlg = AddNewExpressionDialog(exp)
 | 
						|
        dlg.exec_()
 | 
						|
        if dlg.name:
 | 
						|
            self.expressions[dlg.name] = dlg.expression
 | 
						|
 | 
						|
        with open(self.expsFile(), "w") as f:
 | 
						|
            f.write(json.dumps(self.expressions))
 | 
						|
 | 
						|
    def fillPredefined(self):
 | 
						|
        self.comboPredefined.clear()
 | 
						|
        for expression in self.expressions:
 | 
						|
            self.comboPredefined.addItem(expression)
 | 
						|
 | 
						|
    def setList(self, options):
 | 
						|
        self.options = options
 | 
						|
        self.listWidget.clear()
 | 
						|
        entries = QgsRasterCalculatorEntry.rasterEntries()
 | 
						|
 | 
						|
        def _find_source(name):
 | 
						|
            for entry in entries:
 | 
						|
                if entry.ref == name:
 | 
						|
                    return entry.raster.source()
 | 
						|
            return ''
 | 
						|
 | 
						|
        for name in options.keys():
 | 
						|
            item = QListWidgetItem(name, self.listWidget)
 | 
						|
            tooltip = _find_source(name)
 | 
						|
            if tooltip:
 | 
						|
                item.setData(Qt.ToolTipRole, tooltip)
 | 
						|
            self.listWidget.addItem(item)
 | 
						|
 | 
						|
    def setValue(self, value):
 | 
						|
        self.text.setPlainText(value)
 | 
						|
 | 
						|
    def value(self):
 | 
						|
        return self.text.toPlainText()
 | 
						|
 | 
						|
 | 
						|
class ExpressionWidgetWrapper(WidgetWrapper):
 | 
						|
 | 
						|
    def _panel(self, options):
 | 
						|
        return ExpressionWidget(options)
 | 
						|
 | 
						|
    def _get_options(self):
 | 
						|
        entries = QgsRasterCalculatorEntry.rasterEntries()
 | 
						|
        options = {}
 | 
						|
        for entry in entries:
 | 
						|
            options[entry.ref] = entry.ref
 | 
						|
        return options
 | 
						|
 | 
						|
    def createWidget(self):
 | 
						|
        if self.dialogType == DIALOG_STANDARD:
 | 
						|
            if iface is not None and iface.layerTreeView() is not None and iface.layerTreeView().layerTreeModel() is not None:
 | 
						|
                iface.layerTreeView().layerTreeModel().dataChanged.connect(self.refresh)
 | 
						|
            return self._panel(self._get_options())
 | 
						|
        elif self.dialogType == DIALOG_BATCH:
 | 
						|
            return QLineEdit()
 | 
						|
        else:
 | 
						|
            layers = self.dialog.getAvailableValuesOfType([QgsProcessingParameterRasterLayer], [QgsProcessingOutputRasterLayer])
 | 
						|
            options = {self.dialog.resolveValueDescription(lyr): "{}@1".format(self.dialog.resolveValueDescription(lyr)) for lyr in layers}
 | 
						|
            self.widget = self._panel(options)
 | 
						|
            return self.widget
 | 
						|
 | 
						|
    def refresh(self, *args):
 | 
						|
        self.widget.setList(self._get_options())
 | 
						|
 | 
						|
    def setValue(self, value):
 | 
						|
        if self.dialogType == DIALOG_STANDARD:
 | 
						|
            pass  # TODO
 | 
						|
        elif self.dialogType == DIALOG_BATCH:
 | 
						|
            return self.widget.setText(value)
 | 
						|
        else:
 | 
						|
            self.widget.setValue(value)
 | 
						|
 | 
						|
    def value(self):
 | 
						|
        if self.dialogType == DIALOG_STANDARD:
 | 
						|
            return self.widget.value()
 | 
						|
        elif self.dialogType == DIALOG_BATCH:
 | 
						|
            return self.widget.text()
 | 
						|
        else:
 | 
						|
            return self.widget.value()
 | 
						|
 | 
						|
 | 
						|
class LayersListWidgetWrapper(WidgetWrapper):
 | 
						|
 | 
						|
    def createWidget(self):
 | 
						|
        if self.dialogType == DIALOG_BATCH:
 | 
						|
            widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
 | 
						|
            widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
 | 
						|
            return widget
 | 
						|
        else:
 | 
						|
            return None
 | 
						|
 | 
						|
    def setValue(self, value):
 | 
						|
        if self.dialogType == DIALOG_BATCH:
 | 
						|
            return self.widget.setText(value)
 | 
						|
 | 
						|
    def value(self):
 | 
						|
        if self.dialogType == DIALOG_STANDARD:
 | 
						|
            if self.param.datatype == dataobjects.TYPE_FILE:
 | 
						|
                return self.param.setValue(self.widget.selectedoptions)
 | 
						|
            else:
 | 
						|
                if self.param.datatype == dataobjects.TYPE_RASTER:
 | 
						|
                    options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)
 | 
						|
                elif self.param.datatype == dataobjects.TYPE_VECTOR_ANY:
 | 
						|
                    options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
 | 
						|
                else:
 | 
						|
                    options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.param.datatype], False)
 | 
						|
                return [options[i] for i in self.widget.selectedoptions]
 | 
						|
        elif self.dialogType == DIALOG_BATCH:
 | 
						|
            return self.widget.getText()
 | 
						|
        else:
 | 
						|
            options = self._getOptions()
 | 
						|
            values = [options[i] for i in self.widget.selectedoptions]
 | 
						|
            if len(values) == 0 and not self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
 | 
						|
                raise InvalidParameterValue()
 | 
						|
            return values
 |