# -*- coding: utf-8 -*- """ /*************************************************************************** EO Time Series Viewer ------------------- begin : 2017-08-04 git sha : $Format:%H$ copyright : (C) 2017 by HU-Berlin email : benjamin.jakimow@geo.hu-berlin.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ """ # noinspection PyPep8Naming import os, sys, pickle, datetime from collections import OrderedDict from qgis.gui import * from qgis.core import * from qgis.PyQt.QtCore import * from qgis.PyQt.QtXml import * from qgis.PyQt.QtGui import * from .timeseries import * from .utils import SpatialExtent, SpatialPoint, px2geo, loadUI, nextColor from .externals.qps.plotstyling.plotstyling import PlotStyle, PlotStyleButton, PlotStyleDialog from .externals.pyqtgraph import ScatterPlotItem, SpotItem, GraphicsScene from .externals.qps.externals.pyqtgraph.GraphicsScene.mouseEvents import MouseClickEvent, MouseDragEvent from .externals import pyqtgraph as pg from .sensorvisualization import SensorListModel from .temporalprofiles import * from .temporalprofiles3d import * from .pixelloader import PixelLoaderTask, doLoaderTask import numpy as np DEBUG = False OPENGL_AVAILABLE = False ENABLE_OPENGL = False try: #import OpenGL OPENGL_AVAILABLE = True from eotimeseriesviewer.temporalprofiles3d import * #t = ViewWidget3D() #del t except Exception as ex: print('unable to import OpenGL based packages:\n{}'.format(ex)) def getTextColorWithContrast(c): assert isinstance(c, QColor) if c.lightness() < 0.5: return QColor('white') else: return QColor('black') def selectedModelIndices(tableView): assert isinstance(tableView, QTableView) result = {} sm = tableView.selectionModel() m = tableView.model() if isinstance(sm, QItemSelectionModel) and isinstance(m, QAbstractItemModel): for idx in sm.selectedIndexes(): assert isinstance(idx, QModelIndex) if idx.row() not in result.keys(): result[idx.row()] = idx return result.values() class _SensorPoints(pg.PlotDataItem): def __init__(self, *args, **kwds): super(_SensorPoints, self).__init__(*args, **kwds) # menu creation is deferred because it is expensive and often # the user will never see the menu anyway. self.menu = None def boundingRect(self): return super(_SensorPoints,self).boundingRect() def paint(self, p, *args): super(_SensorPoints, self).paint(p, *args) # On right-click, raise the context menu def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.RightButton: if self.raiseContextMenu(ev): ev.accept() def raiseContextMenu(self, ev): menu = self.getContextMenus() # Let the scene add on to the end of our context menu # (this is optional) menu = self.scene().addParentContextMenus(self, menu, ev) pos = ev.screenPos() menu.popup(QtCore.QPoint(pos.x(), pos.y())) return True # This method will be called when this item's _children_ want to raise # a context menu that includes their parents' menus. def getContextMenus(self, event=None): if self.menu is None: self.menu = QMenu() self.menu.setTitle(self.name + " options..") green = QAction("Turn green", self.menu) green.triggered.connect(self.setGreen) self.menu.addAction(green) self.menu.green = green blue = QAction("Turn blue", self.menu) blue.triggered.connect(self.setBlue) self.menu.addAction(blue) self.menu.green = blue alpha = QWidgetAction(self.menu) alphaSlider = QSlider() alphaSlider.setOrientation(QtCore.Qt.Horizontal) alphaSlider.setMaximum(255) alphaSlider.setValue(255) alphaSlider.valueChanged.connect(self.setAlpha) alpha.setDefaultWidget(alphaSlider) self.menu.addAction(alpha) self.menu.alpha = alpha self.menu.alphaSlider = alphaSlider return self.menu class SensorPixelDataMemoryLayer(QgsVectorLayer): def __init__(self, sensor, crs=None): assert isinstance(sensor, SensorInstrument) if crs is None: crs = QgsCoordinateReferenceSystem('EPSG:4862') uri = 'Point?crs={}'.format(crs.authid()) super(SensorPixelDataMemoryLayer, self).__init__(uri, 'Pixels_sensor_' + sensor.name(), 'memory', False) self.mSensor = sensor #initialize fields assert self.startEditing() # standard field names, types, etc. fieldDefs = [('pxid', QVariant.String, 'integer'), ('date', QVariant.String, 'char'), ('doy', QVariant.Int, 'integer'), ('geo_x', QVariant.Double, 'decimal'), ('geo_y', QVariant.Double, 'decimal'), ('px_x', QVariant.Int, 'integer'), ('px_y', QVariant.Int, 'integer'), ] # one field for each band for b in range(sensor.nb): fName = 'b{}'.format(b + 1) fieldDefs.append((fName, QVariant.Double, 'decimal')) # initialize fields for fieldDef in fieldDefs: field = QgsField(fieldDef[0], fieldDef[1], fieldDef[2]) self.addAttribute(field) self.commitChanges() def sensor(self): return self.mSensor def nPixels(self): raise NotImplementedError() def dates(self): raise NotImplementedError() class PlotSettingsModel3D(QAbstractTableModel): #sigSensorAdded = pyqtSignal(SensorPlotSettings) sigVisibilityChanged = pyqtSignal(TemporalProfile2DPlotStyle) sigPlotStylesAdded = pyqtSignal(list) sigPlotStylesRemoved = pyqtSignal(list) def __init__(self, parent=None, *args): #assert isinstance(tableView, QTableView) super(PlotSettingsModel3D, self).__init__(parent=parent) self.mTimeSeries = None self.cnID = 'ID' self.cnExpression = LABEL_EXPRESSION_3D self.cnTemporalProfile = 'Coordinate' self.cnStyle = 'Style' self.cnSensor = 'Sensor' self.columnNames = [self.cnTemporalProfile, self.cnSensor, self.cnStyle, self.cnExpression] self.mPlotSettings = [] #assert isinstance(plotWidget, DateTimePlotWidget) self.sortColumnIndex = 0 self.sortOrder = Qt.AscendingOrder self.sort(0, Qt.AscendingOrder) def hasStyleForSensor(self, sensor): assert isinstance(sensor, SensorInstrument) for plotStyle in self.mPlotSettings: assert isinstance(plotStyle, TemporalProfile3DPlotStyle) if plotStyle.sensor() == sensor: return True return False def onSensorRemoved(self, sensor): assert isinstance(sensor, SensorInstrument) self.removePlotStyles([s for s in self.mPlotSettings if s.sensor() == sensor]) def __len__(self): return len(self.mPlotSettings) def __iter__(self): return iter(self.mPlotSettings) def __getitem__(self, slice): return self.mPlotSettings[slice] def __contains__(self, item): return item in self.mPlotSettings def columnIndex(self, name): return self.columnNames.index(name) def insertPlotStyles(self, plotStyles, i=None): """ Inserts PlotStyle :param plotStyles: TemporalProfilePlotStyle | [list-of-TemporalProfilePlotStyle] :param i: index to insert, defaults to the last list position """ if isinstance(plotStyles, TemporalProfile3DPlotStyle): plotStyles = [plotStyles] assert isinstance(plotStyles, list) for plotStyle in plotStyles: assert isinstance(plotStyle, TemporalProfile3DPlotStyle) if i is None: i = len(self.mPlotSettings) if len(plotStyles) > 0: self.beginInsertRows(QModelIndex(), i, i + len(plotStyles)-1) for j, plotStyle in enumerate(plotStyles): assert isinstance(plotStyle, TemporalProfile3DPlotStyle) self.mPlotSettings.insert(i+j, plotStyle) self.endInsertRows() self.sigPlotStylesAdded.emit(plotStyles) def removePlotStyles(self, plotStyles): """ Removes PlotStyle instances :param plotStyles: TemporalProfilePlotStyle | [list-of-TemporalProfilePlotStyle] """ if isinstance(plotStyles, TemporalProfile3DPlotStyle): plotStyles = [plotStyles] assert isinstance(plotStyles, list) if len(plotStyles) > 0: for plotStyle in plotStyles: assert isinstance(plotStyle, TemporalProfile3DPlotStyle) if plotStyle in self.mPlotSettings: idx = self.plotStyle2idx(plotStyle) self.beginRemoveRows(QModelIndex(), idx.row(),idx.row()) self.mPlotSettings.remove(plotStyle) self.endRemoveRows() self.sigPlotStylesRemoved.emit(plotStyles) def sort(self, col, order): if self.rowCount() == 0: return colName = self.columnames[col] r = order != Qt.AscendingOrder #self.beginMoveRows(idxSrc, if colName == self.cnSensor: self.mPlotSettings.sort(key = lambda sv:sv.sensor().name(), reverse=r) def rowCount(self, parent = QModelIndex()): return len(self.mPlotSettings) def removeRows(self, row, count , parent = QModelIndex()): self.beginRemoveRows(parent, row, row + count-1) toRemove = self.mPlotSettings[row:row + count] for tsd in toRemove: self.mPlotSettings.remove(tsd) self.endRemoveRows() def plotStyle2idx(self, plotStyle): assert isinstance(plotStyle, TemporalProfile3DPlotStyle) if plotStyle in self.mPlotSettings: i = self.mPlotSettings.index(plotStyle) return self.createIndex(i, 0) else: return QModelIndex() def idx2plotStyle(self, index): if index.isValid() and index.row() < self.rowCount(): return self.mPlotSettings[index.row()] return None def columnCount(self, parent = QModelIndex()): return len(self.columnNames) def data(self, index, role = Qt.DisplayRole): if role is None or not index.isValid(): return None value = None columnName = self.columnNames[index.column()] plotStyle = self.idx2plotStyle(index) if isinstance(plotStyle, TemporalProfile3DPlotStyle): sensor = plotStyle.sensor() #print(('data', columnName, role)) if role == Qt.DisplayRole: if columnName == self.cnSensor: if isinstance(sensor, SensorInstrument): value = sensor.name() else: value = '<Select Sensor>' elif columnName == self.cnExpression: value = plotStyle.expression() elif columnName == self.cnTemporalProfile: tp = plotStyle.temporalProfile() if isinstance(tp, TemporalProfile): value = tp.name() else: value = 'undefined' elif role == Qt.EditRole: if columnName == self.cnExpression: value = plotStyle.expression() elif role == Qt.CheckStateRole: if columnName == self.cnTemporalProfile: value = Qt.Checked if plotStyle.isVisible() else Qt.Unchecked elif role == Qt.UserRole: value = plotStyle if columnName == self.cnSensor: value = plotStyle.sensor() elif columnName == self.cnStyle: value = plotStyle else: value = plotStyle #print(('get data',value)) return value def setData(self, index, value, role=None): if role is None or not index.isValid(): return False #print(('Set data', index.row(), index.column(), value, role)) columnName = self.columnNames[index.column()] if value is None: return False result = False plotStyle = self.idx2plotStyle(index) if isinstance(plotStyle, TemporalProfile3DPlotStyle): """ if role in [Qt.DisplayRole]: if columnName == self.cnExpression and isinstance(value, str): plotStyle.setExpression(value) result = True elif columnName == self.cnStyle: if isinstance(value, PlotStyle): plotStyle.copyFrom(value) result = True """ if role == Qt.CheckStateRole: if columnName == self.cnTemporalProfile: plotStyle.setVisibility(value == Qt.Checked) result = True if role == Qt.EditRole: if columnName == self.cnSensor: plotStyle.setSensor(value) result = True elif columnName == self.cnExpression: plotStyle.setExpression(value) result = True elif columnName == self.cnTemporalProfile: plotStyle.setTemporalProfile(value) result = True elif columnName == self.cnStyle: #set the style and trigger an update lastItemType = plotStyle.itemType() lastExpression = plotStyle.expression() plotStyle.copyFrom(value) if lastItemType != plotStyle.itemType() or \ lastExpression != plotStyle.expression(): plotStyle.updateDataProperties() else: plotStyle.updateStyleProperties() result = True return result def flags(self, index): if index.isValid(): stype = self.idx2plotStyle(index) columnName = self.columnNames[index.column()] flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if columnName in [self.cnTemporalProfile]: flags = flags | Qt.ItemIsUserCheckable if columnName in [self.cnTemporalProfile, self.cnSensor, self.cnExpression, self.cnStyle]: #allow check state flags = flags | Qt.ItemIsEditable return flags #return item.qt_flags(index.column()) return Qt.NoItemFlags def headerData(self, col, orientation, role): if Qt is None: return None if orientation == Qt.Horizontal and role == Qt.DisplayRole: return self.columnNames[col] elif orientation == Qt.Vertical and role == Qt.DisplayRole: return col return None class PlotSettingsModel2D(QAbstractTableModel): # sigSensorAdded = pyqtSignal(SensorPlotSettings) sigVisibilityChanged = pyqtSignal(TemporalProfile2DPlotStyle) sigDataChanged = pyqtSignal(TemporalProfile2DPlotStyle) sigPlotStylesAdded = pyqtSignal(list) sigPlotStylesRemoved = pyqtSignal(list) def __init__(self, parent=None, *args): super(PlotSettingsModel2D, self).__init__(parent=parent) self.cnID = 'ID' self.cnSensor = 'Sensor' self.cnExpression = LABEL_EXPRESSION_2D self.cnStyle = 'Style' self.cnTemporalProfile = 'Coordinate' self.columnNames = [self.cnTemporalProfile, self.cnSensor, self.cnStyle, self.cnExpression] self.mPlotSettings = [] self.mIconSize = QSize(25, 25) self.dataChanged.connect(self.signaler) def __len__(self): return len(self.mPlotSettings) def __iter__(self): return iter(self.mPlotSettings) def __getitem__(self, slice): return self.mPlotSettings[slice] def __contains__(self, item): return item in self.mPlotSettings def testSlot(self, *args): print(('TESTSLOT', args)) def columnIndex(self, name): return self.columnNames.index(name) def signaler(self, idxUL, idxLR): if idxUL.isValid(): plotStyle = self.idx2plotStyle(idxUL) cname = self.columnNames[idxUL.column()] if cname in [self.cnSensor,self.cnStyle]: self.sigVisibilityChanged.emit(plotStyle) if cname in [self.cnExpression]: self.sigDataChanged.emit(plotStyle) def requiredBandsIndices(self, sensor)->list: """ Returns the band indices required to calculate the values for the different PlotStyle expressions making use of sensor :param sensor: SensorInstrument for which the band indices are to be returned. :return: [list-of-band-indices] """ bandIndices = set() assert isinstance(sensor, SensorInstrument) for p in [p for p in self.mPlotSettings if p.sensor() == sensor]: assert isinstance(p, TemporalProfile2DPlotStyle) expression = p.expression() #remove leading & tailing " bandKeys = regBandKey.findall(expression) for bandIndex in [bandKey2bandIndex(key) for key in bandKeys]: bandIndices.add(bandIndex) return bandIndices def insertPlotStyles(self, plotStyles, i=None): """ Inserts PlotStyle :param plotStyles: TemporalProfilePlotStyle | [list-of-TemporalProfilePlotStyle] :param i: index to insert, defaults to the last list position """ if isinstance(plotStyles, TemporalProfile2DPlotStyle): plotStyles = [plotStyles] assert isinstance(plotStyles, list) for plotStyle in plotStyles: assert isinstance(plotStyle, TemporalProfile2DPlotStyle) if i is None: i = len(self.mPlotSettings) if len(plotStyles) > 0: self.beginInsertRows(QModelIndex(), i, i + len(plotStyles)-1) for j, plotStyle in enumerate(plotStyles): assert isinstance(plotStyle, TemporalProfile2DPlotStyle) plotStyle.sigExpressionUpdated.connect(lambda s = plotStyle: self.onStyleUpdated(s)) self.mPlotSettings.insert(i+j, plotStyle) self.endInsertRows() self.sigPlotStylesAdded.emit(plotStyles) def onStyleUpdated(self, style): idx = self.plotStyle2idx(style) r = idx.row() self.dataChanged.emit(self.createIndex(r, 0), self.createIndex(r, self.columnCount())) s = "" def removePlotStyles(self, plotStyles): """ Removes PlotStyle instances :param plotStyles: TemporalProfilePlotStyle | [list-of-TemporalProfilePlotStyle] """ if isinstance(plotStyles, PlotStyle): plotStyles = [plotStyles] assert isinstance(plotStyles, list) if len(plotStyles) > 0: for plotStyle in plotStyles: assert isinstance(plotStyle, PlotStyle) if plotStyle in self.mPlotSettings: idx = self.plotStyle2idx(plotStyle) self.beginRemoveRows(QModelIndex(), idx.row(),idx.row()) self.mPlotSettings.remove(plotStyle) self.endRemoveRows() self.sigPlotStylesRemoved.emit(plotStyles) def rowCount(self, parent = QModelIndex()): return len(self.mPlotSettings) def removeRows(self, row, count , parent = QModelIndex()): self.beginRemoveRows(parent, row, row + count-1) toRemove = self.mPlotSettings[row:row + count] for tsd in toRemove: self.mPlotSettings.remove(tsd) self.endRemoveRows() def plotStyle2idx(self, plotStyle): assert isinstance(plotStyle, TemporalProfile2DPlotStyle) if plotStyle in self.mPlotSettings: i = self.mPlotSettings.index(plotStyle) return self.createIndex(i, 0) else: return QModelIndex() def idx2plotStyle(self, index): if index.isValid() and index.row() < self.rowCount(): return self.mPlotSettings[index.row()] return None def columnCount(self, parent = QModelIndex()): return len(self.columnNames) def data(self, index, role = Qt.DisplayRole): if role is None or not index.isValid(): return None value = None columnName = self.columnNames[index.column()] plotStyle = self.idx2plotStyle(index) if isinstance(plotStyle, TemporalProfile2DPlotStyle): sensor = plotStyle.sensor() #print(('data', columnName, role)) if role == Qt.DisplayRole: if columnName == self.cnSensor: if isinstance(sensor, SensorInstrument): value = sensor.name() else: value = '<Select Sensor>' elif columnName == self.cnExpression: value = plotStyle.expression() elif columnName == self.cnTemporalProfile: tp = plotStyle.temporalProfile() if isinstance(tp, TemporalProfile): value = '{} "{}"'.format(tp.id(), tp.name()) else: value = 'undefined' elif role == Qt.CheckStateRole: if columnName == self.cnTemporalProfile: value = Qt.Checked if plotStyle.isVisible() else Qt.Unchecked elif role == Qt.UserRole: value = plotStyle if columnName == self.cnSensor: value = plotStyle.sensor() elif columnName == self.cnExpression: value = plotStyle.expression() elif columnName == self.cnStyle: value = plotStyle elif columnName == self.cnTemporalProfile: value == plotStyle.temporalProfile() else: value = plotStyle #print(('get data',value)) return value def setData(self, index, value, role=None): if role is None or not index.isValid(): return False #print(('Set data', index.row(), index.column(), value, role)) columnName = self.columnNames[index.column()] result = False plotStyle = self.idx2plotStyle(index) if isinstance(plotStyle, TemporalProfile2DPlotStyle): if role in [Qt.DisplayRole]: if columnName == self.cnExpression: plotStyle.setExpression(value) plotStyle.updateDataProperties() result = True elif columnName == self.cnStyle: if isinstance(value, PlotStyle): plotStyle.copyFrom(value) plotStyle.updateStyleProperties() result = True if role == Qt.CheckStateRole: if columnName == self.cnTemporalProfile: plotStyle.setVisibility(value == Qt.Checked) result = True if role == Qt.EditRole: if columnName == self.cnExpression: plotStyle.setExpression(value) result = True elif columnName == self.cnStyle: plotStyle.copyFrom(value) result = True elif columnName == self.cnSensor: plotStyle.setSensor(value) result = True elif columnName == self.cnTemporalProfile: plotStyle.setTemporalProfile(value) result = True if result: #save plot-style self.savePlotSettings(plotStyle, index='DEFAULT') self.dataChanged.emit(index, index) return result def savePlotSettings(self, sensorPlotSettings, index='DEFAULT'): return def restorePlotSettings(self, sensor, index='DEFAULT'): return None def flags(self, index): if index.isValid(): columnName = self.columnNames[index.column()] flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if columnName in [self.cnTemporalProfile]: flags = flags | Qt.ItemIsUserCheckable if columnName in [self.cnTemporalProfile, self.cnSensor, self.cnExpression, self.cnStyle]: #allow check state flags = flags | Qt.ItemIsEditable return flags #return item.qt_flags(index.column()) return Qt.NoItemFlags def headerData(self, col, orientation, role): if Qt is None: return None if orientation == Qt.Horizontal and role == Qt.DisplayRole: return self.columnNames[col] elif orientation == Qt.Vertical and role == Qt.DisplayRole: return col return None class PlotSettingsTableView(QTableView): def __init__(self, *args, **kwds): super(PlotSettingsTableView, self).__init__(*args, **kwds) pal = self.palette() cSelected = pal.color(QPalette.Active, QPalette.Highlight) pal.setColor(QPalette.Inactive, QPalette.Highlight, cSelected) self.setPalette(pal) def contextMenuEvent(self, event: QContextMenuEvent): """ Creates and shows the QMenu :param event: QContextMenuEvent """ indices = self.selectionModel().selectedIndexes() indices2 = [self.model().mapToSource(idx) for idx in indices] if len(indices2) > 0: menu = QMenu(self) a = menu.addAction('Set Style') a.triggered.connect(lambda *args, i=indices2: self.onSetStyle(i)) menu.popup(QCursor.pos()) def onSetStyle(self, indices): if len(indices) > 0: refStyle = None model = self.model().sourceModel() assert isinstance(model, PlotSettingsModel2D) colStyle = model.columnIndex(model.cnStyle) for idx in indices: style = self.model().sourceModel().idx2plotStyle(idx) if isinstance(style, PlotStyle): refStyle = style break if isinstance(refStyle, PlotStyle): newStyle = PlotStyleDialog.getPlotStyle(plotStyle=refStyle) if isinstance(newStyle, PlotStyle): for idx in indices: assert isinstance(idx, QModelIndex) idxStyle = model.createIndex(idx.row(), colStyle) model.setData(idxStyle, newStyle, role=Qt.EditRole) class PlotSettingsModel2DWidgetDelegate(QStyledItemDelegate): """ """ def __init__(self, tableView, temporalProfileLayer, parent=None): assert isinstance(tableView, QTableView) assert isinstance(temporalProfileLayer, TemporalProfileLayer) super(PlotSettingsModel2DWidgetDelegate, self).__init__(parent=parent) self._preferedSize = QgsFieldExpressionWidget().sizeHint() self.mTableView = tableView self.mTemporalProfileLayer = temporalProfileLayer self.mTimeSeries = temporalProfileLayer.timeSeries() self.mSensorLayers = {} def paint(self, painter: QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex): if index.column() == 2: style = self.style(index) h = self.mTableView.verticalHeader().defaultSectionSize() w = self.mTableView.horizontalHeader().defaultSectionSize() if h > 0 and w > 0: px = style.createPixmap(size=QSize(w, h)) label = QLabel() label.setPixmap(px) painter.drawPixmap(option.rect, px) #QApplication.style().drawControl(QStyle.CE_CustomBase, label, painter) else: super(PlotSettingsModel2DWidgetDelegate, self).paint(painter, option, index) else: super(PlotSettingsModel2DWidgetDelegate, self).paint(painter, option, index) def sortFilterProxyModel(self)->QSortFilterProxyModel: return self.mTableView.model() def plotSettingsModel(self)->PlotSettingsModel2D: return self.sortFilterProxyModel().sourceModel() def setItemDelegates(self, tableView): assert isinstance(tableView, QTableView) model = self.plotSettingsModel() assert isinstance(model, PlotSettingsModel2D) for c in [model.cnSensor, model.cnExpression, model.cnStyle, model.cnTemporalProfile]: i = model.columnNames.index(c) tableView.setItemDelegateForColumn(i, self) def getColumnName(self, index): assert index.isValid() model = self.plotSettingsModel() assert isinstance(model, PlotSettingsModel2D) return model.columnNames[index.column()] """ def sizeHint(self, options, index): s = super(ExpressionDelegate, self).sizeHint(options, index) exprString = self.tableView.model().data(index) l = QLabel() l.setText(exprString) x = l.sizeHint().width() + 100 s = QSize(x, s.height()) return self._preferedSize """ def exampleLyr(self, sensor): # if isinstance(sensor, SensorInstrument): if sensor not in self.mSensorLayers.keys(): crs = QgsCoordinateReferenceSystem('EPSG:4862') uri = 'Point?crs={}'.format(crs.authid()) lyr = QgsVectorLayer(uri, 'LOCATIONS', 'memory') f = sensorExampleQgsFeature(sensor) assert isinstance(f, QgsFeature) assert lyr.startEditing() for field in f.fields(): lyr.addAttribute(field) lyr.addFeature(f) lyr.commitChanges() self.mSensorLayers[sensor] = lyr return self.mSensorLayers[sensor] def createEditor(self, parent, option, index): cname = self.getColumnName(index) model = self.plotSettingsModel() pmodel = self.sortFilterProxyModel() w = None if index.isValid() and isinstance(model, PlotSettingsModel2D): plotStyle = model.idx2plotStyle(pmodel.mapToSource(index)) if isinstance(plotStyle, TemporalProfile2DPlotStyle): if cname == model.cnExpression: w = QgsFieldExpressionWidget(parent=parent) w.setExpressionDialogTitle('Values') w.setToolTip('Set an expression to specify the image band or calculate a spectral index.') w.fieldChanged[str, bool].connect(lambda n, b: self.checkData(index, w, w.expression())) w.setExpression(plotStyle.expression()) if isinstance(plotStyle.sensor(), SensorInstrument): w.setLayer(self.exampleLyr(plotStyle.sensor())) elif cname == model.cnStyle: w = PlotStyleButton(parent=parent) w.setPlotStyle(plotStyle) w.setToolTip('Set style.') w.sigPlotStyleChanged.connect(lambda: self.checkData(index, w, w.plotStyle())) elif cname == model.cnSensor: w = QComboBox(parent=parent) m = SensorListModel(self.mTimeSeries) w.setModel(m) elif cname == model.cnTemporalProfile: w = QgsFeatureListComboBox(parent=parent) w.setSourceLayer(self.mTemporalProfileLayer) w.setIdentifierField(FN_ID) w.setDisplayExpression('to_string("{}")+\' \'+"name"'.format(FN_ID)) w.setAllowNull(False) else: raise NotImplementedError() return w def checkData(self, index, w, value): assert isinstance(index, QModelIndex) model = self.mTableView.model() if index.isValid() and isinstance(model, PlotSettingsModel2D): plotStyle = model.idx2plotStyle(index) assert isinstance(plotStyle, TemporalProfile2DPlotStyle) if isinstance(w, QgsFieldExpressionWidget): assert value == w.expression() assert w.isExpressionValid(value) == w.isValidExpression() if w.isValidExpression(): self.commitData.emit(w) else: s = "" #print(('Delegate commit failed',w.asExpression())) if isinstance(w, PlotStyleButton): self.commitData.emit(w) def style(self, proxyIndex:QModelIndex)->PlotStyle: model = self.plotSettingsModel() index = self.sortFilterProxyModel().mapToSource(proxyIndex) return model.data(index, role=Qt.UserRole) def setEditorData(self, editor, proxyIndex): model = self.plotSettingsModel() index = self.sortFilterProxyModel().mapToSource(proxyIndex) w = None if index.isValid() and isinstance(model, PlotSettingsModel2D): cname = self.getColumnName(proxyIndex) if cname == model.cnExpression: lastExpr = model.data(index, Qt.DisplayRole) assert isinstance(editor, QgsFieldExpressionWidget) editor.setProperty('lastexpr', lastExpr) editor.setField(lastExpr) elif cname == model.cnStyle: style = model.data(index, Qt.UserRole) assert isinstance(editor, PlotStyleButton) editor.setPlotStyle(style) elif cname == model.cnSensor: assert isinstance(editor, QComboBox) m = editor.model() assert isinstance(m, SensorListModel) sensor = index.data(role=Qt.UserRole) if isinstance(sensor, SensorInstrument): idx = m.sensor2idx(sensor) editor.setCurrentIndex(idx.row()) elif cname == model.cnTemporalProfile: assert isinstance(editor, QgsFeatureListComboBox) value = editor.identifierValue() if value != QVariant(): plotStyle = index.data(role=Qt.UserRole) TP = plotStyle.temporalProfile() editor.setIdentifierValue(TP.id()) else: s = "" else: raise NotImplementedError() def setModelData(self, w, model, proxyIndex): index = self.sortFilterProxyModel().mapToSource(proxyIndex) cname = self.getColumnName(proxyIndex) model = self.plotSettingsModel() if index.isValid() and isinstance(model, PlotSettingsModel2D): if cname == model.cnExpression: assert isinstance(w, QgsFieldExpressionWidget) expr = w.asExpression() exprLast = model.data(index, Qt.DisplayRole) if w.isValidExpression() and expr != exprLast: model.setData(index, w.asExpression(), Qt.EditRole) elif cname == model.cnStyle: if isinstance(w, PlotStyleButton): style = w.plotStyle() model.setData(index, style, Qt.EditRole) elif cname == model.cnSensor: assert isinstance(w, QComboBox) sensor = w.itemData(w.currentIndex(), role=Qt.UserRole) if isinstance(sensor, SensorInstrument): model.setData(index, sensor, Qt.EditRole) elif cname == model.cnTemporalProfile: assert isinstance(w, QgsFeatureListComboBox) fid = w.identifierValue() if isinstance(fid, int): # once set manually, do not update to last temporal profile any more oldStyle = index.data(role=Qt.UserRole) if isinstance(oldStyle, TemporalProfile2DPlotStyle): oldStyle.mShowLastLocation = False TP = self.mTemporalProfileLayer.mProfiles.get(fid) model.setData(index, TP, Qt.EditRole) else: raise NotImplementedError() class PlotSettingsModel3DWidgetDelegate(QStyledItemDelegate): """ """ def __init__(self, tableView, temporalProfileLayer, parent=None): assert isinstance(tableView, QTableView) assert isinstance(temporalProfileLayer, TemporalProfileLayer) super(PlotSettingsModel3DWidgetDelegate, self).__init__(parent=parent) self._preferedSize = QgsFieldExpressionWidget().sizeHint() self.mTableView = tableView self.mTimeSeries = temporalProfileLayer.timeSeries() self.mTemporalProfileLayer = temporalProfileLayer self.mSensorLayers = {} def setItemDelegates(self, tableView): assert isinstance(tableView, QTableView) model = tableView.model() assert isinstance(model, PlotSettingsModel3D) for c in [model.cnSensor, model.cnExpression, # model.cnStyle, model.cnTemporalProfile]: i = model.columnNames.index(c) tableView.setItemDelegateForColumn(i, self) def getColumnName(self, index): assert index.isValid() model = index.model() assert isinstance(model, PlotSettingsModel3D) return model.columnNames[index.column()] """ def sizeHint(self, options, index): s = super(ExpressionDelegate, self).sizeHint(options, index) exprString = self.tableView.model().data(index) l = QLabel() l.setText(exprString) x = l.sizeHint().width() + 100 s = QSize(x, s.height()) return self._preferedSize """ def createEditor(self, parent, option, index): cname = self.getColumnName(index) model = self.mTableView.model() w = None if index.isValid() and isinstance(model, PlotSettingsModel3D): plotStyle = model.idx2plotStyle(index) if isinstance(plotStyle, TemporalProfile3DPlotStyle): if cname == model.cnExpression: w = QgsFieldExpressionWidget(parent=parent) w.setExpression(plotStyle.expression()) w.setLayer(self.exampleLyr(plotStyle.sensor())) def onSensorAdded(s): w.setLayer(self.exampleLyr(s)) #plotStyle.sigSensorChanged.connect(lambda s : w.setLayer(self.exampleLyr(s))) plotStyle.sigSensorChanged.connect(onSensorAdded) w.setExpressionDialogTitle('Values') w.setToolTip('Set an expression to specify the image band or calculate a spectral index.') w.fieldChanged[str, bool].connect(lambda n, b : self.checkData(index, w, w.expression())) elif cname == model.cnStyle: w = TemporalProfile3DPlotStyleButton(parent=parent) w.setPlotStyle(plotStyle) w.setToolTip('Set plot style') w.sigPlotStyleChanged.connect(lambda: self.checkData(index, w, w.plotStyle())) elif cname == model.cnSensor: w = QComboBox(parent=parent) m = SensorListModel(self.mTimeSeries) w.setModel(m) elif cname == model.cnTemporalProfile: w = QgsFeatureListComboBox(parent=parent) w.setSourceLayer(self.mTemporalProfileLayer) w.setIdentifierField('id') w.setDisplayExpression('to_string("id")+\' \'+"name"') w.setAllowNull(False) else: raise NotImplementedError() return w def exampleLyr(self, sensor): if sensor not in self.mSensorLayers.keys(): crs = QgsCoordinateReferenceSystem('EPSG:4862') uri = 'Point?crs={}'.format(crs.authid()) lyr = QgsVectorLayer(uri, 'LOCATIONS', 'memory') assert sensor is None or isinstance(sensor, SensorInstrument) f = sensorExampleQgsFeature(sensor, singleBandOnly=True) assert isinstance(f, QgsFeature) assert lyr.startEditing() for field in f.fields(): lyr.addAttribute(field) lyr.addFeature(f) lyr.commitChanges() self.mSensorLayers[sensor] = lyr return self.mSensorLayers[sensor] def checkData(self, index, w, value): assert isinstance(index, QModelIndex) model = self.mTableView.model() if index.isValid() and isinstance(model, PlotSettingsModel3D): plotStyle = model.idx2plotStyle(index) assert isinstance(plotStyle, TemporalProfile3DPlotStyle) if isinstance(w, QgsFieldExpressionWidget): assert value == w.expression() assert w.isExpressionValid(value) == w.isValidExpression() if w.isValidExpression(): self.commitData.emit(w) else: s = "" #print(('Delegate commit failed',w.asExpression())) if isinstance(w, TemporalProfile3DPlotStyleButton): self.commitData.emit(w) def setEditorData(self, editor, index): cname = self.getColumnName(index) model = self.mTableView.model() w = None if index.isValid() and isinstance(model, PlotSettingsModel3D): cname = self.getColumnName(index) style = model.idx2plotStyle(index) if cname == model.cnExpression: lastExpr = index.model().data(index, Qt.DisplayRole) assert isinstance(editor, QgsFieldExpressionWidget) editor.setProperty('lastexpr', lastExpr) editor.setField(lastExpr) elif cname == model.cnStyle: assert isinstance(editor, TemporalProfile3DPlotStyleButton) editor.setPlotStyle(style) elif cname == model.cnSensor: assert isinstance(editor, QComboBox) m = editor.model() assert isinstance(m, SensorListModel) sensor = index.data(role=Qt.UserRole) if isinstance(sensor, SensorInstrument): idx = m.sensor2idx(sensor) editor.setCurrentIndex(idx.row()) elif cname == model.cnTemporalProfile: assert isinstance(editor, QgsFeatureListComboBox) value = editor.identifierValue() if value != QVariant(): plotStyle = index.data(role=Qt.UserRole) TP = plotStyle.temporalProfile() editor.setIdentifierValue(TP.id()) else: s = "" else: raise NotImplementedError() def setModelData(self, w, model, index): cname = self.getColumnName(index) model = self.mTableView.model() if index.isValid() and isinstance(model, PlotSettingsModel3D): if cname == model.cnExpression: assert isinstance(w, QgsFieldExpressionWidget) expr = w.asExpression() exprLast = model.data(index, Qt.DisplayRole) if w.isValidExpression() and expr != exprLast: model.setData(index, w.asExpression(), Qt.EditRole) elif cname == model.cnStyle: assert isinstance(w, TemporalProfile3DPlotStyleButton) model.setData(index, w.plotStyle(), Qt.EditRole) elif cname == model.cnSensor: assert isinstance(w, QComboBox) sensor = w.itemData(w.currentIndex(), role=Qt.UserRole) assert isinstance(sensor, SensorInstrument) model.setData(index, sensor, Qt.EditRole) s = "" elif cname == model.cnTemporalProfile: assert isinstance(w, QgsFeatureListComboBox) fid = w.identifierValue() if isinstance(fid, int): TP = self.mTemporalProfileLayer.mProfiles.get(fid) model.setData(index, TP, Qt.EditRole) else: raise NotImplementedError() class ProfileViewDockUI(QgsDockWidget, loadUI('profileviewdock.ui')): def __init__(self, parent=None): super(ProfileViewDockUI, self).__init__(parent) self.setupUi(self) self.addActions(self.findChildren(QAction)) self.mActions2D = [self.actionAddStyle2D, self.actionRemoveStyle2D, self.actionRefresh2D, self.actionReset2DPlot] self.mActions3D = [self.actionAddStyle3D, self.actionRemoveStyle3D, self.actionRefresh3D, self.actionReset3DCamera] self.mActionsTP = [self.actionLoadTPFromOgr, self.actionSaveTemporalProfiles, self.actionToggleEditing, self.actionRemoveTemporalProfile, self.actionLoadMissingValues] #TBD. #self.line.setVisible(False) #self.listWidget.setVisible(False) self.baseTitle = self.windowTitle() self.stackedWidget.currentChanged.connect(self.onStackPageChanged) self.plotWidget3D = None self.plotWidget3DMPL = None self.init3DWidgets('gl') #pi = self.plotWidget2D.plotItem #ax = DateAxis(orientation='bottom', showValues=True) #pi.layout.addItem(ax, 3,2) self.TS = None self.progressBar.setMinimum(0) self.progressBar.setMaximum(100) self.progressBar.setValue(0) self.progressInfo.setText('') self.pxViewModel2D = None self.pxViewModel3D = None self.tableView2DProfiles.horizontalHeader().setResizeMode(QHeaderView.Interactive) def init3DWidgets(self, mode='gl'): assert mode in ['gl'] l = self.frame3DPlot.layout() if ENABLE_OPENGL and OPENGL_AVAILABLE and mode == 'gl': from eotimeseriesviewer.temporalprofiles3dGL import ViewWidget3D self.plotWidget3D = ViewWidget3D(parent=self.labelDummy3D.parent()) self.plotWidget3D.setObjectName('plotWidget3D') size = self.labelDummy3D.size() l.addWidget(self.plotWidget3D) self.plotWidget3D.setSizePolicy(self.labelDummy3D.sizePolicy()) self.labelDummy3D.setVisible(False) l.removeWidget(self.labelDummy3D) #self.plotWidget3D.setBaseSize(size) self.splitter3D.setSizes([100, 100]) self.frameSettings3D.setEnabled(True) else: self.frameSettings3D.setEnabled(False) def onStackPageChanged(self, i): w = self.stackedWidget.currentWidget() title = self.baseTitle if w == self.page2D: title = '{} | 2D'.format(title) for a in self.mActions2D: a.setVisible(True) for a in self.mActions3D + self.mActionsTP: a.setVisible(False) elif w == self.page3D: title = '{} | 3D (experimental!)'.format(title) for a in self.mActions2D + self.mActionsTP: a.setVisible(False) for a in self.mActions3D: a.setVisible(True) elif w == self.pagePixel: title = '{} | Coordinates'.format(title) for a in self.mActions2D + self.mActions3D: a.setVisible(False) for a in self.mActionsTP: a.setVisible(True) w.update() self.setWindowTitle(title) class SpectralTemporalVisualization(QObject): sigShowPixel = pyqtSignal(TimeSeriesDate, QgsPoint, QgsCoordinateReferenceSystem) """ Signalizes to move to specific date of interest """ sigMoveToDate = pyqtSignal(np.datetime64) def __init__(self, timeSeries, profileDock): super(SpectralTemporalVisualization, self).__init__() assert isinstance(profileDock, ProfileViewDockUI) self.ui = profileDock self.mTasks = dict() #import eotimeseriesviewer.pixelloader #if DEBUG: # eotimeseriesviewer.pixelloader.DEBUG = True #the timeseries. will be set later assert isinstance(timeSeries, TimeSeries) self.TS = timeSeries self.plot_initialized = False self.plot2D = self.ui.plotWidget2D assert isinstance(self.plot2D, DateTimePlotWidget) self.plot2D.getViewBox().sigMoveToDate.connect(self.sigMoveToDate) self.plot2D.getViewBox().scene().sigMouseClicked.connect(self.onPointsClicked2D) self.plot3D = self.ui.plotWidget3D self.mLast2DMouseClickPosition = None # temporal profile collection to store loaded values self.mTemporalProfileLayer = TemporalProfileLayer(self.TS) self.mTemporalProfileLayer.sigTemporalProfilesAdded.connect(self.onTemporalProfilesAdded) self.mTemporalProfileLayer.selectionChanged.connect(self.onTemporalProfileSelectionChanged) # fix to not loose C++ reference on temporal profile layer in case it is removed from QGIS mapcanvas self.mMapCanvas = QgsMapCanvas() self.mMapCanvas.setVisible(False) self.mMapCanvas.setLayers([self.mTemporalProfileLayer]) # self.tpCollectionListModel = TemporalProfileCollectionListModel(self.tpCollection) assert isinstance(self.ui.mDualView, QgsDualView) self.ui.mDualView.init(self.mTemporalProfileLayer, self.mMapCanvas) self.ui.mDualView.setView(QgsDualView.AttributeTable) # pixel loader to load pixel values in parallel config = QgsAttributeTableConfig() config.update(self.mTemporalProfileLayer.fields()) config.setActionWidgetVisible(False) hidden = [] for i, columnConfig in enumerate(config.columns()): assert isinstance(columnConfig, QgsAttributeTableConfig.ColumnConfig) config.setColumnHidden(i, columnConfig.name in hidden) self.mTemporalProfilesTableConfig = config self.mTemporalProfileLayer.setAttributeTableConfig(self.mTemporalProfilesTableConfig) #self.pixelLoader = PixelLoader() #self.pixelLoader.sigPixelLoaded.connect(self.onPixelLoaded) #self.pixelLoader.sigLoadingStarted.connect(self.onLoadingStarted) #self.pixelLoader.sigLoadingFinished.connect(self.onLoadingFinished) #self.pixelLoader.sigProgressChanged.connect(self.onLoadingProgressChanged) # set the plot models for 2D self.plotSettingsModel2D = PlotSettingsModel2D() self.plotSettingsModel2DProxy = QSortFilterProxyModel() self.plotSettingsModel2DProxy.setSourceModel(self.plotSettingsModel2D) self.ui.tableView2DProfiles.setModel(self.plotSettingsModel2DProxy) self.ui.tableView2DProfiles.setSortingEnabled(True) self.ui.tableView2DProfiles.selectionModel().selectionChanged.connect(self.onPlot2DSelectionChanged) self.ui.tableView2DProfiles.horizontalHeader().setResizeMode(QHeaderView.Interactive) self.plotSettingsModel2D.sigDataChanged.connect(self.requestUpdate) self.plotSettingsModel2D.rowsInserted.connect(self.onRowsInserted2D) self.delegateTableView2D = PlotSettingsModel2DWidgetDelegate(self.ui.tableView2DProfiles, self.mTemporalProfileLayer) self.delegateTableView2D.setItemDelegates(self.ui.tableView2DProfiles) #self.ui.tableView2DProfiles.horizontalHeader().sectionResized.connect(self.on2DSettingsTableColumnResize) # set the plot models for 3D self.plotSettingsModel3D = PlotSettingsModel3D() self.ui.tableView3DProfiles.setModel(self.plotSettingsModel3D) self.ui.tableView3DProfiles.setSortingEnabled(False) self.ui.tableView2DProfiles.selectionModel().selectionChanged.connect(self.onPlot3DSelectionChanged) self.ui.tableView3DProfiles.horizontalHeader().setResizeMode(QHeaderView.Interactive) self.plotSettingsModel3D.rowsInserted.connect(self.onRowsInserted3D) self.delegateTableView3D = PlotSettingsModel3DWidgetDelegate(self.ui.tableView3DProfiles, self.mTemporalProfileLayer) self.delegateTableView3D.setItemDelegates(self.ui.tableView3DProfiles) if not ENABLE_OPENGL: self.ui.listWidget.item(1).setHidden(True) self.ui.page3D.setHidden(True) def onTemporalProfilesRemoved(removedProfiles): #set to valid temporal profile affectedStyles2D = [p for p in self.plotSettingsModel2D if p.temporalProfile() in removedProfiles] affectedStyles3D = [p for p in self.plotSettingsModel3D if p.temporalProfile() in removedProfiles] alternativeProfile = self.mTemporalProfileLayer[-1] if len(self.mTemporalProfileLayer) > 0 else None for s in affectedStyles2D: assert isinstance(s, TemporalProfile2DPlotStyle) m = self.plotSettingsModel2D idx = m.plotStyle2idx(s) idx = m.createIndex(idx.row(), m.columnNames.index(m.cnTemporalProfile)) m.setData(idx, alternativeProfile, Qt.EditRole) for s in affectedStyles3D: assert isinstance(s, TemporalProfile3DPlotStyle) m = self.plotSettingsModel3D idx = m.plotStyle2idx(s) idx = m.createIndex(idx.row(), m.columnNames.index(m.cnTemporalProfile)) m.setData(idx, alternativeProfile, Qt.EditRole) self.mTemporalProfileLayer.sigTemporalProfilesRemoved.connect(onTemporalProfilesRemoved) # remove / add plot style def on2DPlotStyleRemoved(plotStyles): for plotStyle in plotStyles: assert isinstance(plotStyle, PlotStyle) for pi in plotStyle.mPlotItems: self.plot2D.plotItem.removeItem(pi) def on3DPlotStyleRemoved(plotStyles): toRemove = [] for plotStyle in plotStyles: assert isinstance(plotStyle, TemporalProfile3DPlotStyle) toRemove.append(plotStyle.mPlotItems) self.plot3D.removeItems(toRemove) self.plotSettingsModel2D.sigPlotStylesRemoved.connect(on2DPlotStyleRemoved) self.plotSettingsModel3D.sigPlotStylesRemoved.connect(on3DPlotStyleRemoved) # initialize the update loop self.updateRequested = True self.updateTimer = QTimer(self) self.updateTimer.timeout.connect(self.onDataUpdate) self.updateTimer.start(2000) self.sigMoveToDate.connect(self.onMoveToDate) self.initActions() #self.ui.stackedWidget.setCurrentPage(self.ui.pagePixel) self.ui.onStackPageChanged(self.ui.stackedWidget.currentIndex()) def onLoadingStarted(self): self.ui.progressInfo.setText('Start loading...') self.ui.progressBar.setRange(0, 0) self.ui.progressBar.setValue(0) def onLoadingProgressChanged(self, progress): if self.ui.progressBar.maximum() == 0: self.ui.progressBar.setRange(0, 100) value = int(progress) self.ui.progressBar.setValue(value) if value == 100: self.plot2D.enableAutoRange('x', False) self.ui.progressInfo.setText('') self.ui.progressBar.setValue(0) def plotStyles(self): return self.plotSettingsModel2D[:] def temporalProfileLayer(self)->TemporalProfileLayer: """ Returns a QgsVectorLayer that is used to store profile coordinates. :return: """ return self.mTemporalProfileLayer def selected2DPlotStyles(self): result = [] m = self.ui.tableView2DProfiles.model() for idx in selectedModelIndices(self.ui.tableView2DProfiles): result.append(m.data(idx, Qt.UserRole)) return result def removePlotStyles2D(self, plotStyles): m = self.ui.tableView2DProfiles.model() if isinstance(m.sourceModel(), PlotSettingsModel2D): m.sourceModel().removePlotStyles(plotStyles) def removeTemporalProfiles(self, fids): self.mTemporalProfileLayer.selectByIds(fids) b = self.mTemporalProfileLayer.isEditable() self.mTemporalProfileLayer.startEditing() self.mTemporalProfileLayer.deleteSelectedFeatures() self.mTemporalProfileLayer.saveEdits(leaveEditable=b) def createNewPlotStyle2D(self): l = len(self.mTemporalProfileLayer) plotStyle = TemporalProfile2DPlotStyle() plotStyle.sigExpressionUpdated.connect(self.updatePlot2D) sensors = self.TS.sensors() if len(sensors) > 0: plotStyle.setSensor(sensors[0]) if len(self.mTemporalProfileLayer) > 0: temporalProfile = self.mTemporalProfileLayer[0] plotStyle.setTemporalProfile(temporalProfile) if len(self.plotSettingsModel2D) > 0: lastStyle = self.plotSettingsModel2D[0] #top style in list is the most-recent assert isinstance(lastStyle, TemporalProfile2DPlotStyle) markerColor = nextColor(lastStyle.markerBrush.color()) plotStyle.markerBrush.setColor(markerColor) self.plotSettingsModel2D.insertPlotStyles([plotStyle], i=0) pdi = plotStyle.createPlotItem(self.plot2D) assert isinstance(pdi, TemporalProfilePlotDataItem) #pdi.sigClicked.connect(self.onProfileClicked2D) #pdi.sigPointsClicked.connect(self.onPointsClicked2D) self.plot2D.plotItem.addItem(pdi) #self.plot2D.getPlotItem().addItem(pg.PlotDataItem(x=[1, 2, 3], y=[1, 2, 3])) #plotItem.addDataItem(pdi) #plotItem.plot().sigPlotChanged.emit(plotItem) self.updatePlot2D() return plotStyle def createNewPlotStyle3D(self): if not (ENABLE_OPENGL and OPENGL_AVAILABLE): return plotStyle = TemporalProfile3DPlotStyle() plotStyle.sigExpressionUpdated.connect(self.updatePlot3D) if len(self.mTemporalProfileLayer) > 0: temporalProfile = self.mTemporalProfileLayer[0] plotStyle.setTemporalProfile(temporalProfile) if len(self.plotSettingsModel3D) > 0: color = self.plotSettingsModel3D[-1].color() plotStyle.setColor(nextColor(color)) sensors = list(self.TS.mSensors2TSDs.keys()) if len(sensors) > 0: plotStyle.setSensor(sensors[0]) self.plotSettingsModel3D.insertPlotStyles([plotStyle], i=0) # latest to the top plotItems = plotStyle.createPlotItem() self.plot3D.addItems(plotItems) #self.updatePlot3D() def onPointsClicked2D(self, event: MouseClickEvent): info = [] assert isinstance(event, MouseClickEvent) for item in self.plot2D.scene().itemsNearEvent(event): if isinstance(item, ScatterPlotItem) and isinstance(item.parentItem(), TemporalProfilePlotDataItem): pdi = item.parentItem() assert isinstance(pdi, TemporalProfilePlotDataItem) tp = pdi.mPlotStyle.temporalProfile() assert isinstance(tp, TemporalProfile) c = tp.coordinate() spottedItems = item.pointsAt(event.pos()) if len(spottedItems) > 0: info.append('Sensor: {}'.format(pdi.mPlotStyle.sensor().name())) info.append('Coordinate: {}, {}'.format(c.x(), c.y())) for item in spottedItems: if isinstance(item, SpotItem): brush1 = item.brush() brush2 = item.brush() brush2.setColor(QColor('yellow')) item.setBrush(brush2) QTimer.singleShot(500, lambda *args, spotItem=item, brush=brush1: spotItem.setBrush(brush)) pos = item.pos() self.mLast2DMouseClickPosition = pos x = pos.x() y = pos.y() date = num2date(x) info.append('{};{}'.format(date, y)) self.ui.tbInfo2D.setPlainText('\n'.join(info)) def onTemporalProfilesAdded(self, profiles): # self.mTemporalProfileLayer.prune() for plotStyle in self.plotSettingsModel3D: assert isinstance(plotStyle, TemporalProfilePlotStyleBase) if not isinstance(plotStyle.temporalProfile(), TemporalProfile): r = self.plotSettingsModel3D.plotStyle2idx(plotStyle).row() c = self.plotSettingsModel3D.columnIndex(self.plotSettingsModel3D.cnTemporalProfile) idx = self.plotSettingsModel3D.createIndex(r, c) self.plotSettingsModel3D.setData(idx, self.mTemporalProfileLayer[0]) def onTemporalProfileSelectionChanged(self, selectedFIDs, deselectedFIDs): nSelected = len(selectedFIDs) self.ui.actionRemoveTemporalProfile.setEnabled(nSelected > 0) def onPlot2DSelectionChanged(self, selected, deselected): self.ui.actionRemoveStyle2D.setEnabled(len(selected) > 0) # todo: highlight selected profiles in plot def onPlot3DSelectionChanged(self, selected, deselected): self.ui.actionRemoveStyle3D.setEnabled(len(selected) > 0) def initActions(self): self.ui.actionRemoveStyle2D.setEnabled(False) self.ui.actionRemoveTemporalProfile.setEnabled(False) self.ui.actionAddStyle2D.triggered.connect(self.createNewPlotStyle2D) self.ui.actionAddStyle3D.triggered.connect(self.createNewPlotStyle3D) self.ui.actionRefresh2D.triggered.connect(self.updatePlot2D) self.ui.actionRefresh3D.triggered.connect(self.updatePlot3D) self.ui.actionRemoveStyle2D.triggered.connect(lambda:self.removePlotStyles2D(self.selected2DPlotStyles())) self.ui.actionRemoveTemporalProfile.triggered.connect(lambda :self.removeTemporalProfiles(self.mTemporalProfileLayer.selectedFeatureIds())) self.ui.actionToggleEditing.triggered.connect(self.onToggleEditing) self.ui.actionReset2DPlot.triggered.connect(self.plot2D.resetViewBox) self.plot2D.resetTransform() self.ui.actionReset3DCamera.triggered.connect(self.reset3DCamera) self.ui.actionLoadTPFromOgr.triggered.connect(self.onLoadFromVector) self.ui.actionLoadMissingValues.triggered.connect(lambda: self.loadMissingData()) self.ui.actionSaveTemporalProfiles.triggered.connect(lambda *args : self.mTemporalProfileLayer.saveTemporalProfiles(None)) #set actions to be shown in the TemporalProfileTableView context menu ma = [self.ui.actionSaveTemporalProfiles, self.ui.actionLoadMissingValues] self.onEditingToggled() def onLoadFromVector(self): from .externals.qps.layerproperties import SelectMapLayersDialog d = SelectMapLayersDialog() d.addLayerDescription('Vector Layer', QgsMapLayerProxyModel.VectorLayer) d.setWindowTitle('Select Vector Layer') if d.exec() == QDialog.Accepted: for l in d.mapLayers(): self.mTemporalProfileLayer.loadCoordinatesFromOgr(l.source()) break def onToggleEditing(self, b): if self.mTemporalProfileLayer.isEditable(): self.mTemporalProfileLayer.saveEdits(leaveEditable=False) else: self.mTemporalProfileLayer.startEditing() self.onEditingToggled() def onEditingToggled(self): lyr = self.mTemporalProfileLayer hasSelectedFeatures = lyr.selectedFeatureCount() > 0 isEditable = lyr.isEditable() self.ui.actionToggleEditing.blockSignals(True) self.ui.actionToggleEditing.setChecked(isEditable) #self.actionSaveTemporalProfiles.setEnabled(isEditable) #self.actionReload.setEnabled(not isEditable) self.ui.actionToggleEditing.blockSignals(False) #self.actionAddAttribute.setEnabled(isEditable) #self.actionRemoveAttribute.setEnabled(isEditable) self.ui.actionRemoveTemporalProfile.setEnabled(isEditable and hasSelectedFeatures) #self.actionPasteFeatures.setEnabled(isEditable) self.ui.actionToggleEditing.setEnabled(not lyr.readOnly()) def reset3DCamera(self, *args): if ENABLE_OPENGL and OPENGL_AVAILABLE: self.ui.actionReset3DCamera.trigger() sigMoveToTSD = pyqtSignal(TimeSeriesDate) def onMoveToDate(self, date): dt = np.asarray([np.abs(tsd.date() - date) for tsd in self.TS]) i = np.argmin(dt) self.sigMoveToTSD.emit(self.TS[i]) def onPixelLoaded(self, qgsTask ,dump)->typing.List[TemporalProfile]: """ Updates TemporalProfiles :param qgsTask: :param dump: :return: [updated TemporalProfiles] """ tasks = pickle.loads(dump) assert isinstance(tasks, list) s = "" t0 = time.time() updatedTemporalProfiles = [] for task in tasks: assert isinstance(task, TemporalProfileLoaderTask) if len(task.mRESULTS) > 0: for tpId, data in task.mRESULTS.items(): tp = self.mTemporalProfileLayer.mProfiles.get(tpId) if isinstance(tp, TemporalProfile): tsd = tp.timeSeries().getTSD(task.mSourcePath) if isinstance(tsd, TimeSeriesDate): tp.updateData(tsd, data) if tp not in updatedTemporalProfiles: updatedTemporalProfiles.append(tp) if len(task.mERRORS) > 0: s = "" return updatedTemporalProfiles if False: print('WRESULTS {}'.format(time.time() - t0)) def requestUpdate(self, *args): self.updateRequested = True #next time def onRowsInserted2D(self, parent, start, end): model = self.ui.tableView2DProfiles.model().sourceModel() if isinstance(model, PlotSettingsModel2D): colExpression = model.columnIndex(model.cnExpression) colStyle = model.columnIndex(model.cnStyle) while start <= end: idxExpr = model.createIndex(start, colExpression) idxStyle = model.createIndex(start, colStyle) self.ui.tableView2DProfiles.openPersistentEditor(idxExpr) self.ui.tableView2DProfiles.openPersistentEditor(idxStyle) start += 1 def onRowsInserted3D(self, parent, start, end): model = self.ui.tableView3DProfiles.model() if isinstance(model, PlotSettingsModel3D): colExpression = model.columnIndex(model.cnExpression) colStyle = model.columnIndex(model.cnStyle) while start <= end: idxStyle = model.createIndex(start, colStyle) idxExpr = model.createIndex(start, colExpression) self.ui.tableView3DProfiles.openPersistentEditor(idxStyle) self.ui.tableView3DProfiles.openPersistentEditor(idxExpr) start += 1 def loadMissingData(self, backgroundProcess=True): """ Loads all band values of collected locations that have not been loaded until now """ fids = self.mTemporalProfileLayer.selectedFeatureIds() if len(fids) == 0: fids = [f.id() for f in self.mTemporalProfileLayer.getFeatures()] tps = [self.mTemporalProfileLayer.mProfiles.get(fid) for fid in fids] spatialPoints = [tp.coordinate() for tp in tps if isinstance(tp, TemporalProfile)] self.loadCoordinate(spatialPoints=spatialPoints, mode='all', backgroundProcess=backgroundProcess) LOADING_MODES = ['missing', 'reload', 'all'] def loadCoordinate(self, spatialPoints=None, LUT_bandIndices=None, mode='missing', backgroundProcess = True): """ :param spatialPoints: [list-of-geometries] to load pixel values from :param LUT_bandIndices: dictionary {sensor:[indices]} with band indices to be loaded per sensor :param mode: :return: """ """ Loads a temporal profile for a single or multiple geometries. :param spatialPoints: SpatialPoint | [list-of-SpatialPoints] """ assert mode in SpectralTemporalVisualization.LOADING_MODES if isinstance(spatialPoints, SpatialPoint): spatialPoints = [spatialPoints] assert isinstance(spatialPoints, list) if not isinstance(self.plotSettingsModel2D, PlotSettingsModel2D): return False # if not self.pixelLoader.isReadyToLoad(): # return False assert isinstance(self.TS, TimeSeries) # Get or create the TimeSeriesProfiles which will store the loaded values tasks = [] TemporalProfiles = [] # Define which (new) bands need to be loaded for each sensor if LUT_bandIndices is None: LUT_bandIndices = dict() for sensor in self.TS.sensors(): if mode in ['all', 'reload']: LUT_bandIndices[sensor] = list(range(sensor.nb)) else: LUT_bandIndices[sensor] = self.plotSettingsModel2D.requiredBandsIndices(sensor) assert isinstance(LUT_bandIndices, dict) for sensor in self.TS.sensors(): assert sensor in LUT_bandIndices.keys() # update new / existing points for spatialPoint in spatialPoints: assert isinstance(spatialPoint, SpatialPoint) TP = self.mTemporalProfileLayer.fromSpatialPoint(spatialPoint) # if no TemporalProfile existed before, create an empty one if not isinstance(TP, TemporalProfile): TP = self.mTemporalProfileLayer.createTemporalProfiles(spatialPoint)[0] # set existing plot style to current coordinate for plotStyle in self.plotSettingsModel2D: assert isinstance(plotStyle, TemporalProfile2DPlotStyle) if plotStyle.showLastLocation(): r = self.plotSettingsModel2D.plotStyle2idx(plotStyle).row() c = self.plotSettingsModel2D.columnIndex(self.plotSettingsModel2D.cnTemporalProfile) idx = self.plotSettingsModel2D.index(r, c) self.plotSettingsModel2D.setData(idx, TP, role=Qt.EditRole) plotStyle.setTemporalProfile(TP) # create at least 1 plot style if len(self.mTemporalProfileLayer) == 1: if len(self.plotSettingsModel2D) == 0: self.createNewPlotStyle2D() if len(self.plotSettingsModel3D) == 0: self.createNewPlotStyle3D() TemporalProfiles.append(TP) # each TSD is a Task s = "" # a Task defines which bands are to be loaded for tsd in self.TS: assert isinstance(tsd, TimeSeriesDate) # do not load from invisible TSDs if not tsd.isVisible(): continue # which bands do we need to load? requiredIndices = set(LUT_bandIndices[tsd.sensor()]) if len(requiredIndices) == 0: continue if mode == 'missing': missingIndices = set() for TP in TemporalProfiles: assert isinstance(TP, TemporalProfile) need2load = TP.missingBandIndices(tsd, requiredIndices=requiredIndices) missingIndices = missingIndices.union(need2load) missingIndices = sorted(list(missingIndices)) else: missingIndices = requiredIndices if len(missingIndices) > 0: for tss in tsd.sources(): assert isinstance(tss, TimeSeriesSource) intersectingTPs = [] tssExtent = tss.spatialExtent() for TP in TemporalProfiles: assert isinstance(TP, TemporalProfile) if tssExtent.contains(TP.coordinate().toCrs(tssExtent.crs())): intersectingTPs.append(TP) if len(intersectingTPs) > 0: task = TemporalProfileLoaderTask(tss, intersectingTPs) tasks.append(task) # pickle.loads(doLoaderTask(mock, pickle.dumps([t])))[0] if len(tasks) > 0: self.loadTemporalProfileTasks(tasks, runAsync=backgroundProcess) else: if DEBUG: print('Data for geometries already loaded') def loadTemporalProfileTasks(self, tasks:typing.Iterable[TemporalProfileLoaderTask], runAsync=True)->typing.List[TemporalProfile]: """ Loads data into TemporalProfiles :param tasks: :param runAsync: :return: [list-of-updated TemporalProfiles], empty if runAsyn """ dump = pickle.dumps(tasks) if runAsync: qgsTask = QgsTask.fromFunction('Load Profiles', doLoadTemporalProfileTasks, dump, on_finished=self.onPixelLoaded) else: qgsTask = TaskMock() tid = id(qgsTask) qgsTask.progressChanged.connect(self.onLoadingProgressChanged) qgsTask.taskCompleted.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) qgsTask.taskTerminated.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) self.mTasks[tid] = qgsTask if runAsync: tm = QgsApplication.taskManager() assert isinstance(tm, QgsTaskManager) tm.addTask(qgsTask, 1000) return [] else: return self.onPixelLoaded(qgsTask, doLoadTemporalProfileTasks(qgsTask, dump)) def onRemoveTask(self, tid): if tid in self.mTasks.keys(): del self.mTasks[tid] @QtCore.pyqtSlot() def onDataUpdate(self): # self.mTemporalProfileLayer.prune() for plotSetting in self.plotSettingsModel2D: assert isinstance(plotSetting, TemporalProfile2DPlotStyle) tp = plotSetting.temporalProfile() for pdi in plotSetting.mPlotItems: assert isinstance(pdi, TemporalProfilePlotDataItem) pdi.updateDataAndStyle() if isinstance(tp, TemporalProfile) and plotSetting.temporalProfile().updated(): plotSetting.temporalProfile().resetUpdatedFlag() for i in self.plot2D.plotItem.dataItems: i.updateItems() notInit = [0, 1] == self.plot2D.plotItem.getAxis('bottom').range if notInit: x0 = x1 = None for plotSetting in self.plotSettingsModel2D: assert isinstance(plotSetting, TemporalProfile2DPlotStyle) for pdi in plotSetting.mPlotItems: assert isinstance(pdi, TemporalProfilePlotDataItem) if pdi.xData.ndim == 0 or pdi.xData.shape[0] == 0: continue if x0 is None: x0 = pdi.xData.min() x1 = pdi.xData.max() else: x0 = min(pdi.xData.min(), x0) x1 = max(pdi.xData.max(), x1) if x0 is not None: self.plot2D.plotItem.setXRange(x0, x1) # self.plot2D.xAxisInitialized = True @QtCore.pyqtSlot() def updatePlot3D(self): if ENABLE_OPENGL and OPENGL_AVAILABLE: from eotimeseriesviewer.temporalprofiles3dGL import ViewWidget3D assert isinstance(self.plot3D, ViewWidget3D) # 1. ensure that data from all bands will be loaded # new loaded values will be shown in the next updatePlot3D call coordinates = [] allPlotItems = [] for plotStyle3D in self.plotSettingsModel3D: assert isinstance(plotStyle3D, TemporalProfile3DPlotStyle) if plotStyle3D.isPlotable(): coordinates.append(plotStyle3D.temporalProfile().coordinate()) if len(coordinates) > 0: self.loadCoordinate(coordinates, mode='all') toRemove = [item for item in self.plot3D.items if item not in allPlotItems] self.plot3D.removeItems(toRemove) toAdd = [item for item in allPlotItems if item not in self.plot3D.items] self.plot3D.addItems(toAdd) """ # 3. add new plot items plotItems = [] for plotStyle3D in self.plotSettingsModel3D: assert isinstance(plotStyle3D, TemporalProfile3DPlotStyle) if plotStyle3D.isPlotable(): items = plotStyle3D.createPlotItem(None) plotItems.extend(items) self.plot3D.addItems(plotItems) self.plot3D.updateDataRanges() self.plot3D.resetScaling() """ @QtCore.pyqtSlot() def updatePlot2D(self): if isinstance(self.plotSettingsModel2D, PlotSettingsModel2D): locations = set() for plotStyle in self.plotSettingsModel2D: assert isinstance(plotStyle, TemporalProfile2DPlotStyle) if plotStyle.isPlotable(): locations.add(plotStyle.temporalProfile().coordinate()) for pdi in plotStyle.mPlotItems: assert isinstance(pdi, TemporalProfilePlotDataItem) pdi.updateDataAndStyle() #2. load pixel data self.loadCoordinate(list(locations)) # https://github.com/pyqtgraph/pyqtgraph/blob/5195d9dd6308caee87e043e859e7e553b9887453/examples/customPlot.py return def examplePixelLoader(): # prepare QGIS environment if sys.platform == 'darwin': PATH_QGS = r'/Applications/QGIS.app/Contents/MacOS' os.environ['GDAL_DATA'] = r'/usr/local/Cellar/gdal/1.11.3_1/share' else: # assume OSGeo4W startup PATH_QGS = os.environ['QGIS_PREFIX_PATH'] assert os.path.exists(PATH_QGS) qgsApp = QgsApplication([], True) QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns') QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns/qgis') qgsApp.setPrefixPath(PATH_QGS, True) qgsApp.initQgis() gb = QGroupBox() gb.setTitle('Sandbox') PL = PixelLoader() if False: files = ['observationcloud/testdata/2014-07-26_LC82270652014207LGN00_BOA.bsq', 'observationcloud/testdata/2014-08-03_LE72270652014215CUB00_BOA.bsq' ] else: from eotimeseriesviewer.utils import file_search searchDir = r'H:\LandsatData\Landsat_NovoProgresso' files = list(file_search(searchDir, '*227065*band4.img', recursive=True)) #files = files[0:3] lyr = QgsRasterLayer(files[0]) coord = lyr.extent().center() crs = lyr.crs() l = QVBoxLayout() btnStart = QPushButton() btnStop = QPushButton() prog = QProgressBar() tboxResults = QPlainTextEdit() tboxResults.setMaximumHeight(300) tboxThreads = QPlainTextEdit() tboxThreads.setMaximumHeight(200) label = QLabel() label.setText('Progress') def showProgress(n,m,md): prog.setMinimum(0) prog.setMaximum(m) prog.setValue(n) info = [] for k, v in md.items(): info.append('{} = {}'.format(k,str(v))) tboxResults.setPlainText('\n'.join(info)) #tboxThreads.setPlainText(PL.threadInfo()) qgsApp.processEvents() PL.sigPixelLoaded.connect(showProgress) btnStart.setText('Start loading') btnStart.clicked.connect(lambda : PL.startLoading(files, coord, crs)) btnStop.setText('Cancel') btnStop.clicked.connect(lambda: PL.cancelLoading()) lh = QHBoxLayout() lh.addWidget(btnStart) lh.addWidget(btnStop) l.addLayout(lh) l.addWidget(prog) l.addWidget(tboxThreads) l.addWidget(tboxResults) gb.setLayout(l) gb.show() #rs.setBackgroundStyle('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #222, stop:1 #333);') #rs.handle.setStyleSheet('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #282, stop:1 #393);') qgsApp.exec_() qgsApp.exitQgis()