mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-10-31 00:06:02 -04: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
 |