Skip to content
Snippets Groups Projects
profilevisualization.py 84.8 KiB
Newer Older
# -*- 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
import os, sys, pickle, datetime
from collections import OrderedDict
from qgis.gui import *
from qgis.core import *
from qgis.core import QgsExpression
from PyQt5.QtCore import *
from PyQt5.QtXml import *
from PyQt5.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

OPENGL_AVAILABLE = False

try:
    import OpenGL
    OPENGL_AVAILABLE = True
    from pyqtgraph.opengl import *
    from OpenGL.GL import *

    class AxisGrid3D(GLGridItem):
        def __init__(self, *args, **kwds):
            super(AxisGrid3D, self).__init__(*args, **kwds)

            self.mXYRange = np.asarray([[0, 1], [0, 1]])
            self.mColor = QColor('grey')

        def setColor(self, color):
            self.mColor = QColor(color)

        def setXRange(self, x0, x1):
            self.mXYRange[0, 0] = x0
            self.mXYRange[0, 1] = x1

        def setYRange(self, y0, y1):
            self.mXYRange[1, 0] = y0
            self.mXYRange[1, 1] = y1

        def paint(self):
            self.setupGLState()

            if self.antialias:
                glEnable(GL_LINE_SMOOTH)
                glEnable(GL_BLEND)
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
                glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)

            glBegin(GL_LINES)

            x0, x1 = self.mXYRange[0, :]
            y0, y1 = self.mXYRange[1, :]
            xs, ys, zs = self.spacing()
            rx = x1-x0
            ry = y1-y0
            xvals = np.arange(x0, x1, rx/10)
            yvals = np.arange(y0, y1, ry/10)
            #todo: nice line breaks
            #yvals = np.arange(-y / 2., y / 2. + ys * 0.001, ys)
            c = fn.glColor(self.mColor)
            glColor4f(*c)
            for x in xvals:
                glVertex3f(x, yvals[0], 0)
                glVertex3f(x, yvals[-1], 0)
            for y in yvals:
                glVertex3f(xvals[0], y, 0)
                glVertex3f(xvals[-1], y, 0)

            glEnd()

    class Axis3D(GLAxisItem):

        def __init__(self, *args, **kwds):
            super(Axis3D, self).__init__(*args, **kwds)

            self.mRanges = np.asarray([[0,1],[0,1],[0,1]])
            self.mColors = [QColor('white'),QColor('white'),QColor('white')]
            self.mVisibility = [True, True, True]
            self.mLabels = ['X','Y','Z']

        def rangeMinima(self):
            return self.mRanges[:,0]

        def rangeMaxima(self):
            return self.mRanges[:,1]

        def _set(self, ax, vMin=None, vMax=None, label=None, color=None, visible=None):
            i = ['x','y','z'].index(ax.lower())
            if vMin is not None:
                self.mRanges[i][0] = vMin
            if vMax is not None:
                self.mRanges[i][1] = vMax
            if color is not None:
                self.mColors[i] = color
            if label is not None:
                self.mLabels[i] = label
            if visible is not None:
                self.mVisibility[i] = visible


        def setX(self, **kwds):
            self._set('x', **kwds)

        def setY(self, **kwds):
            self._set('y', **kwds)

        def setZ(self, **kwds):
            self._set('z', **kwds)

        def paint(self):
            # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
            # glEnable( GL_BLEND )
            # glEnable( GL_ALPHA_TEST )
            self.setupGLState()

            if self.antialias:
                glEnable(GL_LINE_SMOOTH)
                glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)

            x0, y0, z0 = self.rangeMinima()
            x1, y1, z1 = self.rangeMaxima()
            glLineWidth(2.0)
            glBegin(GL_LINES)
            glColor4f(*fn.glColor(self.mColors[2]))

            glVertex3f(0, 0, z0)
            glVertex3f(0, 0, z1)

            glColor4f(*fn.glColor(self.mColors[1]))
            glVertex3f(0, y0, 0)
            glVertex3f(0, y1, 0)

            glColor4f(*fn.glColor(self.mColors[0]))
            glVertex3f(x0, 0, 0)
            glVertex3f(x1, 0, 0)

            glEnd()
            glLineWidth(1.0)

    class ViewWidget3D(GLViewWidget):

        def __init__(self, parent=None):
            super(ViewWidget3D, self).__init__(parent)
            self.mousePos = QPoint(-1,-1)
            self.setBackgroundColor(QColor('black'))
            self.setMouseTracking(True)


            self.mDataMinRanges = [0, 0, 0]
            self.mDataMaxRanges = [1, 1, 1]
            self.mDataN = 0
            self.glAxes = Axis3D()
            self.glGridItemXY = AxisGrid3D()
            self.glGridItemXZ = AxisGrid3D()
            self.glGridItemYZ = AxisGrid3D()
            self.glGridItemXZ.setVisible(False)
            self.glGridItemYZ.setVisible(False)
            x, y, z = self.glAxes.size()
            self.glGridItemYZ.rotate(-90, 0, 1, 0)
            self.glGridItemXZ.rotate(90, 1, 0, 0)

            #self.glGridItemXY.scale(x/10,y/10, 1)
            #self.glGridItemXZ.scale(x/10,z/10, 1)
            #self.glGridItemYZ.scale(y/10,z/10, 1)

            self.mBasicItems = [self.glGridItemXY, self.glGridItemXZ, self.glGridItemYZ, self.glAxes]
            for item in self.mBasicItems:
                item.setDepthValue(-10)

                self.addItem(item) # draw grid/axis after surfaces since they may be translucent

            self.initContextMenu()
        """
        def setDataRangeX(self, x0, x1):
            assert x0 < x1
            self.mDataMinRanges[0] = x0
            self.mDataMaxRanges[0] = x1

        def setDataRangeY(self, y0, y1):
            assert y0 < y1
            self.mDataMinRanges[0] = y0
            self.mDataMaxRanges[0] = y1

        def setDataRangeZ(self, z0, z1):
            assert z0 < z1
            self.mDataMinRanges[0] = z0
            self.mDataMaxRanges[0] = z1
        """

        def initContextMenu(self):

            menu = QMenu()

            #define grid options
            m = menu.addMenu('Grids')


            def visibilityAll(b):
                self.glGridItemXY.setVisible(b)
                self.glGridItemXZ.setVisible(b)
                self.glGridItemYZ.setVisible(b)

            a = m.addAction('Show All')
            a.setCheckable(False)
            a.triggered.connect(lambda : visibilityAll(True))

            a = m.addAction('Hide All')
            a.setCheckable(False)
            a.triggered.connect(lambda: visibilityAll(False))
            m.addSeparator()

            a = m.addAction('XY')
            a.setCheckable(True)
            a.setChecked(self.glGridItemXY.visible())
            a.toggled.connect(self.glGridItemXY.setVisible)


            a = m.addAction('XZ')
            a.setCheckable(True)
            a.setChecked(self.glGridItemXZ.visible())
            a.toggled.connect(self.glGridItemXZ.setVisible)

            a = m.addAction('YZ')
            a.setCheckable(True)
            a.setChecked(self.glGridItemYZ.visible())
            a.toggled.connect(self.glGridItemYZ.setVisible)

            m = menu.addMenu('Axes')

            frame = QFrame()
            l = QHBoxLayout()
            frame.setLayout(l)
            l.addWidget(QLabel('Color'))
            wa = QWidgetAction(menu)
            wa.setDefaultWidget(frame)
            menu.addAction(wa)
            menu.insertMenu(wa, menu)

            self.btnAxisColor  = QgsColorButton()

            a = m.addAction('X')
            a.setCheckable(True)
            a.setChecked(self.glAxes.mVisibility[0])
            a.toggled.connect(lambda b: self.glAxes.setX(visible=b))

            a = m.addAction('Y')
            a.setCheckable(True)
            a.setChecked(self.glAxes.mVisibility[1])
            a.toggled.connect(lambda b: self.glAxes.setX(visible=b))

            a = m.addAction('Z')
            a.setCheckable(True)
            a.setChecked(self.glAxes.mVisibility[2])
            a.toggled.connect(lambda b: self.glAxes.setX(visible=b))



            self.mMenu = menu

        def resetCamera(self):

           # self.mDataMinRanges
            self.setCameraPosition(self.mDataMinRanges, distance=5, elevation=10)

        def clearItems(self):
            for item in self.items:
                if item not in self.mBasicItems:
                    self.removeItem(item)
        def paintGL(self, *args, **kwds):
            GLViewWidget.paintGL(self, *args, **kwds)
            self.qglColor(Qt.white)
            self.renderAnnotations()

        def zoomToFull(self):

            for item in self.items:
                if item not in self.mBasicItems:
                    s = ""
            s = ""

        def updateDataRanges(self):
            x0 = x1 = y0 = y1 = z0 = z1 = n = None

            if hasattr(self, 'items'):
                for item in self.items:
                    if item not in self.mBasicItems and hasattr(item, 'pos'):
                        pos = item.pos
                        if x0 is None:
                            n = pos.shape[0]
                            x0 = pos[:, 0].min()
                            y0 = pos[:, 1].min()
                            z0 = pos[:, 2].min()
                            x1 = pos[:, 0].max()
                            y1 = pos[:, 1].max()
                            z1 = pos[:, 2].max()
                        else:
                            n  = max(n, pos.shape[0])
                            x0 = min(x0, pos[:, 0].min())
                            y0 = min(y0, pos[:, 1].min())
                            z0 = min(z0, pos[:, 2].min())
                            x1 = max(x1, pos[:, 0].max())
                            y1 = max(y1, pos[:, 1].max())
                            z1 = max(z1, pos[:, 2].max())
                if x1 is not None:
                    self.mDataMinRanges = (x0, y0, z0)
                    self.mDataMaxRanges = (x1, y1, z1)
                    self.mDataN = n
                    self.glAxes.setSize(x1, y1, z1)
                    self.glGridItemXZ.setSize()


        def mouseMoveEvent(self, ev):
            assert isinstance(ev, QMouseEvent)
            """ Allow Shift to Move and Ctrl to Pan.
            Example taken from https://gist.github.com/blink1073/7406607
            """
            shift = ev.modifiers() & QtCore.Qt.ShiftModifier
            ctrl = ev.modifiers() & QtCore.Qt.ControlModifier
            if shift:
                y = ev.pos().y()
                if not hasattr(self, '_prev_zoom_pos') or not self._prev_zoom_pos:
                    self._prev_zoom_pos = y
                    return
                dy = y - self._prev_zoom_pos

                def delta():
                    return -dy * 5

                ev.delta = delta
                self._prev_zoom_pos = y
                self.wheelEvent(ev)
            elif ctrl:
                pos = ev.pos().x(), ev.pos().y()
                if not hasattr(self, '_prev_pan_pos') or not self._prev_pan_pos:
                    self._prev_pan_pos = pos
                    return
                dx = pos[0] - self._prev_pan_pos[0]
                dy = pos[1] - self._prev_pan_pos[1]
                self.pan(dx, dy, 0, relative=True)
                self._prev_pan_pos = pos
            else:
                super(ViewWidget3D, self).mouseMoveEvent(ev)

            #items = self.itemsAt((pos.x(), pos.y(), 3, 3))


        def mousePressEvent(self, event):
            super(ViewWidget3D, self).mousePressEvent(event)
            self.mousePos = event.pos()
            if event.button() == 2:
                self.select = True
            else:
                self.select = False

            try:
                print(self.itemsAt((self.mousePos.x(), self.mousePos.y(), 3, 3)))
            except:
                pass
        def renderAnnotations(self):

            if self.glAxes.visible():
                x, y, z = self.glAxes.rangeMaxima()
                if x is not None:
                    self.renderText(x, 0, 0, self.glAxes.mLabels[0])
                    self.renderText(0, y, 0, self.glAxes.mLabels[1])
                    self.renderText(0, 0, z, self.glAxes.mLabels[2])
            #self.renderText(0.8, 0.8, 0.8, 'text 3D')
            self.renderText(5, 10, 'text 2D fixed')
        def contextMenuEvent(self, event):
            assert isinstance(event, QContextMenuEvent)
            self.mMenu.exec_(self.mapToGlobal(event.pos()))
    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()

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')
            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):
                if cname == model.cnExpression and isinstance(plotStyle.sensor(), SensorInstrument):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                    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)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                model.setData(index, TP, Qt.EditRole)

            else:
                raise NotImplementedError()

class PlotSettingsModel3DWidgetDelegate(QStyledItemDelegate):
    """

    """
    def __init__(self, tableView, timeSeries, temporalProfileListModel, parent=None):

        super(PlotSettingsModel3DWidgetDelegate, self).__init__(parent=parent)
        self._preferedSize = QgsFieldExpressionWidget().sizeHint()
        self.mTableView = tableView
        self.mTimeSeries = timeSeries
        self.mTemporalProfileListModel = temporalProfileListModel
        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.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.cnExpression:
                    w = QgsFieldExpressionWidget(parent=parent)
                    w.setExpression(plotStyle.expression())
                    sensor = plotStyle.sensor()
                    if isinstance(sensor, SensorInstrument):
                        w.setLayer(self.exampleLyr(sensor))
                    plotStyle.sigSensorChanged.connect(lambda s : w.setLayer(self.exampleLyr(s)))
                    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.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))
                elif cname == model.cnSensor:
                    w = QComboBox(parent=parent)
                    m = SensorListModel(self.mTimeSeries)
                    w.setModel(m)

                elif cname == model.cnTemporalProfile:
                    w = QComboBox(parent=parent)
                    w.setModel(self.mTemporalProfileListModel)
                else:
                    raise NotImplementedError()
    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')
            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):
        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, 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, 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, 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)

            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 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()

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.cnID = 'ID'
        self.cnExpression = LABEL_EXPRESSION_3D
        self.cnTemporalProfile = 'Coordinate'
        self.cnSensor = 'Sensor'
        self.columNames = [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 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()
            #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)