Skip to content

Commit

Permalink
added SpectralProfileFieldListModel, SpectralProfileFieldActivatorMod…
Browse files Browse the repository at this point in the history
…el, SpectralProfileFieldActivatorDialog

addresses #31

Signed-off-by: Benjamin Jakimow benjamin.jakimow@geo.hu-berlin.de <benjamin.jakimow@geo.hu-berlin.de>
  • Loading branch information
jakimowb committed Jan 19, 2024
1 parent bb6a8e6 commit 4bc1e88
Show file tree
Hide file tree
Showing 17 changed files with 567 additions and 102 deletions.
6 changes: 3 additions & 3 deletions qps/layerproperties.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
from . import DIR_UI_FILES
from .classification.classificationscheme import ClassificationScheme
from .models import OptionListModel, Option
from .speclib.core import supports_field
from .speclib.core import can_store_spectral_profiles
from .utils import write_vsimem, loadUi, defaultBands, iconForFieldType, qgsFields, copyEditorWidgetSetup
from .vectorlayertools import VectorLayerTools

Expand Down Expand Up @@ -398,7 +398,7 @@ def field(self):
:return:
"""
field = self.privateField()
if supports_field(field) and self.cbSpectralProfile.isEnabled() and self.cbSpectralProfile.isChecked():
if can_store_spectral_profiles(field) and self.cbSpectralProfile.isEnabled() and self.cbSpectralProfile.isChecked():
# field.setComment('Spectral Profile Field')
setup = QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, {})
field.setEditorWidgetSetup(setup)
Expand All @@ -420,7 +420,7 @@ def onTypeChanged(self, *args):
self.setPrecisionMinMax()

prototype = self.privateField()
self.cbSpectralProfile.setEnabled(supports_field(prototype))
self.cbSpectralProfile.setEnabled(can_store_spectral_profiles(prototype))

def setPrecisionMinMax(self):
ntype = self.currentNativeType()
Expand Down
7 changes: 6 additions & 1 deletion qps/speclib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@
"""
import pathlib

from qgis.PyQt.QtWidgets import QWidget
from qgis.PyQt.QtCore import NULL, QVariant
from qgis.core import QgsSettings, QgsCoordinateReferenceSystem, QgsField, QgsFields

EDITOR_WIDGET_REGISTRY_KEY = 'SpectralProfile'
EDITOR_WIDGET_REGISTRY_NAME = 'Spectral Profile'
# EDITOR_WIDGET_REGISTRY_NAME = 'Spectral Profile'

SPECLIB_EPSG_CODE = 4326

Expand Down Expand Up @@ -80,6 +81,10 @@ def speclibUiPath(name: str) -> str:
:return: absolute path to *.ui file
:rtype: str
"""

if isinstance(name, QWidget):
name = name.__class__.__name__.lower() + '.ui'

path = pathlib.Path(__file__).parent / 'ui' / name
assert path.is_file(), f'File does not exist: {path}'
return path.as_posix()
7 changes: 3 additions & 4 deletions qps/speclib/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

from qgis.PyQt.QtCore import QVariant
from qgis.core import QgsVectorLayer, QgsField, QgsFeature, QgsFields
from ...speclib import EDITOR_WIDGET_REGISTRY_KEY


def create_profile_field(*args, **kwds) -> QgsField:
from .spectrallibrary import SpectralLibraryUtils
return SpectralLibraryUtils.createProfileField(*args, **kwds)


def supports_field(field: QgsField) -> bool:
def can_store_spectral_profiles(field: QgsField) -> bool:
"""
Returns True if the QgsField can be used to store spectral profiles
"""
Expand Down Expand Up @@ -41,9 +40,9 @@ def is_profile_field(field: QgsField) -> bool:
return SpectralLibraryUtils.isProfileField(field)


def make_profile_field(field: QgsField) -> True:
def make_profile_field(field: QgsField) -> True:
from .spectrallibrary import SpectralLibraryUtils
return SpectralLibraryUtils.makeProfileField(field)
return SpectralLibraryUtils.makeToProfileField(field)


def contains_profile_field(object: Union[QgsVectorLayer, QgsFeature, QgsFields]) -> bool:
Expand Down
103 changes: 63 additions & 40 deletions qps/speclib/core/spectrallibrary.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,23 @@

from qgis.PyQt.QtCore import Qt, QVariant, QUrl, QMimeData
from qgis.PyQt.QtWidgets import QWidget
from qgis.core import (QgsWkbTypes, QgsExpressionContextUtils, QgsExpression, QgsRasterLayer,
QgsPointXY, QgsGeometry, QgsMapLayerStore, QgsProject, Qgis, edit)
from qgis.core import QgsApplication, QgsFeatureIterator, \
QgsFeature, QgsVectorLayer, QgsAttributeTableConfig, QgsField, QgsFields, QgsCoordinateReferenceSystem, \
QgsActionManager, QgsFeatureRequest, \
QgsEditorWidgetSetup, QgsAction, QgsProcessingFeedback, \
QgsRemappingProxyFeatureSink, QgsRemappingSinkDefinition, \
QgsExpressionContext, QgsCoordinateTransformContext, QgsProperty, QgsExpressionContextScope
from . import field_index, supports_field
from qgis.core import (QgsWkbTypes, QgsExpressionContextUtils, QgsExpression, QgsRasterLayer,
QgsPointXY, QgsGeometry, QgsMapLayerStore, QgsProject, Qgis, edit)
from . import can_store_spectral_profiles, is_profile_field
from . import profile_field_list, create_profile_field, \
is_spectral_library
from .spectralprofile import SpectralSetting, groupBySpectralProperties, prepareProfileValueDict, \
encodeProfileValueDict, ProfileEncoding
encodeProfileValueDict, ProfileEncoding, decodeProfileValueDict
from .. import EDITOR_WIDGET_REGISTRY_KEY, SPECLIB_EPSG_CODE
from .. import FIELD_VALUES, FIELD_NAME
from ...plotstyling.plotstyling import PlotStyle
from ...qgisenums import QGIS_WKBTYPE

from ...utils import findMapLayer, \
qgsField, copyEditorWidgetSetup, SpatialPoint

Expand Down Expand Up @@ -246,7 +245,7 @@ class SpectralLibraryUtils:
@staticmethod
def createProfileField(
name: str,
comment: str = 'SpectralProfile Field',
comment: str = None,
encoding: ProfileEncoding = ProfileEncoding.Text) -> QgsField:
"""
Creates a QgsField that can store spectral profiles
Expand All @@ -269,47 +268,44 @@ def createProfileField(

@staticmethod
def isProfileField(field: QgsField) -> bool:
return supports_field(field) and field.editorWidgetSetup().type() == EDITOR_WIDGET_REGISTRY_KEY
return can_store_spectral_profiles(field) and field.editorWidgetSetup().type() == EDITOR_WIDGET_REGISTRY_KEY

@staticmethod
def findDefaultProfileFields(layer: QgsVectorLayer):
def activateProfileFields(layer: QgsVectorLayer, check: str = 'first_feature'):
"""
Sets fields that can be used for spectral profiles and without a special editor widget to SpectralProfiles
Sets fields that can store spectral profiles and without a special editor widget to SpectralProfiles
editor widget.
Parameters
----------
layer
Returns
-------
:param check:
"""
testFeature = None
for f in layer.getFeatures():
testFeature = f
break
for field in layer.fields():
if field.editorWidgetSetup().type() == '':
SpectralLibraryUtils.makeProfileField(layer, field)

@staticmethod
def makeProfileField(layer: QgsVectorLayer, field: QgsField) -> bool:
"""
Changes the QgsEditorWidgetSetup to make a QgsField a SpectralProfile field
Parameters
----------
field
Returns: bool, True if successful. False if field type cannot be used to store spectral profiles.
-------
"""
field = qgsField(layer, field)
if supports_field(field):
i = layer.fields().lookupField(field.name())
layer.setEditorWidgetSetup(i, QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, {}))
return SpectralLibraryUtils.isProfileField(layer.fields().at(i))
return False
assert check in ['first_feature', 'field_type']
candidates = [f for f in layer.fields() if can_store_spectral_profiles(f)]

if check == 'field_type':
for f in candidates:
SpectralLibraryUtils.makeToProfileField(layer, f)
elif check == 'first_feature':
firstFeature = None
for f in layer.getFeatures():
firstFeature = f
break
if isinstance(firstFeature, QgsFeature):
for f in candidates:

try:
dump = firstFeature.attribute(f.name())
profileDict = decodeProfileValueDict(dump)
if isinstance(profileDict, dict) and len(profileDict) > 0:
SpectralLibraryUtils.makeToProfileField(layer, f)
except Exception:
s = ""
pass

@staticmethod
def writeToSource(*args, **kwds) -> List[str]:
Expand Down Expand Up @@ -463,6 +459,7 @@ def addAttribute(speclib: QgsVectorLayer, field: QgsField) -> bool:
i = speclib.fields().lookupField(field.name())
if i > -1:
speclib.setEditorWidgetSetup(i, field.editorWidgetSetup())
speclib.updatedFields.emit()
return success

@staticmethod
Expand Down Expand Up @@ -506,11 +503,37 @@ def initTableConfig(speclib: QgsVectorLayer):
speclib.setAttributeTableConfig(conf)

@staticmethod
def setAsProfileField(layer: QgsVectorLayer, field: Union[int, str, QgsField]):
idx = field_index(layer, field)
assert idx >= 0, 'Unknown field'
assert supports_field(layer.fields().field(idx)), 'Field cannot store spectral profiles'
layer.setEditorWidgetSetup(idx, QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, {}))
def removeProfileField(layer: QgsVectorLayer, field: Union[int, str, QgsField]) -> bool:
assert isinstance(layer, QgsVectorLayer)
field = qgsField(layer, field)

if field.editorWidgetSetup().type() == EDITOR_WIDGET_REGISTRY_KEY:
i = layer.fields().lookupField(field.name())
layer.setEditorWidgetSetup(i, QgsEditorWidgetSetup('', {}))
layer.updatedFields.emit()
return not is_profile_field(layer.fields()[field.name()])

@staticmethod
def makeToProfileField(layer: QgsVectorLayer, field: Union[int, str, QgsField]) -> bool:
"""
Changes the QgsEditorWidgetSetup to make a QgsField a SpectralProfile field
Parameters
----------
field
Returns: True if successful. False if field type cannot be used to store spectral profiles.
-------
"""
assert isinstance(layer, QgsVectorLayer)
field = qgsField(layer, field)
if not can_store_spectral_profiles(field):
return False
i = layer.fields().lookupField(field.name())
layer.setEditorWidgetSetup(i, QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, {}))

# inform that the field has been changed. see https://github.com/qgis/QGIS/issues/55873
layer.updatedFields.emit()
return SpectralLibraryUtils.isProfileField(layer.fields().at(i))

@staticmethod
def canReadFromMimeData(mimeData: QMimeData) -> bool:
Expand Down
1 change: 1 addition & 0 deletions qps/speclib/core/spectrallibraryio.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ def copyEditorWidgetSetup(cls, path: Union[str, pathlib.Path], fields: QgsFields
i = lyr.fields().lookupField(name)
if i >= 0:
lyr.setEditorWidgetSetup(i, fields.field(name).editorWidgetSetup())
lyr.updatedFields.emit()
msg, success = lyr.saveDefaultStyle(QgsMapLayer.StyleCategory.AllStyleCategories)
if not success:
print(msg, file=sys.stderr)
Expand Down
2 changes: 2 additions & 0 deletions qps/speclib/gui/spectrallibraryplotmodelitems.py
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,8 @@ def createEditor(self, parent):
model = self.model()
from ...speclib.gui.spectrallibraryplotwidget import SpectralProfilePlotModel
if isinstance(model, SpectralProfilePlotModel):
# emit updatedFields to apply latest changes
speclib.updatedFields.emit()
w.setModel(model.profileFieldsModel())
w.setToolTip(self.definition().description())

Expand Down
24 changes: 6 additions & 18 deletions qps/speclib/gui/spectrallibraryplotwidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
QTableView, QComboBox, QMenu, QStyledItemDelegate, QHBoxLayout, QTreeView, QStyleOptionViewItem
from qgis.PyQt.QtXml import QDomElement, QDomDocument
from qgis.core import QgsField, QgsSingleSymbolRenderer, QgsMarkerSymbol, \
QgsVectorLayer, QgsFieldModel, QgsFields, QgsSettings, QgsApplication, QgsExpressionContext, \
QgsVectorLayer, QgsSettings, QgsApplication, QgsExpressionContext, \
QgsFeatureRenderer, QgsRenderContext, QgsSymbol, QgsFeature, QgsFeatureRequest
from qgis.core import QgsProject, QgsMapLayerProxyModel
from qgis.core import QgsProperty, QgsExpressionContextScope
Expand All @@ -31,6 +31,7 @@
ProfileVisualizationGroup, PlotStyleItem, ProfileCandidateGroup, PropertyItemBase, ProfileCandidateItem, \
GeneralSettingsGroup, PropertyLabel
from .spectrallibraryplotunitmodels import SpectralProfilePlotXAxisUnitModel, SpectralProfilePlotXAxisUnitWidgetAction
from .spectralprofilefieldmodel import SpectralProfileFieldListModel
from .. import speclibUiPath
from ..core import profile_field_list, profile_field_indices, is_spectral_library, profile_fields
from ..core.spectralprofile import decodeProfileValueDict
Expand Down Expand Up @@ -93,7 +94,7 @@ def __init__(self, *args, **kwds):

self.mCACHE_PROFILE_DATA = dict()
self.mEnableCaching: bool = False
self.mProfileFieldModel: QgsFieldModel = QgsFieldModel()
self.mProfileFieldModel: SpectralProfileFieldListModel = SpectralProfileFieldListModel()

self.mPlotWidget: SpectralProfilePlotWidget = None
symbol = QgsMarkerSymbol.createSimple({'name': 'square', 'color': 'white'})
Expand Down Expand Up @@ -359,7 +360,7 @@ def __len__(self) -> int:
def __iter__(self) -> Iterator[ProfileVisualizationGroup]:
return iter(self.visualizations())

def profileFieldsModel(self) -> QgsFieldModel:
def profileFieldsModel(self) -> SpectralProfileFieldListModel:
return self.mProfileFieldModel

def propertyGroups(self) -> List[PropertyItemGroup]:
Expand All @@ -379,8 +380,7 @@ def visualizations(self) -> List[ProfileVisualizationGroup]:

def insertPropertyGroup(self,
index: Union[int, QModelIndex],
items: Union[PropertyItemGroup,
List[PropertyItemGroup]],
items: Union[PropertyItemGroup, List[PropertyItemGroup]],
):

# map to model index within group of same zValues
Expand Down Expand Up @@ -840,18 +840,15 @@ def setDualView(self, dualView: QgsDualView):
self.disconnectSpeclibSignals()

self.mSpeclib = speclib
self.mProfileFieldModel.setLayer(speclib)

# connect signals
if isinstance(self.mSpeclib, QgsVectorLayer):
self.mVectorLayerCache = QgsVectorLayerCache(speclib, 1000)
self.connectSpeclibSignals(self.mSpeclib)
self.updateProfileFieldModel()

def connectSpeclibSignals(self, speclib: QgsVectorLayer):

speclib.updatedFields.connect(self.updateProfileFieldModel)
speclib.attributeAdded.connect(self.updateProfileFieldModel)
speclib.attributeDeleted.connect(self.updateProfileFieldModel)
speclib.attributeValueChanged.connect(self.onSpeclibAttributeValueChanged)
speclib.editCommandStarted.connect(self.onSpeclibEditCommandStarted)
speclib.editCommandEnded.connect(self.onSpeclibEditCommandEnded)
Expand All @@ -865,9 +862,6 @@ def connectSpeclibSignals(self, speclib: QgsVectorLayer):
# speclib.willBeDeleted.connect(lambda *args, sl=speclib: self.disconnectSpeclibSignals(sl))

def disconnectSpeclibSignals(self, speclib: QgsVectorLayer):
speclib.updatedFields.disconnect(self.updateProfileFieldModel)
speclib.attributeAdded.disconnect(self.updateProfileFieldModel)
speclib.attributeDeleted.disconnect(self.updateProfileFieldModel)

speclib.editCommandStarted.disconnect(self.onSpeclibEditCommandStarted)
speclib.editCommandEnded.disconnect(self.onSpeclibEditCommandEnded)
Expand Down Expand Up @@ -933,12 +927,6 @@ def onSpeclibCommittedFeaturesAdded(self, id, features):

self.updatePlot(fids_to_update=OLD2NEW.values())

def updateProfileFieldModel(self, *args):
fields = QgsFields()
for f in profile_field_list(self.mSpeclib):
fields.append(f)
self.mProfileFieldModel.setFields(fields)

def onSpeclibStyleChanged(self, *args):
# self.loadFeatureColors()
b = False
Expand Down

0 comments on commit 4bc1e88

Please sign in to comment.