From 453fab7462f339f9c9f5f8d130a94538cf81d302 Mon Sep 17 00:00:00 2001 From: Benjamin Jakimow <benjamin.jakimow@geo.hu-berlin.de> Date: Fri, 11 Jan 2019 10:49:50 +0100 Subject: [PATCH] labeling.py MapCanvas context menu show shortcuts to label on selected QgsFeatures in linked QgsVectorLayer fields --- tests/test_labeling.py | 46 +++++----- timeseriesviewer/labeling.py | 120 ++++++++++++++++----------- timeseriesviewer/mapcanvas.py | 54 ++++++++---- timeseriesviewer/mapvisualization.py | 2 +- 4 files changed, 132 insertions(+), 90 deletions(-) diff --git a/tests/test_labeling.py b/tests/test_labeling.py index 43d19d80..3a09c368 100644 --- a/tests/test_labeling.py +++ b/tests/test_labeling.py @@ -60,10 +60,10 @@ class testclassLabelingTest(unittest.TestCase): model = LabelAttributeTableModel() model.setVectorLayer(lyr) - model.setFieldShortCut('sensor', LabelShortCutType.Sensor) - model.setFieldShortCut('date', LabelShortCutType.Date) - model.setFieldShortCut('DOY', LabelShortCutType.DOY) - model.setFieldShortCut('decyr', LabelShortCutType.Off) + model.setFieldShortCut('sensor', LabelShortcutType.Sensor) + model.setFieldShortCut('date', LabelShortcutType.Date) + model.setFieldShortCut('DOY', LabelShortcutType.DOY) + model.setFieldShortCut('decyr', LabelShortcutType.Off) self.assertIsInstance(lyr, QgsVectorLayer) @@ -113,13 +113,13 @@ class testclassLabelingTest(unittest.TestCase): possibleTypes = shortcuts(field) if re.search('string', field.typeName(), re.I): - for t in list(LabelShortCutType): + for t in list(LabelShortcutType): self.assertTrue(t in possibleTypes) elif re.search('integer', field.typeName(), re.I): - for t in [LabelShortCutType.Classification, LabelShortCutType.Off, LabelShortCutType.DOY]: + for t in [LabelShortcutType.Classification, LabelShortcutType.Off, LabelShortcutType.DOY]: self.assertTrue(t in possibleTypes) elif re.search('real', field.typeName(), re.I): - for t in [LabelShortCutType.Classification, LabelShortCutType.Off, LabelShortCutType.DOY]: + for t in [LabelShortcutType.Classification, LabelShortcutType.Off, LabelShortcutType.DOY]: self.assertTrue(t in possibleTypes) else: self.fail('Unhandled QgsField typeName: {}'.format(field.typeName())) @@ -151,22 +151,22 @@ class testclassLabelingTest(unittest.TestCase): vl.setEditorWidgetSetup(vl.fields().lookupField('sensor'), QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, - {'labelType': LabelShortCutType.Sensor})) + {'labelType': LabelShortcutType.Sensor})) vl.setEditorWidgetSetup(vl.fields().lookupField('date'), - QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, {'labelType': LabelShortCutType.Date})) + QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, {'labelType': LabelShortcutType.Date})) vl.setEditorWidgetSetup(vl.fields().lookupField('DOY'), - QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, {'labelType': LabelShortCutType.DOY})) + QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, {'labelType': LabelShortcutType.DOY})) vl.setEditorWidgetSetup(vl.fields().lookupField('decyr'), - QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, {'labelType': LabelShortCutType.DecimalYear})) + QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, {'labelType': LabelShortcutType.DecimalYear})) vl.setEditorWidgetSetup(vl.fields().lookupField('class1l'), QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, - {'labelType': LabelShortCutType.Classification, + {'labelType': LabelShortcutType.Classification, 'classificationScheme':classScheme1})) vl.setEditorWidgetSetup(vl.fields().lookupField('class1n'), QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, - {'labelType': LabelShortCutType.Classification, + {'labelType': LabelShortcutType.Classification, 'classificationScheme': classScheme1})) for i in range(vl.fields().count()): @@ -278,14 +278,14 @@ class testclassLabelingTest(unittest.TestCase): self.assertTrue(model.mVectorLayer == lyr) self.assertTrue(lyr.fields().count() == model.rowCount()) - dock.setFieldShortCut('sensor', LabelShortCutType.Sensor) - dock.setFieldShortCut('date', LabelShortCutType.Date) - dock.setFieldShortCut('DOY', LabelShortCutType.DOY) - dock.setFieldShortCut('decyr', LabelShortCutType.Off) - dock.setFieldShortCut('class1l', LabelShortCutType.Off) - dock.setFieldShortCut('class1n', LabelShortCutType.Off) - dock.setFieldShortCut('class2l', LabelShortCutType.Off) - dock.setFieldShortCut('class2n', LabelShortCutType.Off) + dock.setFieldShortCut('sensor', LabelShortcutType.Sensor) + dock.setFieldShortCut('date', LabelShortcutType.Date) + dock.setFieldShortCut('DOY', LabelShortcutType.DOY) + dock.setFieldShortCut('decyr', LabelShortcutType.Off) + dock.setFieldShortCut('class1l', LabelShortcutType.Off) + dock.setFieldShortCut('class1n', LabelShortcutType.Off) + dock.setFieldShortCut('class2l', LabelShortcutType.Off) + dock.setFieldShortCut('class2n', LabelShortcutType.Off) for name in lyr.fields().names(): options = model.shortcuts(name) @@ -297,8 +297,8 @@ class testclassLabelingTest(unittest.TestCase): self.assertTrue(m.data(m.createIndex(3, 0), Qt.DisplayRole) == 'sensor') v = m.data(m.createIndex(3, 0), Qt.UserRole) - self.assertIsInstance(v, LabelShortCutType) - self.assertTrue(v == LabelShortCutType.Sensor) + self.assertIsInstance(v, LabelShortcutType) + self.assertTrue(v == LabelShortcutType.Sensor) lyr.selectByIds([1,2,3]) diff --git a/timeseriesviewer/labeling.py b/timeseriesviewer/labeling.py index 42690a17..b2a41089 100644 --- a/timeseriesviewer/labeling.py +++ b/timeseriesviewer/labeling.py @@ -13,11 +13,13 @@ from timeseriesviewer.classification.classificationscheme \ from timeseriesviewer.timeseries import TimeSeriesDatum +#the QgsProject(s) and QgsMapLayerStore(s) to search for QgsVectorLayers MAP_LAYER_STORES = [QgsProject.instance()] +CONFKEY_CLASSIFICATIONSCHEME = 'classificationScheme' +CONFKEY_LABELTYPE = 'labelKey' - -class LabelShortCutType(enum.Enum): +class LabelShortcutType(enum.Enum): """Enumeration for shortcuts to be derived from a TimeSeriesDatum instance""" Off = 'No label shortcut' Date = 'Date-Time' @@ -35,11 +37,11 @@ def shortcuts(field:QgsField): """ assert isinstance(field, QgsField) - shortCutsString = [LabelShortCutType.Sensor, LabelShortCutType.Date, LabelShortCutType.Classification] - shortCutsInt = [LabelShortCutType.Year, LabelShortCutType.DOY, LabelShortCutType.Classification] - shortCutsFloat = [LabelShortCutType.Year, LabelShortCutType.DOY, LabelShortCutType.DecimalYear, LabelShortCutType.Classification] + shortCutsString = [LabelShortcutType.Sensor, LabelShortcutType.Date, LabelShortcutType.Classification] + shortCutsInt = [LabelShortcutType.Year, LabelShortcutType.DOY, LabelShortcutType.Classification] + shortCutsFloat = [LabelShortcutType.Year, LabelShortcutType.DOY, LabelShortcutType.DecimalYear, LabelShortcutType.Classification] - options = [LabelShortCutType.Off] + options = [LabelShortcutType.Off] t = field.typeName().lower() if t == 'string': options.extend(shortCutsString) @@ -66,33 +68,53 @@ def layerClassSchemes(layer:QgsVectorLayer)->list: break return schemes +def labelShortcutLayerClassificationSchemes(layer:QgsVectorLayer): + """ + Returns the ClassificationSchemes used for labeling shortcuts + :param layer: QgsVectorLayer + :return: [list-of-classificationSchemes] + """ + classSchemes = [] + assert isinstance(layer, QgsVectorLayer) + for i in range(layer.fields().count()): + setup = layer.editorWidgetSetup(i) + assert isinstance(setup, QgsEditorWidgetSetup) + if setup.type() == EDITOR_WIDGET_REGISTRY_KEY: + conf = setup.config() + ci = conf.get(CONFKEY_CLASSIFICATIONSCHEME) + if isinstance(ci, ClassificationScheme) and ci not in classSchemes: + classSchemes.add(ci) + + return classSchemes def labelShortcutLayers()->list: """ Returns a list of all known QgsVectorLayer which define at least one LabelShortcutEditWidget :return: [list-of-QgsVectorLayer] """ - layers = set() + layers = [] classSchemes = set() for store in MAP_LAYER_STORES: assert isinstance(store, (QgsProject, QgsMapLayerStore)) for layer in store.mapLayers().values(): if isinstance(layer, QgsVectorLayer): - s ="" for i in range(layer.fields().count()): setup = layer.editorWidgetSetup(i) assert isinstance(setup, QgsEditorWidgetSetup) if setup.type() == EDITOR_WIDGET_REGISTRY_KEY: - conf = setup.config() - ci = conf.get('classificationScheme') - if isinstance(ci, ClassificationScheme): - classSchemes.add(ci) - - layers.add(layer) + if layer not in layers: + layers.append(layer) break - return layers, list(classSchemes) + return layers def applyShortcutsToRegisteredLayers(tsd:TimeSeriesDatum, classInfos:list): + """ + + :param tsd: + :param classInfos: + :return: + """ + for layer in labelShortcutLayers(): assert isinstance(layer, QgsVectorLayer) @@ -120,19 +142,19 @@ def applyShortcuts(vectorLayer:QgsVectorLayer, tsd:TimeSeriesDatum, classInfos:l assert isinstance(field, QgsField) conf = setup.config() - labelType = conf.get('labelType') - if isinstance(labelType, LabelShortCutType): + labelType = conf.get(CONFKEY_LABELTYPE) + if isinstance(labelType, LabelShortcutType): value = None - if labelType == LabelShortCutType.Sensor: + if labelType == LabelShortcutType.Sensor: value = tsd.sensor().name() - elif labelType == LabelShortCutType.DOY: + elif labelType == LabelShortcutType.DOY: value = tsd.doy() - elif labelType == LabelShortCutType.Date: + elif labelType == LabelShortcutType.Date: value = str(tsd.date()) - elif labelType == LabelShortCutType.DecimalYear: + elif labelType == LabelShortcutType.DecimalYear: value = tsd.decimalYear() - elif labelType == LabelShortCutType.Classification: - classScheme = conf.get('classificationScheme') + elif labelType == LabelShortcutType.Classification: + classScheme = conf.get(CONFKEY_CLASSIFICATIONSCHEME) if isinstance(classScheme, ClassificationScheme): for classInfo in classInfos: assert isinstance(classInfo, ClassInfo) @@ -220,7 +242,7 @@ class LabelAttributeTableModel(QAbstractTableModel): fields = self.mVectorLayer.fields() assert isinstance(fields, QgsFields) names = fields.names() - names = [n for n in names if n in self.mLabelTypes.keys() and self.mLabelTypes[n] != LabelShortCutType.Off] + names = [n for n in names if n in self.mLabelTypes.keys() and self.mLabelTypes[n] != LabelShortcutType.Off] for feature in self.mVectorLayer.selectedFeatures(): fid = feature.id() @@ -230,14 +252,14 @@ class LabelAttributeTableModel(QAbstractTableModel): assert isinstance(field, QgsField) labelType = self.mLabelTypes[name] value = None - if isinstance(labelType, LabelShortCutType): - if labelType == LabelShortCutType.Sensor: + if isinstance(labelType, LabelShortcutType): + if labelType == LabelShortcutType.Sensor: value = tsd.sensor().name() - elif labelType == LabelShortCutType.DOY: + elif labelType == LabelShortcutType.DOY: value = tsd.doy() - elif labelType == LabelShortCutType.Date: + elif labelType == LabelShortcutType.Date: value = str(tsd.date()) - elif labelType == LabelShortCutType.DecimalYear: + elif labelType == LabelShortcutType.DecimalYear: value = tsd.decimalYear() else: pass @@ -272,7 +294,7 @@ class LabelAttributeTableModel(QAbstractTableModel): for i in range(fields.count()): field = fields.at(i) assert isinstance(field, QgsField) - self.mLabelTypes[field.name()] = LabelShortCutType.Off + self.mLabelTypes[field.name()] = LabelShortcutType.Off self.endResetModel() @@ -312,11 +334,11 @@ class LabelAttributeTableModel(QAbstractTableModel): return len(self.mColumnNames) - def setFieldShortCut(self, fieldName:str, attributeType:LabelShortCutType): + def setFieldShortCut(self, fieldName:str, attributeType:LabelShortcutType): if isinstance(fieldName, QgsField): fieldName = fieldName.name() assert isinstance(fieldName, str) - assert isinstance(attributeType, LabelShortCutType) + assert isinstance(attributeType, LabelShortcutType) if self.hasVectorLayer(): fields = self.mVectorLayer.fields() @@ -364,7 +386,7 @@ class LabelAttributeTableModel(QAbstractTableModel): elif columnName == self.cnFieldType: value = '{}'.format(field.typeName()) elif columnName == self.cnLabel: - if isinstance(labelType, LabelShortCutType): + if isinstance(labelType, LabelShortcutType): value = labelType.name else: value = str(labelType) @@ -384,7 +406,7 @@ class LabelAttributeTableModel(QAbstractTableModel): oldTabelType = self.mLabelTypes.get(field.name()) changed = False if columnName == self.cnLabel and role == Qt.EditRole: - if isinstance(value, LabelShortCutType) and value != oldTabelType: + if isinstance(value, LabelShortcutType) and value != oldTabelType: self.mLabelTypes[field.name()] = value changed = True if changed: @@ -453,7 +475,7 @@ class LabelAttributeTypeWidgetDelegate(QStyledItemDelegate): if cname == model.cnLabel: w = QComboBox(parent=parent) for i, shortcutType in enumerate(model.shortcuts(index)): - assert isinstance(shortcutType, LabelShortCutType) + assert isinstance(shortcutType, LabelShortcutType) w.addItem(shortcutType.name, shortcutType) w.setItemData(i, shortcutType.value, Qt.ToolTipRole) return w @@ -467,7 +489,7 @@ class LabelAttributeTypeWidgetDelegate(QStyledItemDelegate): if index.isValid() and isinstance(model, LabelAttributeTableModel): if cname == model.cnLabel and isinstance(editor, QComboBox): labelType = model.data(index, role=Qt.UserRole) - assert isinstance(labelType, LabelShortCutType) + assert isinstance(labelType, LabelShortcutType) for i in range(editor.count()): d = editor.itemData(i) if d == labelType: @@ -512,7 +534,7 @@ class LabelingDock(QgsDockWidget, loadUI('labelingdock.ui')): self.initActions() self.onVectorLayerChanged() - def setFieldShortCut(self, fieldName:str, labelShortCut:LabelShortCutType): + def setFieldShortCut(self, fieldName:str, labelShortCut:LabelShortcutType): self.mLabelAttributeModel.setFieldShortCut(fieldName, labelShortCut) def onVectorLayerChanged(self): @@ -639,19 +661,19 @@ class LabelShortcutEditorConfigWidget(QgsEditorConfigWidget): def config(self, *args, **kwargs)->dict: conf = dict() - conf['labelType'] = self.mCBShortCutType.currentData() + conf[CONFKEY_LABELTYPE] = self.mCBShortCutType.currentData() cs = self.mClassWidget.classificationScheme() assert isinstance(cs, ClassificationScheme) #todo: json for serialization - conf['classificationScheme'] = cs + conf[CONFKEY_CLASSIFICATIONSCHEME] = cs return conf def setConfig(self, config:dict): self.mLastConfig = config - labelType = config.get('labelType') - if not isinstance(labelType, LabelShortCutType): - labelType = LabelShortCutType.Off + labelType = config.get(CONFKEY_LABELTYPE) + if not isinstance(labelType, LabelShortcutType): + labelType = LabelShortcutType.Off if labelType not in self.mAllowedShortCuts: labelType = self.mAllowedShortCuts[0] @@ -660,14 +682,14 @@ class LabelShortcutEditorConfigWidget(QgsEditorConfigWidget): self.mCBShortCutType.setCurrentIndex(i) - classScheme = config.get('classificationScheme') + classScheme = config.get(CONFKEY_CLASSIFICATIONSCHEME) if isinstance(classScheme, ClassificationScheme): self.mClassWidget.setClassificationScheme(classScheme) def onIndexChanged(self, *args): ltype = self.shortcutType() - if ltype == LabelShortCutType.Classification: + if ltype == LabelShortcutType.Classification: self.mClassWidget.setEnabled(True) else: self.mClassWidget.setEnabled(False) @@ -679,7 +701,7 @@ class LabelShortcutEditorConfigWidget(QgsEditorConfigWidget): assert isinstance(classScheme, ClassificationScheme) self.mClassWidget.setClassificationScheme(classScheme) - def shortcutType(self)->LabelShortCutType: + def shortcutType(self)->LabelShortcutType: return self.mCBShortCutType.currentData(Qt.UserRole) @@ -692,16 +714,16 @@ class LabelShortcutEditorWidgetWrapper(QgsEditorWidgetWrapper): self.mEditor = None self.mValidator = None - def configLabelType(self)->LabelShortCutType: - return self.config('labelType') + def configLabelType(self)->LabelShortcutType: + return self.config(CONFKEY_LABELTYPE) def configClassificationScheme(self)->ClassificationScheme: - return self.config('classificationScheme') + return self.config(CONFKEY_CLASSIFICATIONSCHEME) def createWidget(self, parent: QWidget): #log('createWidget') labelType = self.configLabelType() - if labelType == LabelShortCutType.Classification: + if labelType == LabelShortcutType.Classification: self.mEditor = ClassificationSchemeComboBox(parent) else: self.mEditor = QLineEdit(parent) diff --git a/timeseriesviewer/mapcanvas.py b/timeseriesviewer/mapcanvas.py index c57909d9..6a93b8c9 100644 --- a/timeseriesviewer/mapcanvas.py +++ b/timeseriesviewer/mapcanvas.py @@ -35,7 +35,9 @@ from .utils import * from .timeseries import TimeSeriesDatum from .crosshair import CrosshairDialog, CrosshairStyle from .maptools import * -from .labeling import LabelAttributeTableModel +from .labeling import LabelAttributeTableModel, labelShortcutLayers, layerClassSchemes, applyShortcutsToRegisteredLayers +from .classification.classificationscheme import ClassificationScheme, ClassInfo +import timeseriesviewer.settings class MapCanvasLayerModel(QAbstractTableModel): @@ -342,7 +344,7 @@ class MapCanvas(QgsMapCanvas): self.mTSD = self.mMapView = None - self.mLabelingModel = None + #self.mLabelingModel = None #the canvas self.mIsRefreshing = False self.mRenderingFinished = True @@ -420,9 +422,9 @@ class MapCanvas(QgsMapCanvas): assert isinstance(mapView, MapView) self.mMapView = mapView - def setLabelingModel(self, model): - assert isinstance(model, (LabelAttributeTableModel, None)) - self.mLabelingModel = model + #def setLabelingModel(self, model): + # assert isinstance(model, (LabelAttributeTableModel, None)) + # self.mLabelingModel = model def setTSD(self, tsd:TimeSeriesDatum): @@ -827,20 +829,38 @@ class MapCanvas(QgsMapCanvas): action.triggered.connect(lambda: self.saveMapImageDialog('JPG')) menu.addSeparator() - from timeseriesviewer.labeling import labelShortcutLayers - labelLayers = labelShortcutLayers() - - if len(labelLayers) == 0: - a = menu.addAction('No vector layers to label') - else: - a = menu.addAction('Label time & sensor attributes') - m = menu.addMenu('Class Labels...') - for + m = menu.addMenu('Label...') - if isinstance(self.mLabelingModel, LabelAttributeTableModel) and isinstance(self.mTSD, TimeSeriesDatum): - - m = self.mLabelingModel.contextMenuTSD(self.mTSD, menu) + labelLayers = labelShortcutLayers() + hasShortcutLayers = len(labelLayers) > 0 + lyrWithSelectedFeaturs = [l for l in labelLayers if len(l.selectedFeatureIds()) > 0] + hasSelectedFeaturs = len(lyrWithSelectedFeaturs) > 0 + + + a = m.addAction('Time & sensor') + a.setEnabled(hasShortcutLayers) + a.setToolTip('Write time and sensor attribute related to {}.'.format(self.tsd().date())) + + classSchemes = [] + for layer in lyrWithSelectedFeaturs: + for classScheme in layerClassSchemes(layer): + assert isinstance(classScheme, ClassificationScheme) + if classScheme in classSchemes: + continue + + classMenu = m.addMenu('Classification "{}"'.format(classScheme.name())) + assert isinstance(classMenu, QMenu) + for classInfo in classScheme: + assert isinstance(classInfo, ClassInfo) + a = classMenu.addAction(classInfo.name()) + a.setIcon(classInfo.icon()) + a.setToolTip('Write "{}" or "{}" to connected vector field attributes'.format(clasSInfo.name(), classInfo.value())) + + a.triggered.connect( + lambda tsd=self.tsd(), ci = classInfo: + applyShortcutsToRegisteredLayers(tsd, [ci])) + classSchemes.append(classScheme) menu.addSeparator() diff --git a/timeseriesviewer/mapvisualization.py b/timeseriesviewer/mapvisualization.py index 7c4f7171..da501faf 100644 --- a/timeseriesviewer/mapvisualization.py +++ b/timeseriesviewer/mapvisualization.py @@ -1744,7 +1744,7 @@ class SpatialTemporalVisualization(QObject): mapCanvas.setFixedSize(self.mSize) mapCanvas.setDestinationCrs(self.mCRS) mapCanvas.setSpatialExtent(self.mSpatialExtent) - mapCanvas.setLabelingModel(self.TSV.ui.dockLabeling.mLabelAttributeModel) + #register on map canvas signals def onChanged(e, mapCanvas0=None): -- GitLab