Skip to content
Snippets Groups Projects
profilevisualization.py 77.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • # -*- coding: utf-8 -*-
    """
    /***************************************************************************
                                  HUB TimeSeriesViewer
                                  -------------------
            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
    from __future__ import absolute_import
    
    import os, sys, pickle, datetime
    
    from collections import OrderedDict
    
    from qgis.gui import *
    from qgis.core import *
    
    from qgis.core import QgsExpression
    
    from PyQt4.QtCore import *
    from PyQt4.QtXml import *
    from PyQt4.QtGui import *
    
    from timeseriesviewer import jp, SETTINGS
    
    from timeseriesviewer.timeseries import *
    from timeseriesviewer.utils import SpatialExtent, SpatialPoint, px2geo
    
    from timeseriesviewer.ui.docks import TsvDockWidgetBase, loadUI
    
    from timeseriesviewer.plotstyling import PlotStyle, PlotStyleButton
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    from timeseriesviewer.pixelloader import PixelLoader, PixelLoaderTask
    from timeseriesviewer.sensorvisualization import SensorListModel
    
    from timeseriesviewer.temporalprofiles import *
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    from pyqtgraph import functions as fn
    from pyqtgraph import AxisItem
    
    
    import datetime
    
    
    from osgeo import gdal, gdal_array
    import numpy as np
    
    
    DEBUG = False
    
    
    LABEL_DN = 'DN or Index'
    LABEL_TIME = 'Date'
    
    
    OPENGL_AVAILABLE = False
    
    try:
        import OpenGL
        OPENGL_AVAILABLE = True
    
    
        from pyqtgraph.opengl import GLViewWidget
    
    
        class ViewWidget3D(GLViewWidget):
    
            def paintGL(self, *args, **kwds):
                GLViewWidget.paintGL(self, *args, **kwds)
                self.qglColor(Qt.white)
                self.renderAnnotations()
    
            def renderAnnotations(self):
    
    
                self.renderText(0.8, 0.8, 0.8, 'text 3D')
                self.renderText(5, 10, 'text 2D fixed')
    
        """
        class TemporalProfileGLLinePlotItem(gl.GLLinePlotItem):
    
            def __init__(self, plotStyle, *args, **kwds):
                assert isinstance(plotStyle, TemporalProfile3DPlotStyle)
    
            gl.GLLinePlotItem
        """
    
        if DEBUG:
            print('unable to import package OpenGL')
    
    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 DateTimeAxis(pg.AxisItem):
    
            super(DateTimeAxis, self).__init__(*args, **kwds)
    
            self.setRange(1,3000)
            self.enableAutoSIPrefix(False)
            self.labelAngle = 0
    
        def logTickStrings(self, values, scale, spacing):
            s = ""
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def tickStrings(self, values, scale, spacing):
            strns = []
    
    
            if len(values) == 0:
                return []
            #assert isinstance(values[0],
    
    
            values = [num2date(v) if v > 0 else num2date(1) for v in values]
    
            ndays = rng.astype(int)
    
            strns = []
    
            for v in values:
                if ndays == 0:
                    strns.append(v.astype(str))
    
        def tickValues(self, minVal, maxVal, size):
    
            d = super(DateTimeAxis, self).tickValues(minVal, maxVal, size)
    
        def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
    
    
            p.setRenderHint(p.Antialiasing, False)
            p.setRenderHint(p.TextAntialiasing, True)
    
            ## draw long line along axis
            pen, p1, p2 = axisSpec
            p.setPen(pen)
            p.drawLine(p1, p2)
            p.translate(0.5, 0)  ## resolves some damn pixel ambiguity
    
            ## draw ticks
            for pen, p1, p2 in tickSpecs:
                p.setPen(pen)
                p.drawLine(p1, p2)
    
    
            ## Draw all text
            if self.tickFont is not None:
                p.setFont(self.tickFont)
            p.setPen(self.pen())
    
            #for rect, flags, text in textSpecs:
            #    p.drawText(rect, flags, text)
            #    # p.drawRect(rect)
    
            #see https://github.com/pyqtgraph/pyqtgraph/issues/322
            for rect, flags, text in textSpecs:
                p.save()  # save the painter state
                p.translate(rect.center())   # move coordinate system to center of text rect
                p.rotate(self.labelAngle)  # rotate text
                p.translate(-rect.center())  # revert coordinate system
                p.drawText(rect, flags, text)
                p.restore()  # restore the painter state
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    class _SensorPoints(pg.PlotDataItem):
    
        def __init__(self, *args, **kwds):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            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()
    
            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.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
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    class PlotSettingsModel2DWidgetDelegate(QStyledItemDelegate):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        """
        def __init__(self, tableView, timeSeries, temporalProfileListModel, parent=None):
    
            super(PlotSettingsModel2DWidgetDelegate, self).__init__(parent=parent)
    
            self._preferedSize = QgsFieldExpressionWidget().sizeHint()
    
            self.mTableView = tableView
            self.mTimeSeries = timeSeries
            self.mTemporalProfileListModel = temporalProfileListModel
            self.mSensorLayers = {}
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def setItemDelegates(self, tableView):
            assert isinstance(tableView, QTableView)
            model = tableView.model()
    
    
            assert isinstance(model, PlotSettingsModel2D)
    
            for c in [model.cnSensor, model.cnExpression, model.cnStyle, model.cnTemporalProfile]:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                i = model.columNames.index(c)
                tableView.setItemDelegateForColumn(i, self)
    
    
        def getColumnName(self, index):
            assert index.isValid()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            model = index.model()
    
            assert isinstance(model, PlotSettingsModel2D)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return model.columNames[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):
            assert 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', False)
                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.mTableView.model()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            w = None
    
            if index.isValid() and isinstance(model, PlotSettingsModel2D):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                plotStyle = model.idx2plotStyle(index)
    
                if isinstance(plotStyle, TemporalProfile2DPlotStyle):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    if cname == model.cnExpression:
                        w = QgsFieldExpressionWidget(parent=parent)
    
                        w.setExpression(plotStyle.expression())
                        w.setLayer(self.exampleLyr(plotStyle.sensor()))
                        plotStyle.sigSensorChanged.connect(lambda s : w.setLayer(self.exampleLyr(s)))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        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()))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
                    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()))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
                    elif cname == model.cnSensor:
                        w = QComboBox(parent=parent)
    
                        m = SensorListModel(self.mTimeSeries)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        w.setModel(m)
    
                    elif cname == model.cnTemporalProfile:
                        w = QComboBox(parent=parent)
    
                        w.setModel(self.mTemporalProfileListModel)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    else:
                        raise NotImplementedError()
    
        def checkData(self, index, w, expression):
            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 expression == w.expression()
                    assert w.isExpressionValid(expression) == 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 setEditorData(self, editor, index):
            cname = self.getColumnName(index)
    
            model = self.mTableView.model()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            w = None
    
            if index.isValid() and isinstance(model, PlotSettingsModel2D):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
                cname = self.getColumnName(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:
                    style = index.data()
                    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, QComboBox)
                    m = editor.model()
                    assert isinstance(m, TemporalProfileCollectionListModel)
                    TP = index.data(role=Qt.UserRole)
                    if isinstance(TP, TemporalProfile):
                        idx = m.tp2idx(TP)
                        editor.setCurrentIndex(idx)
                else:
                    raise NotImplementedError()
    
    
        def setModelData(self, w, model, index):
            cname = self.getColumnName(index)
    
            model = self.mTableView.model()
    
            if index.isValid() and isinstance(model, PlotSettingsModel2D):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if cname == model.cnExpression:
                    assert isinstance(w, QgsFieldExpressionWidget)
                    expr = w.asExpression()
                    exprLast = model.data(index, Qt.DisplayRole)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    if w.isValidExpression() and expr != exprLast:
                        model.setData(index, w.asExpression(), Qt.EditRole)
    
                elif cname == model.cnStyle:
                    assert isinstance(w, PlotStyleButton)
    
                    model.setData(index, w.plotStyle(), Qt.EditRole)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
                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)
    
                elif cname == model.cnTemporalProfile:
                    assert isinstance(w, QComboBox)
                    TP = w.itemData(w.currentIndex(), role=Qt.UserRole)
                    assert isinstance(TP, TemporalProfile)
                    model.setData(index, TP, Qt.EditRole)
    
                else:
                    raise NotImplementedError()
    
    
    class PlotSettingsModel3DWidgetDelegate(QStyledItemDelegate):
        """
    
        """
        def __init__(self, tableView, parent=None):
    
            super(PlotSettingsModel3DWidgetDelegate, self).__init__(parent=parent)
            self._preferedSize = QgsFieldExpressionWidget().sizeHint()
            self.mTableView = tableView
    
    
    
        def setItemDelegates(self, tableView):
            assert isinstance(tableView, QTableView)
            model = tableView.model()
    
            assert isinstance(model, PlotSettingsModel3D)
            for c in [model.cnStyle]:
                i = model.columNames.index(c)
                tableView.setItemDelegateForColumn(i, self)
    
        def getColumnName(self, index):
            assert index.isValid()
            model = index.model()
            assert isinstance(model, PlotSettingsModel3D)
            return model.columNames[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.cnStyle:
                        w = QgsColorButton(parent=parent)
                        w.setColor(plotStyle.color())
                        w.setToolTip('Set line color')
                        w.colorChanged.connect(lambda: self.checkData(index, w))
    
            return w
    
        def checkData(self, index, w):
            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, QgsColorButton):
                    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):
                style = model.idx2plotStyle(index)
                assert isinstance(style, TemporalProfile3DPlotStyle)
                cname = self.getColumnName(index)
                if cname == model.cnStyle:
                    assert isinstance(editor, QgsColorButton)
                    editor.setColor(style.color())
                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.cnStyle:
                    assert isinstance(w, QgsColorButton)
                    model.setData(index, w.color(), Qt.EditRole)
                else:
                    raise NotImplementedError()
    
    
    
    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 DateTimeViewBox(pg.ViewBox):
        """
        Subclass of ViewBox
        """
        sigMoveToDate = pyqtSignal(np.datetime64)
        def __init__(self, parent=None):
            """
            Constructor of the CustomViewBox
            """
            super(DateTimeViewBox, self).__init__(parent)
            #self.menu = None # Override pyqtgraph ViewBoxMenu
            #self.menu = self.getMenu() # Create the menu
            #self.menu = None
    
    
        def raiseContextMenu(self, ev):
    
            pt = self.mapDeviceToView(ev.pos())
    
            print(pt.x(), pt.y())
            date = num2date(pt.x())
            menu = QMenu(None)
            a = menu.addAction('Move to {}'.format(date))
            a.setData(date)
            a.triggered.connect(lambda : self.sigMoveToDate.emit(date))
            self.scene().addParentContextMenus(self, menu, ev)
            menu.exec_(ev.screenPos().toPoint())
    
    
    
    
    class DateTimePlotWidget(pg.PlotWidget):
        """
        Subclass of PlotWidget
        """
        def __init__(self, parent=None):
            """
            Constructor of the widget
            """
            super(DateTimePlotWidget, self).__init__(parent, viewBox=DateTimeViewBox())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.plotItem = pg.PlotItem(
    
                axisItems={'bottom':DateTimeAxis(orientation='bottom')},
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                viewBox=DateTimeViewBox()
            )
    
            self.setCentralItem(self.plotItem)
    
            self.xAxisInitialized = False
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    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.cnScale = 'Scale'
            self.cnOffset = 'Offset'
    
    
            self.columNames = [self.cnSensor, self.cnScale, self.cnOffset, self.cnStyle]
    
            self.mPlotSettings = []
            #assert isinstance(plotWidget, DateTimePlotWidget)
    
            self.sortColumnIndex = 0
            self.sortOrder = Qt.AscendingOrder
    
    
            self.sort(0, Qt.AscendingOrder)
    
        def connectTimeSeries(self, timeSeries):
            if isinstance(timeSeries, TimeSeries):
    
                self.mTimeSeries = timeSeries
                self.mTimeSeries.sigSensorAdded.connect(self.createStyle)
                self.mTimeSeries.sigSensorRemoved.connect(self.onSensorRemoved)
                for sensor in self.mTimeSeries.sensors():
                    self.onSensorAdded(sensor)
    
    
        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 createStyle(self, sensor):
            if not self.hasStyleForSensor(sensor):
    
                s = TemporalProfile3DPlotStyle(sensor)
    
                #use another color for the new sensor
                if len(self) > 0:
                    color = self[-1].color()
                    s.setColor(nextColor(color))
    
                self.insertPlotStyles([s])
    
        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.columNames.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.columNames)
    
        def data(self, index, role = Qt.DisplayRole):
            if role is None or not index.isValid():
                return None
    
            value = None
            columnName = self.columNames[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.cnScale:
                        value = plotStyle.mScale
                    elif columnName == self.cnOffset:
                        value = plotStyle.mOffset
                if role == Qt.EditRole:
                    if columnName == self.cnScale:
                        value = plotStyle.mScale
                    elif columnName == self.cnOffset:
                        value = plotStyle.mOffset
    
                elif role == Qt.CheckStateRole:
                    if columnName == self.cnSensor:
                        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.columNames[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.cnScale and isinstance(value, float):
                        plotStyle.setScaling(value, plotStyle.mOffset)
                        result = True
                    elif columnName == self.cnOffset and isinstance(value, float):
                        plotStyle.setScaling(plotStyle.mScale, value)
                        result = True
                    elif columnName == self.cnStyle:
                        if isinstance(value, PlotStyle):
                            plotStyle.copyFrom(value)
                            result = True
                        elif isinstance(value, QColor):
                            plotStyle.setColor(value)
                            result = True
    
                if role == Qt.CheckStateRole:
                    if columnName == self.cnSensor:
                        plotStyle.setVisibility(value == Qt.Checked)
                        result = True
    
                if role == Qt.EditRole:
                    if columnName == self.cnScale:
                        plotStyle.setScaling(value, plotStyle.mOffset)
                        result = True
                    elif columnName == self.cnOffset:
                        plotStyle.setScaling(plotStyle.mScale, value)
                        result = True
                    elif columnName == self.cnStyle:
                        if isinstance(value, QColor):
                            plotStyle.setColor(value)
                            result = True
                        if isinstance(value, TemporalProfile3DPlotStyle):
                            plotStyle.copyFrom(value)
                            result = True
    
            return result
    
    
        def flags(self, index):
            if index.isValid():
                columnName = self.columNames[index.column()]
                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
                if columnName in [self.cnSensor]:
                    flags = flags | Qt.ItemIsUserCheckable
                if columnName in [self.cnScale, self.cnOffset, 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.columNames[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)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __init__(self, temporalProfileCollection, plotWidget, parent=None, *args):
    
    
            #assert isinstance(tableView, QTableView)
    
    
            super(PlotSettingsModel2D, self).__init__(parent=parent)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(temporalProfileCollection, TemporalProfileCollection)
    
            self.cnID = 'ID'
    
            self.cnExpression = LABEL_DN
    
            self.cnTemporalProfile = 'Coordinate'
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.columNames = [self.cnTemporalProfile, self.cnSensor, self.cnStyle, self.cnExpression]
    
            self.mPlotSettings = []
            #assert isinstance(plotWidget, DateTimePlotWidget)
            self.mPlotWidget = plotWidget
    
            self.sortColumnIndex = 0
            self.sortOrder = Qt.AscendingOrder
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.tpCollection = temporalProfileCollection
    
            #self.tpCollection.sigTemporalProfilesRemoved.connect(lambda removedTPs : self.removePlotStyles([p for p in self.mPlotSettings if p.temporalProfile() in removedTPs]))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(self.tpCollection.TS, TimeSeries)
            #self.tpCollection.TS.sigSensorAdded.connect(self.addPlotItem)
            #self.tpCollection.TS.sigSensorRemoved.connect(self.removeSensor)
    
    
            self.sort(0, Qt.AscendingOrder)
            self.dataChanged.connect(self.signaler)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __len__(self):
            return len(self.mPlotSettings)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __iter__(self):
            return iter(self.mPlotSettings)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __getitem__(self, slice):
            return self.mPlotSettings[slice]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __contains__(self, item):
            return item in self.mPlotSettings
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def testSlot(self, *args):
            print(('TESTSLOT', args))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def columnIndex(self, name):
            return self.columNames.index(name)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def signaler(self, idxUL, idxLR):
            if idxUL.isValid():
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                plotStyle = self.idx2plotStyle(idxUL)
                cname = self.columNames[idxUL.column()]
                if cname in [self.cnSensor,self.cnStyle]:
                    self.sigVisibilityChanged.emit(plotStyle)
                if cname in [self.cnExpression]:
                    self.sigDataChanged.emit(plotStyle)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def requiredBandsIndices(self, sensor):
            """
            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)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for p in [p for p in self.mPlotSettings if p.sensor() == sensor]:
    
                assert isinstance(p, TemporalProfile2DPlotStyle)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                expression = p.expression()
                #remove leading & tailing "
    
                bandKeys = regBandKey.findall(expression)
    
                for bandIndex in [bandKey2bandIndex(key) for key in bandKeys]:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    bandIndices.add(bandIndex)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return bandIndices
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        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):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                plotStyles = [plotStyles]
            assert isinstance(plotStyles, list)
            for plotStyle in plotStyles:
    
                assert isinstance(plotStyle, TemporalProfile2DPlotStyle)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            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)
    
                    self.mPlotSettings.insert(i+j, plotStyle)
                self.endInsertRows()
                self.sigPlotStylesAdded.emit(plotStyles)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def removePlotStyles(self, plotStyles):