Skip to content
Snippets Groups Projects
profilevisualization.py 84.8 KiB
Newer Older
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
        if b: self.update()


    def update(self):
        super(TemporalProfilePlotStyle, self).update()

        for pdi in self.mPlotItems:
            assert isinstance(pdi, TemporalProfilePlotDataItem)
            pdi.updateStyle()
            #pdi.updateItems()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    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
        if b:
            self.update()
            self.sigExpressionUpdated.emit()
    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)
        self.xAxisInitialized = False
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)
    sigPlotStylesAdded = pyqtSignal(list)
    sigPlotStylesRemoved = pyqtSignal(list)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    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):
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 = LABEL_DN
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.cnStyle = 'style'
        self.cnTemporalProfile = 'px'
        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, TemporalProfilePlotStyle)
            expression = p.expression()
            #remove leading & tailing "
            bandKeys = PlotSettingsModel.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, TemporalProfilePlotStyle):
            plotStyles = [plotStyles]
        assert isinstance(plotStyles, list)
        for plotStyle in plotStyles:
            assert isinstance(plotStyle, TemporalProfilePlotStyle)
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, TemporalProfilePlotStyle)
                self.mPlotSettings.insert(i+j, plotStyle)
            self.endInsertRows()
            self.sigPlotStylesAdded.emit(plotStyles)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def removePlotStyles(self, plotStyles):
        """
        Removes PlotStyle instances
        :param plotStyles: TemporalProfilePlotStyle | [list-of-TemporalProfilePlotStyle]
        """
        if isinstance(plotStyles, PlotStyle):
            plotStyles = [plotStyles]
        assert isinstance(plotStyles, list)

        if len(plotStyles) > 0:
            for plotStyle in plotStyles:
                assert isinstance(plotStyle, PlotStyle)
                if plotStyle in self.mPlotSettings:
                    idx = self.plotStyle2idx(plotStyle)
                    self.beginRemoveRows(QModelIndex(), idx.row(),idx.row())
                    self.mPlotSettings.remove(plotStyle)
                    self.endRemoveRows()
                if isinstance(plotStyle, TemporalProfilePlotStyle):
                    for pi in plotStyle.mPlotItems:
                        self.mPlotWidget.getPlotItem().removeItem(pi)
            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,

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if colName == self.cnSensor:
            self.mPlotSettings.sort(key = lambda sv:sv.sensor().name(), reverse=r)
        elif colName == self.cnExpression:
            self.mPlotSettings.sort(key=lambda sv: sv.expression(), reverse=r)
        elif colName == self.cnStyle:
            self.mPlotSettings.sort(key=lambda sv: sv.color, reverse=r)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def rowCount(self, parent = QModelIndex()):
        return len(self.mPlotSettings)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def removeRows(self, row, count , parent = QModelIndex()):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.beginRemoveRows(parent, row, row + count-1)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        toRemove = self.mPlotSettings[row:row + count]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.mPlotSettings.remove(tsd)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def plotStyle2idx(self, plotStyle):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(plotStyle, TemporalProfilePlotStyle)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        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()):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return len(self.columNames)

    def data(self, index, role = Qt.DisplayRole):
        if role is None or not index.isValid():
            return None

        value = None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        columnName = self.columNames[index.column()]
        plotStyle = self.idx2plotStyle(index)
        if isinstance(plotStyle, TemporalProfilePlotStyle):
            sensor = plotStyle.sensor()
            #print(('data', columnName, role))
            if role == Qt.DisplayRole:
                if columnName == self.cnSensor:
                    if isinstance(sensor, SensorInstrument):
                        value = sensor.name()
                    else:
                        value = '<Select Sensor>'
                elif columnName == self.cnExpression:
                    value = plotStyle.expression()
                elif columnName == self.cnTemporalProfile:
                    value = plotStyle.temporalProfile().name()

            #elif role == Qt.DecorationRole:
            #    if columnName == self.cnStyle:
            #        value = plotStyle.createIcon(QSize(96,96))

            elif role == Qt.CheckStateRole:
                if columnName == self.cnTemporalProfile:
                    value = Qt.Checked if plotStyle.isVisible() else Qt.Unchecked



            elif role == Qt.UserRole:
                value = plotStyle
                if columnName == self.cnSensor:
                    value = plotStyle.sensor()
                elif columnName == self.cnExpression:
                    value = plotStyle.expression()
                elif columnName == self.cnStyle:
                    value = plotStyle
                elif columnName == self.cnTemporalProfile:
                    value == plotStyle.temporalProfile()
                else:
                    value = plotStyle
        #print(('get data',value))
        return value

    def setData(self, index, value, role=None):
        if role is None or not index.isValid():
            return False
        #print(('Set data', index.row(), index.column(), value, role))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        columnName = self.columNames[index.column()]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        result = False
        plotStyle = self.idx2plotStyle(index)
        if isinstance(plotStyle, TemporalProfilePlotStyle):
            if role in [Qt.DisplayRole]:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                if columnName == self.cnExpression:
                    plotStyle.setExpression(value)
                    result = True
                elif columnName == self.cnStyle:
                    if isinstance(value, PlotStyle):
                        plotStyle.copyFrom(value)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                        result = True

            if role == Qt.CheckStateRole:
                if columnName == self.cnTemporalProfile:
                    plotStyle.setVisibility(value == Qt.Checked)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if role == Qt.EditRole:
                if columnName == self.cnExpression:
                    plotStyle.setExpression(value)
                    result = True
                elif columnName == self.cnStyle:
                    plotStyle.copyFrom(value)
                    result = True
                elif columnName == self.cnSensor:
                    plotStyle.setSensor(value)
                    result = True
                elif columnName == self.cnTemporalProfile:
                    plotStyle.setTemporalProfile(value)
                    result = True
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.savePlotSettings(plotStyle, index='DEFAULT')
            self.dataChanged.emit(index, index)

        return result

    def savePlotSettings(self, sensorPlotSettings, index='DEFAULT'):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return
        #todo
        assert isinstance(sensorPlotSettings, TemporalProfilePlotStyle)
        #todo: avoid dumps
        id = 'SPS.{}.{}'.format(index, sensorPlotSettings.sensor().id())
        d = pickle.dumps(sensorPlotSettings)
        SETTINGS.setValue(id, d)

    def restorePlotSettings(self, sensor, index='DEFAULT'):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return None

        #todo
        assert isinstance(sensor, SensorInstrument)
        id = 'SPS.{}.{}'.format(index, sensor.id())
        sensorPlotSettings = SETTINGS.value(id)
        if sensorPlotSettings is not None:
            try:
                sensorPlotSettings = pickle.loads(sensorPlotSettings)
                s = ""
            except:
                sensorPlotSettings = None
                pass
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if isinstance(sensorPlotSettings, TemporalProfilePlotStyle):
            return sensorPlotSettings
    def flags(self, index):
        if index.isValid():
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            columnName = self.columNames[index.column()]
            flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if columnName in [self.cnTemporalProfile]:
                flags = flags | Qt.ItemIsUserCheckable
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if columnName in [self.cnTemporalProfile, self.cnSensor, self.cnExpression, self.cnStyle]: #allow check state
                flags = flags | Qt.ItemIsEditable
            return flags
            #return item.qt_flags(index.column())
        return Qt.NoItemFlags

    def headerData(self, col, orientation, role):
        if Qt is None:
            return None
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            return self.columNames[col]
        elif orientation == Qt.Vertical and role == Qt.DisplayRole:
            return col
        return None



Benjamin Jakimow's avatar
Benjamin Jakimow committed
class ProfileViewDockUI(QgsDockWidget, loadUI('profileviewdock.ui')):
    def __init__(self, parent=None):
        super(ProfileViewDockUI, self).__init__(parent)
        self.setupUi(self)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #self.line.setVisible(False)
        #self.listWidget.setVisible(False)
        self.stackedWidget.setCurrentWidget(self.page2D)
        self.plotWidget3D = None
            l = self.layout3DPlotWidget

            from pyqtgraph.opengl import GLViewWidget
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.plotWidget3D = GLViewWidget(parent=self.page3D)
            self.plotWidget3D.setObjectName('plotWidget3D')
            size = self.labelDummy3D.size()
            self.labelDummy3D.setVisible(False)
            l.removeWidget(self.labelDummy3D)
            #self.plotWidget3D.setSize(size)

        #pi = self.plotWidget2D.plotItem
        #ax = DateAxis(orientation='bottom', showValues=True)
        #pi.layout.addItem(ax, 3,2)

        self.baseTitle = self.windowTitle()
        self.TS = None
        self.progressBar.setMinimum(0)
        self.progressBar.setMaximum(100)
        self.progressBar.setValue(0)
        self.progressInfo.setText('')
        self.pxViewModel2D = None
        self.pxViewModel3D = None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.tableView2DProfiles.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
        self.tableView2DProfiles.setSortingEnabled(True)
        self.tableViewTemporalProfiles.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
        self.tableViewTemporalProfiles.setSortingEnabled(True)

def dateDOY(date):
    if isinstance(date, np.datetime64):
        date = date.astype(datetime.date)
    return date.timetuple().tm_yday

def daysPerYear(year):
    if isinstance(year, np.datetime64):
        year = year.astype(datetime.date)
    if isinstance(year, datetime.date):
        year = year.timetuple().tm_year

    return dateDOY(datetime.date(year=year, month=12, day=31))

    #kindly taken from https://stackoverflow.com/questions/6451655/python-how-to-convert-datetime-dates-to-decimal-years
    if isinstance(d, np.datetime64):
        d = d.astype(datetime.datetime)
    assert isinstance(d, datetime.date)

    yearDuration = daysPerYear(d)
    yearElapsed = d.timetuple().tm_yday
    fraction = float(yearElapsed) / float(yearDuration)
    return float(d.year) + fraction

def num2date(n, dt64=True):
    n = float(n)

    year = int(n)
    fraction = n - year
    yearDuration = daysPerYear(year)
    yearElapsed = fraction * yearDuration

    import math
    doy = round(yearElapsed)
    if doy < 1:
        doy = 1
    try:
        date = datetime.date(year, 1, 1) + datetime.timedelta(days=doy-1)
    except:
        s = ""
    if dt64:
        return np.datetime64(date)
    else:



    #return np.datetime64('{:04}-01-01'.format(year), 'D') + np.timedelta64(int(yearElapsed), 'D')


def depr_date2num(d):
    d2 = d.astype(datetime.datetime)
    o = d2.toordinal()

    #assert d == num2date(o)

    return o
def depr_num2date(n):
    n = int(np.round(n))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    d = datetime.datetime.fromordinal(n)
    d = d.date()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    return np.datetime64('{:04}-{:02}-{:02}'.format(d.year,d.month,d.day), 'D')

class TemporalProfile(QObject):

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    _mNextID = 0
    @staticmethod
    def nextID():
        n = TemporalProfile._mNextID
        TemporalProfile._mNextID += 1
        return n

    def __init__(self, timeSeries, spatialPoint):
        super(TemporalProfile, self).__init__()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(timeSeries, TimeSeries)
        assert isinstance(spatialPoint, SpatialPoint)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mTimeSeries = timeSeries
        self.mCoordinate = spatialPoint
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mID = TemporalProfile.nextID()
        self.mData = {}
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mUpdated = False
        self.mName = '#{}'.format(self.mID)

        self.mLoaded = self.mLoadedMax = 0
        self.initMetadata()
        self.updateLoadingStatus()

    def initMetadata(self):
        for tsd in self.mTimeSeries:
            assert isinstance(tsd, TimeSeriesDatum)
            meta = {'doy':tsd.doy,
                    'date':str(tsd.date)}
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.updateData(tsd, meta)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    sigNameChanged = pyqtSignal(str)
    def setName(self, name):
        if name != self.mName:
            self.mName = name
            self.sigNameChanged.emit(self.mName)

    def name(self):
        return self.mName


    def plot(self):

        import pyqtgraph as pg

        for sensor in self.mTimeSeries.sensors():
            assert isinstance(sensor, SensorInstrument)

            plotStyle = TemporalProfilePlotStyle(self)
            plotStyle.setSensor(sensor)

            pi = TemporalProfilePlotDataItem(plotStyle)
            pi.setClickable(True)
            pw = pg.plot(title=self.name())
            pw.getPlotItem().addItem(pi)
            pi.setColor('green')
            pg.QAPP.exec_()

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def updateData(self, tsd, values):
        assert isinstance(tsd, TimeSeriesDatum)
        assert isinstance(values, dict)

        if tsd not in self.mData.keys():
            self.mData[tsd] = {}

        self.mData[tsd].update(values)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.updateLoadingStatus()
        self.mUpdated = True

    def resetUpdated(self):
        self.mUpdated = False

    def updated(self):
        return self.mUpdated
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def qgsFieldFromKeyValue(self, key, value):
        t = type(value)
        if t in [int, float] or np.isreal(value):
Benjamin Jakimow's avatar
Benjamin Jakimow committed

            fLen  = 0
            fPrec = 0
            fComm = ''
            fType = ''
            f = QgsField(key, QVariant.Double, 'double', 40, 5)
        else:
            f = QgsField(key, QVariant.String, 'text', 40, 5)
        return f

    def dataFromExpression(self, sensor, expression, dateType='date'):
        assert dateType in ['date','doy']
        x = []
        y = []
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if not isinstance(expression, QgsExpression):
            expression = QgsExpression(expression)
        assert isinstance(expression, QgsExpression)

        expression = QgsExpression(expression)
        fields = QgsFields()
        sensorTSDs = sorted([tsd for tsd in self.mData.keys() if tsd.sensor == sensor])
        for tsd in sensorTSDs:
            data = self.mData[tsd]
            for k, v in data.items():
                if v is not None and fields.fieldNameIndex(k) == -1:
                    fields.append(self.qgsFieldFromKeyValue(k, v))
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        for i, tsd in enumerate(sensorTSDs):
            assert isinstance(tsd, TimeSeriesDatum)
            data = self.mData[tsd]
            context = QgsExpressionContext()
            scope = QgsExpressionContextScope()
            f = QgsFeature()
            f.setFields(fields)
            f.setValid(True)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            for k, v in data.items():
                if v is None:
                    continue
                idx = f.fieldNameIndex(k)
                field = f.fields().field(idx)
                if field.typeName() == 'text':
                    v = str(v)
                else:
                    v = float(v)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
                f.setAttribute(k,v)

            scope.setFeature(f)
            context.appendScope(scope)
            #value = expression.evaluatePrepared(f)
            value = expression.evaluate(context)


            if value in [None, NULL]:
                s = ""
            else:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                if dateType == 'date':
                    x.append(date2num(tsd.date))
                elif dateType == 'doy':
                    x.append(tsd.doy)
                y.append(value)

        #return np.asarray(x), np.asarray(y)
        assert len(x) == len(y)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def data(self, tsd):
        assert isinstance(tsd, TimeSeriesDatum)
        if self.hasData(tsd):
            return self.mData[tsd]
        else:
            return {}


    def loadingStatus(self):
        """
        Returns the loading status in terms of single pixel values.
        nLoaded = sum of single band values
        nLoadedMax = potential maximum of band values that might be loaded
        :return: (nLoaded, nLoadedMax)
        """
        return self.mLoaded, self.mLoadedMax

    def updateLoadingStatus(self):
        """
        Calculates and the loading status in terms of single pixel values.
        nMax is the sum of all bands over each TimeSeriesDatum and Sensors
        """

        self.mLoaded = self.mLoadedMax

        for tsd in self.mTimeSeries:
            assert isinstance(tsd, TimeSeriesDatum)
            self.mLoadedMax += tsd.sensor.nb
            if self.hasData(tsd):
                self.mLoaded += len([k for k in self.mData[tsd].keys() if k.startswith('b')])


    def hasData(self,tsd):
        assert isinstance(tsd, TimeSeriesDatum)
        return tsd in self.mData.keys()

    def __repr__(self):
        return 'TemporalProfile {}'.format(self.mCoordinate)



class SpectralTemporalVisualization(QObject):

    sigShowPixel = pyqtSignal(TimeSeriesDatum, QgsPoint, QgsCoordinateReferenceSystem)

    """
    Signalizes to move to specific date of interest
    """
    sigMoveToDate = pyqtSignal(np.datetime64)
        super(SpectralTemporalVisualization, self).__init__()

        assert isinstance(ui, ProfileViewDockUI), 'arg ui of type: {} {}'.format(type(ui), str(ui))
        if DEBUG:
            import timeseriesviewer.pixelloader
            timeseriesviewer.pixelloader.DEBUG = True

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.TS = None
        self.pixelLoader = PixelLoader()
        self.pixelLoader.sigPixelLoaded.connect(self.onPixelLoaded)
        self.pixelLoader.sigLoadingStarted.connect(lambda: self.ui.progressInfo.setText('Start loading...'))
        self.plot_initialized = False
        self.tableView2DProfiles = ui.tableView2DProfiles
        self.tableView2DProfiles.setSortingEnabled(False)


        # self.mSelectionModel.currentChanged.connect(self.onCurrentSelectionChanged)

        self.plot2D = ui.plotWidget2D
        pi = self.plot2D.getPlotItem()
        pi.getViewBox().sigMoveToDate.connect(self.sigMoveToDate)
        pi.getAxis('bottom').setLabel(LABEL_TIME)
        pi.getAxis('left').setLabel(LABEL_DN)
        self.plot2Dvline = pg.InfiniteLine(angle=90, movable=False)
        self.plot2Dhline = pg.InfiniteLine(angle=0, movable=False)
        #self.plot2DLabel = pg.TextItem(text='LABEL')
        self.plot2DLabel = pg.LabelItem(justify='right')
        #pi.setContentsMargins(0.1, 0.1, 0.1, 0.1)
        pi.addItem(self.plot2Dvline, ignoreBounds=True)
        pi.addItem(self.plot2Dhline, ignoreBounds=True)
        self.plot2D.addItem(self.plot2DLabel, ignoreBounds=True)

        self.proxy2D = pg.SignalProxy(self.plot2D.scene().sigMouseMoved, rateLimit=60, slot=self.onMouseMoved2D)
        self.plot3D = ui.plotWidget3D
        self.plot3D.setCameraPosition(distance=50)

        ## Add a grid to the view
        if OPENGL_AVAILABLE:
            import pyqtgraph.opengl as gl
            self.glGridItem = gl.GLGridItem()
            self.glGridItem.setDepthValue(10)  # draw grid after surfaces since they may be translucent
            self.glPlotDataItems = []
            self.plot3D.addItem(self.glGridItem)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.tpCollection = TemporalProfileCollection()
        self.tpCollectionListModel = TemporalProfileCollectionListModel(self.tpCollection)

        self.ui.tableViewTemporalProfiles.setModel(self.tpCollection)
        self.ui.tableViewTemporalProfiles.selectionModel().selectionChanged.connect(self.onTemporalProfileSelectionChanged)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        self.ui.cbTemporalProfile3D.setModel(self.tpCollectionListModel)
        #self.pxCollection.sigPixelAdded.connect(self.requestUpdate)
        #self.pxCollection.sigPixelRemoved.connect(self.clear)

        self.plotSettingsModel = None
        self.pixelLoader.sigLoadingStarted.connect(self.clear)
        self.pixelLoader.sigLoadingFinished.connect(lambda : self.plot2D.enableAutoRange('x', False))
Benjamin Jakimow's avatar
Benjamin Jakimow committed


        # self.VIEW.setItemDelegateForColumn(3, PointStyleDelegate(self.VIEW))
        self.plotData2D = dict()
        self.plotData3D = dict()

        self.updateRequested = True
        self.updateTimer = QTimer(self)
        self.updateTimer.timeout.connect(self.onDataUpdate)
        self.updateTimer.start(5000)

        self.sigMoveToDate.connect(self.onMoveToDate)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.initActions()


    def onMouseMoved2D(self, evt):
        pos = evt[0]  ## using signal proxy turns original arguments into a tuple

        plotItem = self.plot2D.getPlotItem()
        if plotItem.sceneBoundingRect().contains(pos):
            mousePoint = plotItem.vb.mapSceneToView(pos)
            x = mousePoint.x()
            y = mousePoint.y()

            index = int(mousePoint.x())
            self.plot2DLabel.setText('Refreshed {}'.format(mousePoint.x(), mousePoint.y()))
            #if index > 0 and index < len(data1):
            #    label.setText(
            #        "<span style='font-size: 12pt'>x=%0.1f,   <span style='color: red'>y1=%0.1f</span>,   <span style='color: green'>y2=%0.1f</span>" % (
            #        mousePoint.x(), data1[index], data2[index]))
            self.plot2Dvline.setPos(mousePoint.x())
            self.plot2Dhline.setPos(mousePoint.y())


        return
        vb = plotItem.vb


        if plotItem.sceneBoundingRect().contains(pos):
            mousePoint = vb.mapSceneToView(pos)

            self.vLine.setPos(mousePoint.x())
            self.hLine.setPos(mousePoint.y())

            info = '{:0.2f}, {:0.2f}'.format(mousePoint.x(), mousePoint.y())
            self.tbCursorLocationValue.setText(info)
            """
            self.mlabel.setText(
                    "<span style='font-size: 12pt'>{}</span>".format(info)
            """
        else:
            self.tbCursorLocationValue.setText('')
            self.mlabel.setText('')



    def selected2DPlotStyles(self):
        result = []

        m = self.ui.tableView2DProfiles.model()
        for idx in selectedModelIndices(self.ui.tableView2DProfiles):
            result.append(m.idx2plotStyle(idx))
        return result

    def selectedTemporalProfiles(self):
        result = []
        m = self.ui.tableViewTemporalProfiles.model()
        for idx in selectedModelIndices(self.ui.tableViewTemporalProfiles):
            result.append(m.idx2tp(idx))
        return result

    def removePlotStyles(self, plotStyles):
        m = self.ui.tableView2DProfiles.model()
        if isinstance(m, PlotSettingsModel):
            m.removePlotStyles(plotStyles)

    def removeTemporalProfiles(self, temporalProfiles):
        m = self.ui.tableViewTemporalProfiles.model()
        if isinstance(m, TemporalProfileCollection):
            m.removeTemporalProfiles(temporalProfiles)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def createNewPlotStyle(self):
        l = len(self.tpCollection)
        if l > 0:
            temporalProfile = self.tpCollection[0]
            plotStyle = TemporalProfilePlotStyle(temporalProfile)
            plotStyle.sigExpressionUpdated.connect(self.updatePlot2D)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            sensors = self.TS.Sensors.keys()
            if len(sensors) > 0:
                plotStyle.setSensor(sensors[0])
            self.plotSettingsModel.insertPlotStyles([plotStyle])
            pdi = plotStyle.createPlotItem(self.plot2D)

            assert isinstance(pdi, TemporalProfilePlotDataItem)
            pdi.sigClicked.connect(self.onProfileClicked2D)
            pdi.sigPointsClicked.connect(self.onPointsClicked2D)
            self.plot2D.getPlotItem().addItem(pdi)
            #self.plot2D.getPlotItem().addItem(pg.PlotDataItem(x=[1, 2, 3], y=[1, 2, 3]))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            #plotItem.addDataItem(pdi)
            #plotItem.plot().sigPlotChanged.emit(plotItem)
            self.updatePlot2D()

Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def onProfileClicked2D(self, pdi):
        if isinstance(pdi, TemporalProfilePlotDataItem):
            sensor = pdi.mPlotStyle.sensor()
            tp = pdi.mPlotStyle.temporalProfile()
            if isinstance(tp, TemporalProfile) and isinstance(sensor, SensorInstrument):
                info = ['Sensor:{}'.format(sensor.name()),
                        'Coordinate:{}, {}'.format(tp.mCoordinate.x(), tp.mCoordinate.y())]
                self.ui.tbInfo2D.setPlainText('\n'.join(info))


    def onPointsClicked2D(self, pdi, spottedItems):
        if isinstance(pdi, TemporalProfilePlotDataItem) and isinstance(spottedItems, list):
            sensor = pdi.mPlotStyle.sensor()
            tp = pdi.mPlotStyle.temporalProfile()
            if isinstance(tp, TemporalProfile) and isinstance(sensor, SensorInstrument):

                info = ['Sensor: {}'.format(sensor.name()),
                        'Coordinate: {}, {}'.format(tp.mCoordinate.x(), tp.mCoordinate.y())]

                for item in spottedItems:
                    pos = item.pos()
                    x = pos.x()
                    y = pos.y()
                    date = num2date(x)
                    info.append('Date: {}\nValue: {}'.format(date, y))

                self.ui.tbInfo2D.setPlainText('\n'.join(info))
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def onTemporalProfileSelectionChanged(self, selected, deselected):
        self.ui.actionRemoveTemporalProfile.setEnabled(len(selected) > 0)

    def onPlot2DSelectionChanged(self, selected, deselected):

        self.ui.actionRemoveView.setEnabled(len(selected) > 0)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def initActions(self):

        self.ui.actionRemoveView.setEnabled(False)
        self.ui.actionRemoveTemporalProfile.setEnabled(False)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.ui.btnAddView.setDefaultAction(self.ui.actionAddView)
        self.ui.btnRemoveView.setDefaultAction(self.ui.actionRemoveView)
        self.ui.btnRefresh2D.setDefaultAction(self.ui.actionRefresh2D)
        self.ui.btnRefresh3D.setDefaultAction(self.ui.actionRefresh3D)
        self.ui.btnRemoveTemporalProfile.setDefaultAction(self.ui.actionRemoveTemporalProfile)
        self.ui.actionRefresh2D.triggered.connect(self.updatePlot2D)
        self.ui.actionRefresh3D.triggered.connect(self.updatePlot3D)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.ui.actionAddView.triggered.connect(self.createNewPlotStyle)
        self.ui.actionRemoveView.triggered.connect(lambda:self.removePlotStyles(self.selected2DPlotStyles()))
        self.ui.actionRemoveTemporalProfile.triggered.connect(lambda :self.removeTemporalProfiles(self.selectedTemporalProfiles()))

        self.tpCollection.sigMaxProfilesChanged.connect(self.ui.sbMaxTP.setValue)
        self.ui.sbMaxTP.valueChanged.connect(self.tpCollection.setMaxProfiles)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #todo: self.ui.actionRemoveView.triggered.connect(self.plotSettingsModel.createPlotStyle)


    def setTimeSeries(self, TS):

        assert isinstance(TS, TimeSeries)
        self.TS = TS

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.tpCollection.connectTimeSeries(self.TS)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.plotSettingsModel = PlotSettingsModel(self.tpCollection, self.plot2D, parent=self)
        self.plotSettingsModel.sigVisibilityChanged.connect(self.setVisibility)
        self.plotSettingsModel.sigDataChanged.connect(self.requestUpdate)
        self.plotSettingsModel.rowsInserted.connect(self.onRowsInserted)
        # self.plotSettingsModel.modelReset.connect(self.updatePersistantWidgets)
        self.tableView2DProfiles.setModel(self.plotSettingsModel)
        #self.tableView2DProfilesSelectionModel = QItemSelectionModel(self.mModel)
        self.tableView2DProfiles.selectionModel().selectionChanged.connect(self.onPlot2DSelectionChanged)
        #self.tableView2DProfilesSelectionModel.selectionChanged.connect(self.onPlot2DSelectionChanged)
        #self.tableView2DProfilesSelectionModel.setSelectionModel(self.mSelectionModel)
        self.delegate = PlotSettingsWidgetDelegate(self.tableView2DProfiles, self.TS, self.tpCollectionListModel)
        self.delegate.setItemDelegates(self.tableView2DProfiles)
    sigMoveToTSD = pyqtSignal(TimeSeriesDatum)
    def onMoveToDate(self, date):
        dt = np.asarray([np.abs(tsd.date - date) for tsd in self.TS])