Commit a553923b authored by Benjamin Jakimow's avatar Benjamin Jakimow
Browse files
parent 3212c2e5
......@@ -100,97 +100,69 @@ class CursorLocationInfoModel(TreeModel):
REMAINDER = 'reminder'
def __init__(self, parent=None):
super(CursorLocationInfoModel, self).__init__(parent)
super().__init__(parent=parent)
self.setColumnNames(['Band/Field', 'Value'])
self.mColumnNames = ['Band/Field', 'Value', 'Description']
self.mExpandedNodeRemainder = {}
self.mNodeExpansion = CursorLocationInfoModel.REMAINDER
def setNodeExpansion(self, type):
assert type in [CursorLocationInfoModel.ALWAYS_EXPAND,
CursorLocationInfoModel.NEVER_EXPAND,
CursorLocationInfoModel.REMAINDER]
self.mNodeExpansion = type
def flags(self, index):
def setExpandedNodeRemainder(self, node=None):
treeView = self.mTreeView
assert isinstance(treeView, QTreeView)
if node is None:
for n in self.mRootNode.childNodes():
self.setExpandedNodeRemainder(node=n)
else:
self.mExpandedNodeRemainder[self.weakNodeId(node)] = self.mTreeView.isExpanded(self.node2idx(node))
for n in node.childNodes():
self.setExpandedNodeRemainder(node=n)
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
def weakNodeId(self, node):
"""
def weakNodeId(self, node: TreeNode) -> str:
assert isinstance(node, TreeNode)
n = node.name()
while node.parentNode() != self.mRootNode:
node = node.parentNode()
n += '{}:{}'.format(node.name(), n)
return n
"""
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
def addSourceValues(self, sourceValueSet):
def addSourceValues(self, sourceValueSet: SourceValueSet):
if not isinstance(sourceValueSet, SourceValueSet):
return
# get-or-create node
def gocn(root, name) -> TreeNode:
assert isinstance(root, TreeNode)
n = TreeNode(root, name)
weakId = self.weakNodeId(n)
expand = False
if not isinstance(root.parentNode(), TreeNode):
expand = True
else:
if self.mNodeExpansion == CursorLocationInfoModel.REMAINDER:
expand = self.mExpandedNodeRemainder.get(weakId, False)
elif self.mNodeExpansion == CursorLocationInfoModel.NEVER_EXPAND:
expand = False
elif self.mNodeExpansion == CursorLocationInfoModel.ALWAYS_EXPAND:
expand = True
self.mTreeView.setExpanded(self.node2idx(n), expand)
return n
bn = os.path.basename(sourceValueSet.source)
if isinstance(sourceValueSet, RasterValueSet):
root = gocn(self.mRootNode, name=bn)
root = TreeNode(bn)
root.setIcon(QIcon(':/qps/ui/icons/raster.svg'))
# add subnodes
n = gocn(root, 'Pixel')
n.setValues('{},{}'.format(sourceValueSet.pxPosition.x(), sourceValueSet.pxPosition.y()))
pxNode = TreeNode('Pixel')
pxNode.setValues('{},{}'.format(sourceValueSet.pxPosition.x(), sourceValueSet.pxPosition.y()))
subNodes = [pxNode]
for bv in sourceValueSet.bandValues:
if isinstance(bv, RasterValueSet.BandInfo):
n = gocn(root, 'Band {}'.format(bv.bandIndex + 1))
n = TreeNode('Band {}'.format(bv.bandIndex + 1))
n.setToolTip('Band {} {}'.format(bv.bandIndex + 1, bv.bandName).strip())
n.setValues([bv.bandValue, bv.bandName])
subNodes.append(n)
if isinstance(bv.classInfo, ClassInfo):
nc = gocn(root, 'Class')
nc = TreeNode('Class')
nc.setValues(bv.classInfo.name())
nc.setIcon(bv.classInfo.icon())
n.appendChildNodes(nc)
elif isinstance(bv, QColor):
n = gocn(root, 'Color')
n = TreeNode('Color')
n.setToolTip('Color selected from screen pixel')
n.setValues(bv.getRgb())
subNodes.append(n)
root.appendChildNodes(subNodes)
self.rootNode().appendChildNodes(root)
if isinstance(sourceValueSet, VectorValueSet):
if len(sourceValueSet.features) == 0:
return
root = gocn(self.mRootNode, name=bn)
root = TreeNode(bn)
refFeature = sourceValueSet.features[0]
assert isinstance(refFeature, QgsFeature)
typeName = QgsWkbTypes.displayString(refFeature.geometry().wkbType()).lower()
......@@ -201,21 +173,33 @@ class CursorLocationInfoModel(TreeModel):
if 'point' in typeName:
root.setIcon(QIcon(r':/images/themes/default/mIconPointLayer.svg'))
subNodes = []
for field in refFeature.fields():
assert isinstance(field, QgsField)
fieldNode = gocn(root, name=field.name())
fieldNode = TreeNode(field.name())
featureNodes = []
for i, feature in enumerate(sourceValueSet.features):
assert isinstance(feature, QgsFeature)
nf = gocn(fieldNode, name='{}'.format(feature.id()))
nf = TreeNode(name='{}'.format(feature.id()))
nf.setValues([feature.attribute(field.name()), field.typeName()])
nf.setToolTip('Value of feature "{}" in field with name "{}"'.format(feature.id(), field.name()))
featureNodes.append(nf)
fieldNode.appendChildNodes(featureNodes)
subNodes.append(fieldNode)
root.appendChildNodes(subNodes)
self.rootNode().appendChildNodes(root)
s = ""
def clear(self):
self.mRootNode.removeChildNodes(0, self.mRootNode.childCount())
self.mRootNode.removeAllChildNodes()
class CursorLocationInfoTreeView(TreeView):
def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
class ComboBoxOption(object):
......@@ -313,8 +297,11 @@ class CursorLocationInfoDock(QDockWidget):
self.btnCrs.crsChanged.connect(self.setCrs)
self.btnCrs.setCrs(QgsCoordinateReferenceSystem())
self.mLocationInfoModel = CursorLocationInfoModel(parent=self.treeView)
self.treeView.setModel(self.mLocationInfoModel)
self.mLocationInfoModel = CursorLocationInfoModel()
self.mTreeView: CursorLocationInfoTreeView
assert isinstance(self.mTreeView, CursorLocationInfoTreeView)
self.mTreeView.setAutoExpansionDepth(3)
self.mTreeView.setModel(self.mLocationInfoModel)
self.mLayerModeModel = ComboBoxOptionModel(LAYERMODES, parent=self)
self.mLayerTypeModel = ComboBoxOptionModel(LAYERTYPES, parent=self)
......@@ -355,6 +342,9 @@ class CursorLocationInfoDock(QDockWidget):
self.setCanvas(canvas)
self.reloadCursorLocation()
def treeView(self) -> CursorLocationInfoTreeView:
return self.mTreeView
def reloadCursorLocation(self):
"""
Call to load / re-load the data for the cursor location
......@@ -381,7 +371,7 @@ class CursorLocationInfoDock(QDockWidget):
for c in self.mCanvases:
lyrs.extend(layerFilter(c))
self.mLocationInfoModel.setExpandedNodeRemainder()
self.treeView().updateNodeExpansion(False)
self.mLocationInfoModel.clear()
for l in lyrs:
......@@ -473,6 +463,8 @@ class CursorLocationInfoDock(QDockWidget):
pass
self.treeView().updateNodeExpansion(True)
def setCursorLocation(self, spatialPoint: SpatialPoint):
"""
Set the cursor lcation to be loaded.
......@@ -535,5 +527,3 @@ class CursorLocationInfoDock(QDockWidget):
return self.mLocationHistory[0]
else:
return None, None
......@@ -27,8 +27,9 @@ import os
import pathlib
import enum
import re
from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsFileUtils, QgsSettings, QgsStyle, QgsApplication
from qgis.gui import QgsRasterHistogramWidget, QgsMapCanvas, QgsMapLayerConfigWidget, \
from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsFileUtils, QgsSettings, \
QgsStyle, QgsMapLayerStyle, QgsApplication
from qgis.gui import QgsRasterHistogramWidget, QgsMapCanvas, QgsMapLayerConfigWidget, \
QgsLayerTreeEmbeddedConfigWidget, QgsMapLayerConfigWidgetFactory, QgsRendererRasterPropertiesWidget, \
QgsRendererPropertiesDialog, QgsRasterTransparencyWidget, QgsProjectionSelectionWidget
#
......@@ -192,11 +193,11 @@ class SymbologyConfigWidget(QpsMapLayerConfigWidget):
Emulates the QGS Layer Property Dialogs "Source" page
"""
def __init__(self, layer: QgsMapLayer, canvas: QgsMapCanvas, style: QgsStyle = QgsStyle(), parent=None):
def __init__(self, layer: QgsMapLayer, canvas: QgsMapCanvas, parent=None):
super().__init__(layer, canvas, parent=parent)
loadUi(configWidgetUi('symbologyconfigwidget.ui'), self)
self.mSymbologyWidget = None
self.mStyle: QgsStyle = style
self.mStyle: QgsMapLayerStyle = None
self.mDefaultRenderer = None
if isinstance(layer, (QgsRasterLayer, QgsVectorLayer)):
self.mDefaultRenderer = layer.renderer().clone()
......@@ -206,7 +207,7 @@ class SymbologyConfigWidget(QpsMapLayerConfigWidget):
def symbologyWidget(self) -> typing.Union[QgsRendererRasterPropertiesWidget, QgsRendererPropertiesDialog]:
return self.scrollArea.widget()
def style(self) -> QgsStyle:
def style(self) -> QgsMapLayerStyle:
return self.mStyle
def menuButtonMenu(self) -> QMenu:
......@@ -302,6 +303,13 @@ class SymbologyConfigWidget(QpsMapLayerConfigWidget):
else:
QMessageBox.information(self, 'Load Style', msg)
def reset(self):
lyr = self.mapLayer()
if isinstance(lyr, QgsMapLayer):
lyr.styleManager().reset()
self.syncToLayer()
def saveStyle(self, fileName: str = None):
lastUsedDir = QgsSettings().value("style/lastStyleDir")
......@@ -364,6 +372,9 @@ class SymbologyConfigWidget(QpsMapLayerConfigWidget):
w = self.symbologyWidget()
lyr = self.mapLayer()
if isinstance(lyr, QgsMapLayer):
self.mStyle = lyr.styleManager().style(lyr.styleManager().currentStyle())
if isinstance(lyr, QgsRasterLayer):
r = lyr.renderer()
if isinstance(w, QgsRendererRasterPropertiesWidget):
......@@ -377,11 +388,12 @@ class SymbologyConfigWidget(QpsMapLayerConfigWidget):
self.setSymbologyWidget(w)
elif isinstance(lyr, QgsVectorLayer):
w = None
if not isinstance(w, QgsRendererPropertiesDialog):
w = QgsRendererPropertiesDialog(lyr, self.style(), embedded=True)
w = QgsRendererPropertiesDialog(lyr, QgsStyle.defaultStyle(), embedded=True)
self.setSymbologyWidget(w)
else:
s = ""
pass
else:
......
......@@ -34,7 +34,7 @@ from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *
from qgis.core import QgsRasterLayer, QgsVectorLayer, QgsMapLayer, \
QgsVectorDataProvider, QgsRasterDataProvider, Qgis
from qgis.gui import QgsMapCanvas, QgsMapLayerConfigWidgetFactory, QgsDoubleSpinBox
from qgis.gui import QgsMapCanvas, QgsMapLayerConfigWidgetFactory, QgsDoubleSpinBox, QgsMessageBar
from .core import QpsMapLayerConfigWidget
from ..classification.classificationscheme import ClassificationScheme, ClassificationSchemeWidget
from ..utils import loadUi, gdalDataset, parseWavelength, parseFWHM
......@@ -115,6 +115,12 @@ class GDALErrorHandler(object):
self.err_no=err_no
self.err_msg=err_msg
if err_level == gdal.CE_Warning:
pass
if err_level > gdal.CE_Warning:
raise RuntimeError(err_level, err_no, err_msg)
class GDALBandMetadataItem(object):
......@@ -129,6 +135,7 @@ class GDALBandMetadataItem(object):
class GDALBandMetadataModel(QAbstractTableModel):
sigWavelengthUnitsChanged = pyqtSignal(str)
sigMessage = pyqtSignal(str, Qgis.MessageLevel)
def __init__(self, parent=None):
super().__init__(parent)
......@@ -136,6 +143,8 @@ class GDALBandMetadataModel(QAbstractTableModel):
self.cnWavelength = 'Wavelength'
self.cnFWHM = 'FWHM'
self.mErrorHandler: GDALErrorHandler = GDALErrorHandler()
self.mWavelengthUnitModel = XUnitModel()
self.mWavelengthUnitModel.mDescription[BAND_INDEX] = 'None'
self.mWavelengthUnitModel.mToolTips[BAND_INDEX] = 'No wavelength defined'
......@@ -323,45 +332,72 @@ class GDALBandMetadataModel(QAbstractTableModel):
def applyToLayer(self, *args):
if isinstance(self.mMapLayer, QgsRasterLayer) and self.mMapLayer.isValid():
def list_or_empty(values):
def list_or_empty(values, domain:str=None):
for v in values:
if v not in ['', None, 'None']:
return ','.join(values)
if v in ['', None, 'None']:
return ''
return ''
result = ','.join(values)
if domain == 'ENVI':
result = f'{{{result}}}'
return result
if self.mMapLayer.dataProvider().name() == 'gdal':
gdal.PushErrorHandler(self.mErrorHandler.handler)
try:
ds: gdal.Dataset = gdal.Open(self.mMapLayer.source(), gdal.GA_Update)
assert isinstance(ds, gdal.Dataset)
except Exception as ex:
print(f'unable to open image in update mode: {self.mMapLayer.source()}')
print(ex, file=sys.stderr)
except RuntimeError as ex:
msg = f'{ex}\nMetadata might not get saved for {self.mMapLayer.source()}'
self.sigMessage.emit(msg, Qgis.Warning)
ds: gdal.Dataset = gdal.Open(self.mMapLayer.source(), gdal.GA_ReadOnly)
is_envi = ds.GetDriver().ShortName == 'ENVI'
if is_envi:
domain = 'ENVI'
else:
domain = None
if self.mWavelengthUnit not in [None, '', BAND_INDEX]:
ds.SetMetadataItem('wavelength units ', self.mWavelengthUnit, domain)
try:
wl = [str(item.wavelength) for item in self.mBandMetadata]
ds.SetMetadataItem('wavelength', list_or_empty(wl), domain)
# if ENVI driver, save to 'ENVI' domain
# if other driver, save to default domain
fwhm = [str(item.fwhm) for item in self.mBandMetadata]
ds.SetMetadataItem('fwhm', list_or_empty(fwhm), domain)
is_envi = ds.GetDriver().ShortName == 'ENVI'
if is_envi:
domain = 'ENVI'
else:
domain = None
for b, item in enumerate(self.mBandMetadata):
assert isinstance(item, GDALBandMetadataItem)
band: gdal.Band = ds.GetRasterBand(b+1)
band.SetDescription(item.name)
if self.mWavelengthUnit not in [None, '', BAND_INDEX]:
# remove potential concurrent definitions of wavelength unit and fwhm
for k in ['wavelength_units', 'wavelength units', 'fwhm', 'wavelength', 'wavelengths']:
for d in ['ENVI', None]:
ds.SetMetadataItem(k, None, d)
for b in range(ds.RasterCount):
band: gdal.Band = ds.GetRasterBand(b+1)
band.SetMetadataItem(k, None, d)
band.FlushCache()
ds.SetMetadataItem('wavelength units', self.mWavelengthUnit, domain)
ds.FlushCache()
del ds
wl = [str(item.wavelength) for item in self.mBandMetadata]
ds.SetMetadataItem('wavelength', list_or_empty(wl), domain)
fwhm = [str(item.fwhm) for item in self.mBandMetadata]
ds.SetMetadataItem('fwhm', list_or_empty(fwhm), domain)
for b, item in enumerate(self.mBandMetadata):
assert isinstance(item, GDALBandMetadataItem)
band: gdal.Band = ds.GetRasterBand(b+1)
band.SetDescription(item.name)
band.FlushCache()
ds.FlushCache()
del ds
except Exception as ex:
msg = f'{ex}'
self.sigMessage.emit(msg, Qgis.Critical)
print(msg, file=sys.stderr)
finally:
gdal.PopErrorHandler()
def validate(self) -> typing.List[str]:
# todo: implement some internal validation and return descriptive error messages
......@@ -409,7 +445,6 @@ class GDALBandMetadataModel(QAbstractTableModel):
self.endResetModel()
class GDALBandMetadataModelTableViewDelegate(QStyledItemDelegate):
"""
......@@ -553,6 +588,7 @@ class GDALBandMetadataModelTableView(QTableView):
class GDALMetadataModel(QAbstractTableModel):
sigEditable = pyqtSignal(bool)
sigMessage = pyqtSignal(str, Qgis.MessageLevel)
def __init__(self, parent=None):
super(GDALMetadataModel, self).__init__(parent)
......@@ -566,9 +602,9 @@ class GDALMetadataModel(QAbstractTableModel):
self.cnValue = 'Value(s)'
self._column_names = [self.cnItem, self.cnDomain, self.cnKey, self.cnValue]
self._isEditable = False
self._isEditable: bool = False
self._MDItems: typing.List[GDALMetadataItem] = []
self._MOKs:typing.List[str] = []
self._MOKs: typing.List[str] = []
def resetChanges(self):
c = self._column_names.index(self.cnValue)
......@@ -653,6 +689,7 @@ class GDALMetadataModel(QAbstractTableModel):
mdItems, moks = self._read_maplayer()
self._MDItems.extend(mdItems)
self._MOKs.extend(moks)
self.endResetModel()
def removeItem(self, item:GDALMetadataItem):
......@@ -679,8 +716,17 @@ class GDALMetadataModel(QAbstractTableModel):
if self.mLayer.dataProvider().name() == 'gdal':
gdal.PushErrorHandler(self.mErrorHandler.handler)
try:
ds: gdal.Dataset = gdal.Open(self.mLayer.source(), gdal.GA_Update)
assert isinstance(ds, gdal.Dataset)
except RuntimeError as ex:
msg = f'{ex}\nMetadata might not get saved for {self.mLayer.source()}'
self.sigMessage.emit(msg, Qgis.Warning)
ds: gdal.Dataset = gdal.Open(self.mLayer.source(), gdal.GA_ReadOnly)
try:
ds = gdal.Open(self.mLayer.source(), gdal.GA_ReadOnly)
if isinstance(ds, gdal.Dataset):
for item in changed:
mo: gdal.MajorObject = None
......@@ -696,11 +742,9 @@ class GDALMetadataModel(QAbstractTableModel):
ds.FlushCache()
del ds
if self.mErrorHandler.err_level >= gdal.CE_Warning:
raise RuntimeError(self.mErrorHandler.err_level,
self.mErrorHandler.err_no,
self.mErrorHandler.err_msg)
except Exception as ex:
msg = str(ex)
self.sigMessage.emit(msg, Qgis.Critical)
print(ex, file=sys.stderr)
finally:
gdal.PopErrorHandler()
......@@ -708,8 +752,10 @@ class GDALMetadataModel(QAbstractTableModel):
if isinstance(self.mLayer, QgsVectorLayer) and isinstance(self.mLayer.dataProvider(), QgsVectorDataProvider):
if self.mLayer.dataProvider().name() == 'ogr':
path = self.mLayer.source().split('|')[0]
gdal.PushErrorHandler(self.mErrorHandler.handler)
use_ogr_exception = ogr.GetUseExceptions()
try:
ogr.UseExceptions()
ds: ogr.DataSource = ogr.Open(path, update=1)
if isinstance(ds, ogr.DataSource):
for item in changed:
......@@ -724,14 +770,18 @@ class GDALMetadataModel(QAbstractTableModel):
mo.SetMetadataItem(item.key, item.value, item.domain)
ds.FlushCache()
if self.mErrorHandler.err_level >= gdal.CE_Warning:
raise RuntimeError(self.mErrorHandler.err_level,
self.mErrorHandler.err_no,
self.mErrorHandler.err_msg)
except Exception as ex:
msg = str(ex)
self.sigMessage.emit(msg, Qgis.Critical)
print(ex, file=sys.stderr)
finally:
gdal.PopErrorHandler()
if use_ogr_exception:
ogr.UseExceptions()
else:
ogr.DontUseExceptions()
#gdal.PopErrorHandler()
def index2MDItem(self, index: QModelIndex) -> GDALMetadataItem:
"""
......@@ -819,12 +869,16 @@ class GDALMetadataModel(QAbstractTableModel):
items = []
major_objects = []
if not isinstance(self.mLayer, QgsMapLayer) or not self.mLayer.isValid():
self.setIsEditable(False)
return items, major_objects
is_editable = False
if isinstance(self.mLayer, QgsRasterLayer) and self.mLayer.dataProvider().name() == 'gdal':
ds = gdal.Open(self.mLayer.source())
is_editable = True
if isinstance(ds, gdal.Dataset):
z = len(str(ds.RasterCount))
mok = 'Dataset'
......@@ -842,6 +896,8 @@ class GDALMetadataModel(QAbstractTableModel):
if isinstance(self.mLayer, QgsVectorLayer) and self.mLayer.dataProvider().name() == 'ogr':
ds = ogr.Open(self.mLayer.source().split('|')[0])
if isinstance(ds, ogr.DataSource):
drv: ogr.Driver = ds.GetDriver()
is_editable = False
sep = self.mLayer.dataProvider().sublayerSeparator()
subLayers = self.mLayer.dataProvider().subLayers()
if len(subLayers) > 0:
......@@ -862,6 +918,7 @@ class GDALMetadataModel(QAbstractTableModel):
for (domain, key, value) in self._read_majorobject(lyr):
items.append(GDALMetadataItem(mok, domain, key, value))
self.setIsEditable(is_editable)
return items, major_objects
......@@ -875,7 +932,7 @@ class GDALMetadataModelTableView(QTableView):
def contextMenuEvent(self, event: QContextMenuEvent) -> None:
"""
Opens a context menue
Opens a context menu
"""
index = self.indexAt(event.pos())
if index.isValid():
......@@ -915,8 +972,6 @@ class GDALMetadataItemDialog(QDialog):
self.tbValue.textChanged.connect(self.validate)
self.cbDomain.currentTextChanged.connect(self.validate)
self.cbMajorObject.currentTextChanged.connect(self.validate)
self.validate()
def validate(self, *args):
......@@ -984,12 +1039,14 @@ class GDALMetadataModelConfigWidget(QpsMapLayerConfigWidget):
pathUi = pathlib.Path(__file__).parents[1] / 'ui' / 'gdalmetadatamodelwidget.ui'
loadUi(pathUi, self)
self.mMessageBar: QgsMessageBar
self.tbFilter: QLineEdit
self.btnMatchCase.setDefaultAction(self.optionMatchCase)
self.btnRegex.setDefaultAction(self.optionRegex)
self._cs = None