Skip to content
Snippets Groups Projects
temporalprofiles2d.py 45.4 KiB
Newer Older
    #sigSensorRemoved = pyqtSignal(SensorInstrument)
    #sigPixelAdded = pyqtSignal()
    #sigPixelRemoved = pyqtSignal()

    sigTemporalProfilesAdded = pyqtSignal(list)
    sigTemporalProfilesRemoved = pyqtSignal(list)
    sigMaxProfilesChanged = pyqtSignal(int)
    def __init__(self, ):
        super(TemporalProfileCollection, self).__init__()
        #self.sensorPxLayers = dict()
        #self.memLyrCrs = QgsCoordinateReferenceSystem('EPSG:4326')
        self.newDataFlag = False

        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')
        self.mTemporalProfiles = []
        self.mTPLookupSpatialPoint = {}
        self.mTPLookupID = {}
        self.mCurrentTPID = 0
        self.mMaxProfiles = 10

        self.nextID = 0

    def __len__(self):
        return len(self.mTemporalProfiles)

    def __iter__(self):
        return iter(self.mTemporalProfiles)

    def __getitem__(self, slice):
        return self.mTemporalProfiles[slice]

    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() and index.row() < len(self.mTemporalProfiles) :
            return self.mTemporalProfiles[index.row()]
        return None

    def tp2idx(self, temporalProfile):
        assert isinstance(temporalProfile, TemporalProfile)

        if temporalProfile in self.mTemporalProfiles:
            row = self.mTemporalProfiles.index(temporalProfile)
            return self.createIndex(row, 0)
        else:
            return QModelIndex()

    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, nNoData, nMax = TP.loadingStatus()
                    value = '{}/{}/{} ({:0.2f} %)'.format(nIs, nNoData, nMax, float(nIs+nNoData) / 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 = 'Coordinate: {}'.format(TP.mCoordinate)
            elif columnName == self.mcnLoaded:
                nIs, nNoData, nMax = TP.loadingStatus()
                value = 'Band-pixels: {} loaded, {} no-data, {} total'.format(nIs, nNoData, nMax)
        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)

        temporalProfiles = [t for t in temporalProfiles if t not in self]
        l = len(temporalProfiles)

        if l > 0:

            #remove older profiles
            self.prune(nMax=self.mMaxProfiles - l)

            self.beginInsertRows(QModelIndex(), i, i + l - 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
                temporalProfile.sigDataChanged.connect(lambda: self.onUpdate(temporalProfile))
                temporalProfile.sigNameChanged.connect(lambda: self.onUpdate(temporalProfile))
                i += 1
            self.endInsertRows()

            self.sigTemporalProfilesAdded.emit(temporalProfiles)


    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 id in self.mTPLookupID:
            return self.mTPLookupID[id]
        else:
            return None

    def fromSpatialPoint(self, spatialPoint):
        if spatialPoint in self.mTPLookupSpatialPoint:
            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)

        temporalProfiles = [tp for tp in temporalProfiles if isinstance(tp, TemporalProfile) and tp in self.mTemporalProfiles]

        if len(temporalProfiles) > 0:

            def deleteFromDict(d, value):
                assert isinstance(d, dict)
                if value in d.values():
                    key = list(d.keys())[list(d.values()).index(value)]
                    d.pop(key)

            for temporalProfile in temporalProfiles:
                assert isinstance(temporalProfile, TemporalProfile)
                idx = self.tp2idx(temporalProfile)
                row = idx.row()
                self.beginRemoveRows(QModelIndex(), row, row)
                self.mTemporalProfiles.remove(temporalProfile)

                deleteFromDict(self.mTPLookupID, temporalProfile)
                deleteFromDict(self.mTPLookupSpatialPoint,  temporalProfile)

                self.endRemoveRows()
            self.sigTemporalProfilesRemoved.emit(temporalProfiles)


    def connectTimeSeries(self, timeSeries):
        self.clear()

        if isinstance(timeSeries, TimeSeries):
            self.TS = timeSeries
            #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

    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
        """
        old = self.mMaxProfiles

        assert n >= 1
        if old != n:
            self.mMaxProfiles = n

            self.prune()
            self.sigMaxProfilesChanged.emit(self.mMaxProfiles)


    def prune(self, nMax=None):
        """
        Reduces the number of temporal profile to the value n defined with .setMaxProfiles(n)
        :return: [list-of-removed-TemporalProfiles]
        """
        if nMax is None:
            nMax = self.mMaxProfiles

        nMax = max(nMax, 1)

        toRemove = len(self) - nMax
        if toRemove > 0:
            toRemove = sorted(self[:], key=lambda p:p.mID)[0:toRemove]
            self.removeTemporalProfiles(toRemove)





    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 onUpdate(self, tp):
        assert isinstance(tp, TemporalProfile)

        if tp in self.mTemporalProfiles:
            idx0 = self.tp2idx(tp)
            idx1 = self.createIndex(idx0.row(), self.rowCount())
            self.dataChanged.emit(idx0, idx1, [Qt.DisplayRole])

    def sort(self, col, order):
        if self.rowCount() == 0:
            return

        self.layoutAboutToBeChanged.emit()
        colName = self.mColumNames[col]
        r = order != Qt.AscendingOrder

        if colName == self.mcnName:
            self.mTemporalProfiles.sort(key = lambda TP:TP.name(), reverse=r)
        elif colName == self.mcnCoordinate:
            self.mTemporalProfiles.sort(key=lambda TP: str(TP.mCoordinate), reverse=r)
            self.mTemporalProfiles.sort(key=lambda TP: TP.mID, reverse=r)
            self.mTemporalProfiles.sort(key=lambda TP: TP.loadingStatus(), reverse=r)
        self.layoutChanged.emit()


    def addPixelLoaderResult(self, d):
        assert isinstance(d, PixelLoaderTask)
        if d.success():
            for TPid in d.temporalProfileIDs:
                TP = self.temporalProfileFromID(TPid)
                if isinstance(TP, TemporalProfile):
                    TP.pullDataUpdate(d)
                else:
                    if DEBUG:
                        print('got result for missing TPid {}'.format(TPid))
                    s = ""

    def clear(self):
        #todo: remove TS Profiles
        #self.mTemporalProfiles.clear()
        #self.sensorPxLayers.clear()
        pass



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(lambda : self.modelReset.emit())
        self.mTPColl.dataChanged.connect(self.onDataChanged)

    def onDataChanged(self, idx0, idx1, roles):
        idx0r = self.createIndex(idx0.row(), idx0.column())
        idx1r = self.createIndex(idx1.row(), idx1.column())

        tp = self.idx2tp(idx0r)
        assert isinstance(tp, TemporalProfile)
        self.dataChanged.emit(idx0r, idx1r, roles)


    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.idx2tp(index)
        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
        else:
            if role == Qt.DisplayRole:
                value = 'undefined'
            elif role == Qt.ToolTipRole:
                value = 'Please select a location to read the temporal profile from'

if __name__ == '__main__':
    import site, sys
    from timeseriesviewer import utils
    qgsApp = utils.initQgisApplication()
    DEBUG = False

    w = TemporalProfilePlotStyle3DWidget()
    w.show()
    print(w.plotStyle())

    #btn = TemporalProfile3DPlotStyleButton()
    #btn.show()
    qgsApp.exec_()
    qgsApp.exitQgis()