Skip to content
Snippets Groups Projects
profilevisualization.py 65.3 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 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
    
    
    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
    
    
    def getTextColorWithContrast(c):
        assert isinstance(c, QColor)
        if c.lightness() < 0.5:
            return QColor('white')
        else:
            return QColor('black')
    
    def bandIndex2bandKey(i):
        assert isinstance(i, int)
        assert i >= 0
        return 'b{}'.format(i + 1)
    
    def bandKey2bandIndex(key):
        match = PlotSettingsModel.regBandKeyExact.search(key)
        assert match
        idx = int(match.group()[1:]) - 1
        return idx
    
    
    
    class DateTimeAxis(pg.AxisItem):
    
            super(DateTimeAxis, self).__init__(*args, **kwds)
    
    
        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) 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)
    
    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 TemporalProfilePlotDataItem(pg.PlotDataItem):
    
        def __init__(self, plotStyle, parent=None):
            assert isinstance(plotStyle, TemporalProfilePlotStyle)
    
    
    
            super(TemporalProfilePlotDataItem, self).__init__([1,2,3], [2,3,4], parent=parent)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mPlotStyle = plotStyle
            self.mPlotStyle.sigUpdated.connect(self.updateStyle)
            self.setClickable(True)
            self.updateData()
            self.updateStyle()
    
        def updateData(self):
            TP = self.mPlotStyle.temporalProfile()
            sensor = self.mPlotStyle.sensor()
            if isinstance(TP, TemporalProfile) and isinstance(sensor, SensorInstrument):
                x, y = TP.dataFromExpression(self.mPlotStyle.sensor(), self.mPlotStyle.expression())
                if len(y) > 0:
                    self.setData(x=x, y=y)
                else:
    
                    self.setData(x=[1,2,3], y=[1,2,3]) #dummy
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                self.update()
    
        def updateStyle(self):
            """
            Updates visibility properties
            """
            self.setVisible(self.mPlotStyle.isVisible())
            self.setPen(self.mPlotStyle.linePen)
    
            #self.setBrush(self.mPlotStyle.markerBrush)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            #self.setFillBrush(self.mPlotStyle.)
    
        def setClickable(self, b, width=None):
            assert isinstance(b, bool)
            self.curve.setClickable(b, width=width)
    
        def setColor(self, color):
            if not isinstance(color, QColor):
    
                color = QColor(color)
            self.setPen(color)
    
        def pen(self):
    
            return fn.mkPen(self.opts['pen'])
    
        def color(self):
            return self.pen().color()
    
            # 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
    
        def setLineWidth(self, width):
            pen = pg.mkPen(self.opts['pen'])
            assert isinstance(pen, QPen)
            pen.setWidth(width)
            self.setPen(pen)
    
    
    
    
    class PlotSettingsWidgetDelegate(QStyledItemDelegate):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        """
        def __init__(self, tableView, timeSeries, temporalProfileListModel, parent=None):
    
    
            super(PlotSettingsWidgetDelegate, self).__init__(parent=parent)
            self._preferedSize = QgsFieldExpressionWidget().sizeHint()
            self.tableView = tableView
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.timeSeries = timeSeries
            self.temporalProfileListModel = temporalProfileListModel
    
        def setItemDelegates(self, tableView):
            assert isinstance(tableView, QTableView)
            model = tableView.model()
    
            assert isinstance(model, PlotSettingsModel)
            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()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            model = index.model()
            assert isinstance(model, PlotSettingsModel)
            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)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            model = self.tableView.model()
            w = None
            if index.isValid() and isinstance(model, PlotSettingsModel):
                plotStyle = model.idx2plotStyle(index)
                if isinstance(plotStyle, TemporalProfilePlotStyle):
                    if cname == model.cnExpression:
                        w = QgsFieldExpressionWidget(parent=parent)
    
                        #todo: w.setLayer(sv.memLyr)
                        w.setExpressionDialogTitle('Values')
                        w.setToolTip('Set an expression to calculate the plot y-values.')
                        w.fieldChanged.connect(lambda : self.checkData(w, w.expression()))
    
                    elif cname == model.cnStyle:
                        w = PlotStyleButton(parent=parent)
                        w.setPlotStyle(plotStyle)
                        w.setToolTip('Set style.')
                        w.sigPlotStyleChanged.connect(lambda: self.checkData(w, w.plotStyle()))
    
                    elif cname == model.cnSensor:
                        w = QComboBox(parent=parent)
                        m = SensorListModel(self.timeSeries)
                        w.setModel(m)
    
                    elif cname == model.cnTemporalProfile:
                        w = QComboBox(parent=parent)
                        w.setModel(self.temporalProfileListModel)
                    else:
                        raise NotImplementedError()
    
        def checkData(self, w, expression):
            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)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            model = self.tableView.model()
    
            w = None
            if index.isValid() and isinstance(model, PlotSettingsModel):
    
                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)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            model = self.tableView.model()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if index.isValid() and isinstance(model, PlotSettingsModel):
                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.UserRole)
    
                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 TemporalProfileCollectionListModel(QAbstractListModel):
    
    
        def __init__(self, temporalProfileCollection, *args, **kwds):
    
            super(TemporalProfileCollectionListModel, self).__init__(*args, **kwds)
            assert isinstance(temporalProfileCollection, TemporalProfileCollection)
    
            self.mTPColl = temporalProfileCollection
            self.mTPColl.rowsAboutToBeInserted.connect(self.rowsAboutToBeInserted)
            self.mTPColl.rowsInserted.connect(self.rowsInserted.emit)
            self.mTPColl.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved)
            self.mTPColl.rowsRemoved.connect(self.rowsRemoved.emit)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def idx2tp(self, *args, **kwds):
            return self.mTPColl.idx2tp(*args, **kwds)
    
        def tp2idx(self, *args, **kwds):
            return self.mTPColl.tp2idx(*args, **kwds)
    
        def flags(self, index):
            if index.isValid():
                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
                return flags
                #return item.qt_flags(index.column())
            return Qt.NoItemFlags
    
        def rowCount(self, *args, **kwds):
            return self.mTPColl.rowCount(*args, **kwds)
    
    
        def data(self, index, role=Qt.DisplayRole):
            if role is None or not index.isValid():
                return None
    
            TP = self.mTPColl.data(index, role=Qt.UserRole)
            value = None
            if isinstance(TP, TemporalProfile):
                if role == Qt.DisplayRole:
                    value = '{}'.format(TP.name())
                elif role == Qt.ToolTipRole:
                    value = '#{} "{}" {}'.format(TP.mID, TP.name(), TP.mCoordinate)
                elif role == Qt.UserRole:
                    value = TP
    
            return value
    
    class TemporalProfileCollection(QAbstractTableModel):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        A collection to store the TemporalProfile data delivered by a PixelLoader
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        #sigSensorAdded = pyqtSignal(SensorInstrument)
        #sigSensorRemoved = pyqtSignal(SensorInstrument)
    
        #sigPixelAdded = pyqtSignal()
        #sigPixelRemoved = pyqtSignal()
    
        def __init__(self, ):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            super(TemporalProfileCollection, self).__init__()
            #self.sensorPxLayers = dict()
            #self.memLyrCrs = QgsCoordinateReferenceSystem('EPSG:4326')
    
            self.newDataFlag = False
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mcnID = 'id'
            self.mcnCoordinate = 'Coordinate'
            self.mcnLoaded = 'Loading'
            self.mcnName = 'Name'
            self.mColumNames = [self.mcnName, self.mcnLoaded, self.mcnCoordinate]
    
    
            crs = QgsCoordinateReferenceSystem('EPSG:4862')
            uri = 'Point?crs={}'.format(crs.authid())
    
            self.TS = None
            self.mLocations = QgsVectorLayer(uri, 'LOCATIONS', 'memory', False)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mTemporalProfiles = []
            self.mTPLookupSpatialPoint = {}
            self.mTPLookupID = {}
    
            self.mCurrentTPID = 0
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mMaxProfiles = 10
    
            self.nextID = 0
    
        def __len__(self):
            return len(self.mTemporalProfiles)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __iter__(self):
            return iter(self.mTemporalProfiles)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __getitem__(self, slice):
            return self.mTemporalProfiles[slice]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __contains__(self, item):
            return item in self.mTemporalProfiles
    
        def rowCount(self, parent=None, *args, **kwargs):
            return len(self.mTemporalProfiles)
    
        def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
            return len(self.mColumNames)
    
        def idx2tp(self, index):
            if index.isValid():
                return self.mTemporalProfiles[index.row()]
            return None
    
        def tp2idx(self, temporalProfile):
            assert isinstance(temporalProfile, TemporalProfile)
            idx = self.createIndex(None, -1, 0)
            if temporalProfile in self.mTemporalProfiles:
                idx.setRow(self.mTemporalProfiles.index(temporalProfile))
            return idx
    
        def data(self, index, role = Qt.DisplayRole):
            if role is None or not index.isValid():
                return None
    
            value = None
            columnName = self.mColumNames[index.column()]
            TP = self.idx2tp(index)
            if not isinstance(TP, TemporalProfile):
                return None
            #self.mColumNames = ['id','coordinate','loaded']
            if role == Qt.DisplayRole:
                if columnName == self.mcnID:
                    value = TP.mID
                elif columnName == self.mcnName:
                    value = TP.name()
                elif columnName == self.mcnCoordinate:
                    value = '{}'.format(TP.mCoordinate)
                elif columnName == self.mcnLoaded:
                    nIs, nMax = TP.loadingStatus()
                    if nMax > 0:
                        value = '{}/{} ({:0.2f} %)'.format(nIs, nMax, float(nIs) / nMax * 100)
            elif role == Qt.EditRole:
                if columnName == self.mcnName:
                    value = TP.name()
            elif role == Qt.ToolTipRole:
                if columnName == self.mcnID:
                    value = 'ID Temporal Profile'
                elif columnName == self.mcnName:
                    value = TP.name()
                elif columnName == self.mcnCoordinate:
                    value = '{}'.format(TP.mCoordinate)
                elif columnName == self.mcnLoaded:
                    nIs, nMax = TP.loadingStatus()
                    value = '{}'.format(TP.mCoordinate)
            elif role == Qt.UserRole:
                value = TP
    
            return value
    
        def flags(self, index):
            if index.isValid():
                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
    
                cName = self.mColumNames[index.column()]
                if cName == self.mcnName:
                    flags = flags | Qt.ItemIsEditable
    
                return flags
                #return item.qt_flags(index.column())
            return None
    
    
        def setData(self, index, value, role=None):
            if role is None or not index.isValid():
                return None
    
            cName = self.mColumNames[index.column()]
            TP = self.idx2tp(index)
            if isinstance(TP, TemporalProfile):
                if role == Qt.EditRole and cName == self.mcnName:
                    if len(value) == 0: #do not accept empty strings
                        return False
                    else:
                        TP.setName(value)
                    return True
    
            return False
    
        def headerData(self, col, orientation, role):
            if Qt is None:
                return None
            if role == Qt.DisplayRole:
                if orientation == Qt.Horizontal:
                    return self.mColumNames[col]
                elif orientation == Qt.Vertical:
                    return col
            return None
    
        def insertTemporalProfiles(self, temporalProfiles, i=None):
            if isinstance(temporalProfiles, TemporalProfile):
                temporalProfiles = [temporalProfiles]
    
            assert isinstance(temporalProfiles, list)
            for temporalProfile in temporalProfiles:
                assert isinstance(temporalProfile, TemporalProfile)
    
            if i is None:
                i = len(self.mTemporalProfiles)
    
            self.beginInsertRows(QModelIndex(), i, i + len(temporalProfiles) - 1)
            for temporalProfile in temporalProfiles:
                assert isinstance(temporalProfile, TemporalProfile)
                id = self.nextID
                self.nextID += 1
                temporalProfile.mID = id
                self.mTemporalProfiles.insert(i, temporalProfile)
                self.mTPLookupID[id] = temporalProfile
                self.mTPLookupSpatialPoint[temporalProfile.mCoordinate] = temporalProfile
                i += 1
            self.endInsertRows()
    
        def temporalProfileFromGeometry(self, geometry):
            if geometry in self.mTPLookupSpatialPoint.keys():
                return self.mTPLookupSpatialPoint[geometry]
            else:
                return None
    
        def temporalProfileFromID(self, id):
            if id in self.mTPLookupID.keys():
                return self.mTPLookupID[id]
            else:
                return None
    
        def id(self, temporalProfile):
            """
            Returns the id of an TemporalProfile
            :param temporalProfile: TemporalProfile
            :return: id or None, inf temporalProfile is not part of this collections
            """
    
            for k, tp in self.mTPLookupID.items():
                if tp == temporalProfile:
                    return k
            return None
    
        def fromID(self, id):
            if self.mTPLookupID.has_key(id):
                return self.mTPLookupID[id]
            else:
                return None
    
        def fromSpatialPoint(self, spatialPoint):
            if self.mTPLookupSpatialPoint.has_key(spatialPoint):
                return self.mTPLookupSpatialPoint[spatialPoint]
            else:
                return None
    
        def removeTemporalProfiles(self, temporalProfiles):
            """
            Removes temporal profiles from this collection
            :param temporalProfile: TemporalProfile
            """
    
            if isinstance(temporalProfiles, TemporalProfile):
                temporalProfiles = [temporalProfiles]
            assert isinstance(temporalProfiles, list)
    
            for temporalProfile in temporalProfiles:
                assert isinstance(p, TemporalProfile)
                if temporalProfile in self.mTemporalProfiles:
    
                    idx = self.tp2idx(temporalProfile)
                    row = idx.row()
                    self.beginRemoveRows(QModelIndex(), row, row)
                    self.mTemporalProfiles.remove(temporalProfile)
                    self.mTPLookupSpatialPoint.__delitem__(temporalProfile)
                    self.mTPLookupID.__delitem__(temporalProfile)
                    self.endRemoveRows()
    
        def connectTimeSeries(self, timeSeries):
    
    
            if isinstance(timeSeries, TimeSeries):
                self.TS = timeSeries
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                #for sensor in self.TS.Sensors:
                #    self.addSensor(sensor)
                #self.TS.sigSensorAdded.connect(self.addSensor)
                #self.TS.sigSensorRemoved.connect(self.removeSensor)
    
            else:
                self.TS = None
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setMaxProfiles(self, n):
            """
            Sets the maximum number of temporal profiles to be stored in this container.
            :param n: number of profiles, must be >= 1
            """
            assert n >= 1
            self.mMaxProfiles = n
    
            self.prune()
    
        def prune(self):
            """
            Reduces the number of temporal profile to the value n defined with .setMaxProfiles(n)
            :return: [list-of-removed-TemporalProfiles]
            """
    
        def getFieldDefn(self, name, values):
            if isinstance(values, np.ndarray):
                # add bands
                if values.dtype in [np.int8, np.int16, np.int32, np.int64,
                                    np.uint8, np.uint16, np.uint32, np.uint64]:
                    fType = QVariant.Int
                    fTypeName = 'integer'
                elif values.dtype in [np.float16, np.float32, np.float64]:
                    fType = QVariant.Double
                    fTypeName = 'decimal'
            else:
                raise NotImplementedError()
    
            return QgsField(name, fType, fTypeName)
    
        def setFeatureAttribute(self, feature, name, value):
            assert isinstance(feature, QgsFeature)
            assert isinstance(name, str)
            i = feature.fieldNameIndex(name)
    
            assert i >= 0, 'Field "{}" does not exist'.format(name)
    
            field = feature.fields()[i]
            if field.isNumeric():
                if field.type() == QVariant.Int:
                    value = int(value)
                elif field.type() == QVariant.Double:
                    value = float(value)
                else:
                    raise NotImplementedError()
            feature.setAttribute(i, value)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def sort(self, col, order):
            if self.rowCount() == 0:
                return
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.layoutAboutToBeChanged.emit()
            colName = self.mColumNames[col]
            r = order != Qt.AscendingOrder
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if colName == self.mcnName:
                self.items.sort(key = lambda TP:TP.name(), reverse=r)
            elif colName == self.mcnCoordinate:
                self.items.sort(key=lambda TP: str(TP.mCoordinate), reverse=r)
            elif colName == self.mcnID:
                self.items.sort(key=lambda TP: TP.mID, reverse=r)
            elif colName == self.mcnLoaded:
                self.items.sort(key=lambda TP: TP.loadingStatus(), reverse=r)
            self.layoutChanged.emit()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def addPixelLoaderResult(self, d):
            assert isinstance(d, PixelLoaderTask)
            if d.success():
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                for i, TPid in enumerate(d.temporalProfileIDs):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    TP = self.temporalProfileFromID(TPid)
                    tsd = self.TS.getTSD(d.sourcePath)
                    assert isinstance(tsd, TimeSeriesDatum)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    if isinstance(TP, TemporalProfile):
                        profileData = d.resProfiles[i]
    
                        if not isinstance(profileData, tuple):
                            s = ""
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        vMean, vStd = profileData
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        values = {}
    
                        validValues = not isinstance(vMean, str)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        #1. add the pixel values per returned band
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        for iBand, bandIndex in enumerate(d.bandIndices):
                            key = 'b{}'.format(bandIndex + 1)
    
                            values[key] = vMean[iBand] if validValues else None
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                            key = 'std{}'.format(bandIndex + 1)
    
                            values[key] = vStd[iBand] if validValues else None
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        #indicesY, indicesX = d.imagePixelIndices()
                        #values['px_x'] = indicesX
                        #values['px_y'] = indicesY
                        TP.updateData(tsd, values)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #todo: remove TS Profiles
            #self.mTemporalProfiles.clear()
            #self.sensorPxLayers.clear()
            pass
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    class TemporalProfilePlotStyle(PlotStyle):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        sigUpdated = pyqtSignal()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __init__(self, temporalProfile):
            super(TemporalProfilePlotStyle, self).__init__()
            assert isinstance(temporalProfile, TemporalProfile)
            self.mSensor = None
            self.mTP = temporalProfile
            self.mExpression = u'"b1"'
            self.mPlotItems = []
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if isinstance(temporalProfile, TemporalProfile):
                self.setTemporalProfile(temporalProfile)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def createPlotItem(self, plotWidget):
            pdi = TemporalProfilePlotDataItem(self)
            self.mPlotItems.append(pdi)
            return pdi
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def temporalProfile(self):
            return self.mTP
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setTemporalProfile(self, temporalPofile):
            assert isinstance(temporalPofile, TemporalProfile)
            b = temporalPofile != self.mTP
            self.mTP = temporalPofile
            if b: self.sigUpdated.emit()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setSensor(self, sensor):
    
            assert isinstance(sensor, SensorInstrument)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            b = sensor != self.mSensor
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if b: self.sigUpdated.emit()
    
    
        def sensor(self):
            return self.mSensor
    
    
        def setExpression(self, exp):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(exp, unicode)
            b = self.mExpression != exp
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if b: self.sigUpdated.emit()
    
    
        def expression(self):
            return self.mExpression
    
    
        def __reduce_ex__(self, protocol):
            return self.__class__, (), self.__getstate__()
    
        def __getstate__(self):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            result = super(TemporalProfilePlotStyle, self).__getstate__()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            del result['mTP']
    
    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)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    class PlotSettingsModel(QAbstractTableModel):
    
    
        #sigSensorAdded = pyqtSignal(SensorPlotSettings)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        sigVisibilityChanged = pyqtSignal(TemporalProfilePlotStyle)
        sigDataChanged = pyqtSignal(TemporalProfilePlotStyle)
    
        regBandKey = re.compile("(?<!\w)b\d+(?!\w)", re.IGNORECASE)
        regBandKeyExact = re.compile('^' + regBandKey.pattern + '$', re.IGNORECASE)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __init__(self, temporalProfileCollection, plotWidget, parent=None, *args):
    
    
            #assert isinstance(tableView, QTableView)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            super(PlotSettingsModel, self).__init__(parent=parent)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(temporalProfileCollection, TemporalProfileCollection)
    
            self.cnID = 'ID'
            self.cnSensor = 'sensor'
            self.cnExpression = 'y-value'
            self.cnStyle = 'style'
            self.cnTemporalProfile = 'px'
            self.columNames = [self.cnTemporalProfile, self.cnSensor, self.cnStyle, self.cnExpression]
    
            self.mPlotSettings = []
            self.mPlotDataItems = []
            #assert isinstance(plotWidget, DateTimePlotWidget)
            self.mPlotWidget = plotWidget
    
            self.sortColumnIndex = 0
            self.sortOrder = Qt.AscendingOrder