Skip to content
Snippets Groups Projects
timeseries.py 60.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def saveToFile(self, path):
    
            """
            Saves the TimeSeries sources into a CSV file
            :param path: str, path of CSV file
            :return: path of CSV file
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if path is None or len(path) == 0:
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
                return None
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            lines = []
            lines.append('#Time series definition file: {}'.format(np.datetime64('now').astype(str)))
    
            lines.append('#<image path>')
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for TSD in self.mTSDs:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                assert isinstance(TSD, TimeSeriesDatum)
                for pathImg in TSD.sourceUris():
                    lines.append(pathImg)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            lines = [l+'\n' for l in lines]
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            with open(path, 'w') as f:
                f.writelines(lines)
    
                messageLog('Time series source images written to {}'.format(path))
    
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
            return path
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def pixelSizes(self):
    
            """
            Returns the pixel sizes of all SensorInstruments
            :return: [list-of-QgsRectangles]
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for sensor in self.mSensors2TSDs.keys():
    
                r.append((QgsRectangle(sensor.px_size_x, sensor.px_size_y)))
            return r
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def maxSpatialExtent(self, crs=None)->SpatialExtent:
    
            """
            Returns the maximum SpatialExtent of all images of the TimeSeries
            :param crs: QgsCoordinateSystem to express the SpatialExtent coordinates.
            :return:
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            extent = None
            for i, tsd in enumerate(self.mTSDs):
                assert isinstance(tsd, TimeSeriesDatum)
                ext = tsd.spatialExtent()
                if isinstance(extent, SpatialExtent):
                    extent = extent.combineExtentWith(ext)
                else:
                    extent = ext
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            return extent
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            """
            Returns the TimeSeriesDatum related to an image source
            :param pathOfInterest: str, image source uri
            :return: TimeSeriesDatum
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for tsd in self.mTSDs:
                assert isinstance(tsd, TimeSeriesDatum)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if pathOfInterest in tsd.sourceUris():
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def tsd(self, date: np.datetime64, sensor)->TimeSeriesDatum:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            Returns the TimeSeriesDatum identified by ate nd sensorID
            :param date:
            :param sensor: SensorInstrument | str with sensor id
            :return:
            """
            assert isinstance(date, np.datetime64)
            if isinstance(sensor, str):
                sensor = self.sensor(sensor)
            if isinstance(sensor, SensorInstrument):
                for tsd in self.mTSDs:
                    if tsd.date() == date and tsd.sensor() == sensor:
                        return tsd
            return None
    
    
        def insertTSD(self, tsd: TimeSeriesDatum)->TimeSeriesDatum:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            Inserts a TimeSeriesDatum
            :param tsd: TimeSeriesDatum
            """
            #insert sorted by time & sensor
            assert tsd not in self.mTSDs
            assert tsd.sensor() in self.mSensors
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            tsd.mTimeSeries = self
            tsd.sigRemoveMe.connect(lambda: self.removeTSDs([tsd]))
    
            tsd.rowsAboutToBeRemoved.connect(self.onSourcesAboutToBeRemoved)
            tsd.rowsRemoved.connect(self.onSourcesRemoved)
            tsd.rowsAboutToBeInserted.connect(self.onSourcesAboutToBeInserted)
            tsd.rowsInserted.connect(self.onSourcesInserted)
    
            row = bisect.bisect(self.mTSDs, tsd)
            self.beginInsertRows(self.mRootIndex, row, row)
            self.mTSDs.insert(row, tsd)
            self.endInsertRows()
            #self.rowsInserted()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            return tsd
    
    
        def onSourcesAboutToBeRemoved(self, parent, first, last):
            s = ""
            pass
    
        def onSourcesRemoved(self, parent, first, last):
            s = ""
        
        def onSourcesAboutToBeInserted(self, parent, first, last):
            s = ""
            
        def onSourcesInserted(self, parent, first, last):
            s = ""
            
      
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def removeTSDs(self, tsds):
            """
            Removes a list of TimeSeriesDatum
            :param tsds: [list-of-TimeSeriesDatum]
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            removed = list()
            for tsd in tsds:
                assert isinstance(tsd, TimeSeriesDatum)
    
                row = self.mTSDs.index(tsd)
                self.beginRemoveRows(self.mRootIndex, row, row)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                self.mTSDs.remove(tsd)
                tsd.mTimeSeries = None
                removed.append(tsd)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.sigTimeSeriesDatesRemoved.emit(removed)
    
    
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def tsds(self, date:np.datetime64=None, sensor:SensorInstrument=None)->list:
    
    
            """
            Returns a list of  TimeSeriesDatum of the TimeSeries. By default all TimeSeriesDatum will be returned.
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            :param date: numpy.datetime64 to return the TimeSeriesDatum for
            :param sensor: SensorInstrument of interest to return the [list-of-TimeSeriesDatum] for.
    
            :return: [list-of-TimeSeriesDatum]
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            tsds = self.mTSDs[:]
            if date:
                tsds = [tsd for tsd in tsds if tsd.date() == date]
            if sensor:
    
                tsds = [tsd for tsd in tsds if tsd.sensor() == sensor]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return tsds
    
        def clear(self):
    
            """
            Removes all data sources from the TimeSeries (which will be empty after calling this routine).
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.removeTSDs(self[:])
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def addSensor(self, sensor:SensorInstrument):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            Adds a Sensor
            :param sensor: SensorInstrument
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if not sensor in self.mSensors:
                self.mSensors.append(sensor)
                self.sigSensorAdded.emit(sensor)
                return sensor
            else:
                return None
    
        def checkSensorList(self):
            """
            Removes sensors without linked TSD / no data
            """
            to_remove = []
            for sensor in self.sensors():
                tsds = [tsd for tsd in self.mTSDs if tsd.sensor() == sensor]
                if len(tsds) == 0:
                    to_remove.append(sensor)
            for sensor in to_remove:
                self.removeSensor(sensor)
    
        def removeSensor(self, sensor:SensorInstrument)->SensorInstrument:
            """
            Removes a sensor and all linked images
            :param sensor: SensorInstrument
            :return: SensorInstrument or none, if sensor was not defined in the TimeSeries
            """
            assert isinstance(sensor, SensorInstrument)
            if sensor in self.mSensors:
                tsds = [tsd for tsd in self.mTSDs if tsd.sensor() == sensor]
                self.removeTSDs(tsds)
                self.mSensors.remove(sensor)
                self.sigSensorRemoved.emit(sensor)
                return sensor
            return None
    
        def addSources(self, sources:list, progressDialog:QProgressDialog=None):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            Adds new data sources to the TimeSeries
            :param sources: [list-of-TimeSeriesSources]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(sources, list)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            nMax = len(sources)
    
            #self.sigTimeSeriesSourcesAboutToBeChanged.emit()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            self.sigLoadingProgress.emit(0, nMax, 'Start loading {} sources...'.format(nMax))
    
            
            if isinstance(progressDialog, QProgressDialog):
                progressDialog.setRange(0, nMax)
                progressDialog.setLabelText('Load rasters...'.format(nMax))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # 1. read sources
            # this could be excluded into a parallel process
            addedDates = []
            for i, source in enumerate(sources):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                msg = None
    
                if False: #debug
                    newTSD = self._addSource(source)
                else:
                    try:
                        newTSD = self._addSource(source)
                    except Exception as ex:
                        msg = 'Unable to add: {}\n{}'.format(str(source), str(ex))
                        print(msg, file=sys.stderr)
    
    
                if isinstance(progressDialog, QProgressDialog):
                    if progressDialog.wasCanceled():
                        break
                    progressDialog.setValue(i)
                    progressDialog.setLabelText('{}/{}'.format(i+1, nMax))
    
    
                    self.sigLoadingProgress.emit(i+1, nMax, msg)
    
    
                if isinstance(newTSD, TimeSeriesDatum):
                    addedDates.append(newTSD)
            #if len(addedDates) > 0:
            if isinstance(progressDialog, QProgressDialog):
                progressDialog.setLabelText('Create map widgets...')
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if len(addedDates) > 0:
                self.sigTimeSeriesDatesAdded.emit(addedDates)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def _addSource(self, source:TimeSeriesSource)->TimeSeriesDatum:
            """
            :param source:
            :return: TimeSeriesDatum (if new created)
            """
            if isinstance(source, TimeSeriesSource):
                tss = source
            else:
                tss = TimeSeriesSource.create(source)
    
            assert isinstance(tss, TimeSeriesSource)
    
            newTSD = None
    
            tss.mDate = self.date2date(tss.date())
            date = tss.date()
            sid = tss.sid()
            sensor = self.sensor(sid)
            # if necessary, add a new sensor instance
            if not isinstance(sensor, SensorInstrument):
                sensor = self.addSensor(SensorInstrument(sid))
            assert isinstance(sensor, SensorInstrument)
            tsd = self.tsd(date, sensor)
            # if necessary, add a new TimeSeriesDatum instance
            if not isinstance(tsd, TimeSeriesDatum):
                tsd = self.insertTSD(TimeSeriesDatum(self, date, sensor))
                newTSD = tsd
                # addedDates.append(tsd)
            assert isinstance(tsd, TimeSeriesDatum)
            # add the source
            tsd.addSource(tss)
            return newTSD
    
    
        def setDateTimePrecision(self, mode:DateTimePrecision):
            """
            Sets the precision with which the parsed DateTime information will be handled.
            :param mode: TimeSeriesViewer:DateTimePrecision
            :return:
            """
            self.mDateTimePrecision = mode
    
            #do we like to update existing sources?
    
    
    
    
        def date2date(self, date:np.datetime64):
            assert isinstance(date, np.datetime64)
            if self.mDateTimePrecision == DateTimePrecision.Original:
                return date
            else:
                date = np.datetime64(date, self.mDateTimePrecision.value)
    
            return date
    
    
        def sources(self) -> list:
            """
            Returns the input sources
            :return: iterator over [list-of-TimeSeriesSources]
            """
    
            for tsd in self:
                for source in tsd:
                    yield source
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def sourceUris(self)->list:
            """
            Returns the uris of all sources
            :return: [list-of-str]
            """
            uris = []
            for tsd in self:
                assert isinstance(tsd, TimeSeriesDatum)
                uris.extend(tsd.sourceUris())
            return uris
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return len(self.mTSDs)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return iter(self.mTSDs)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def __getitem__(self, slice):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return self.mTSDs[slice]
    
    
        def __delitem__(self, slice):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.removeTSDs(slice)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def __contains__(self, item):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return item in self.mTSDs
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def __repr__(self):
            info = []
            info.append('TimeSeries:')
            l = len(self)
            info.append('  Scenes: {}'.format(l))
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return '\n'.join(info)
    
    
        def headerData(self, section, orientation, role):
            assert isinstance(section, int)
    
            if orientation == Qt.Horizontal and role == Qt.DisplayRole:
    
                if len(self.mColumnNames) > section:
                    return self.mColumnNames[section]
                else:
                    return ''
    
            else:
                return None
    
        def parent(self, index: QModelIndex) -> QModelIndex:
            """
            Returns the parent index of a QModelIndex `index`
            :param index: QModelIndex
            :return: QModelIndex
            """
            if not index.isValid():
                return QModelIndex()
    
            node = index.internalPointer()
            tsd = None
            tss = None
    
            if isinstance(node, TimeSeriesDatum):
                return self.mRootIndex
    
            elif isinstance(node, TimeSeriesSource):
                tss = node
                tsd = node.timeSeriesDatum()
                return self.createIndex(self.mTSDs.index(tsd), 0, tsd)
    
        def rowCount(self, index: QModelIndex=None) -> int:
            """
            Return the row-count, i.e. number of child node for a TreeNode as index `index`.
            :param index: QModelIndex
            :return: int
            """
            if index is None:
                index = QModelIndex()
    
            if not index.isValid():
                return len(self)
    
            node = index.internalPointer()
            if isinstance(node, TimeSeriesDatum):
                return len(node)
    
            if isinstance(node, TimeSeriesSource):
                return 0
    
    
        def columnNames(self) -> list:
            """
            Returns the column names
            :return: [list-of-string]
            """
            return self.mColumnNames[:]
    
        def columnCount(self, index:QModelIndex = None) -> int:
            """
            Returns the number of columns
            :param index: QModelIndex
            :return:
            """
    
            return len(self.mColumnNames)
    
    
        def connectTreeView(self, treeView):
            self.mTreeView = treeView
    
        def index(self, row: int, column: int, parent: QModelIndex = None) -> QModelIndex:
            """
            Returns the QModelIndex
            :param row: int
            :param column: int
            :param parent: QModelIndex
            :return: QModelIndex
            """
            if parent is None:
                parent = self.mRootIndex
            else:
                assert isinstance(parent, QModelIndex)
    
            if row < 0 or row >= len(self):
                return QModelIndex()
            if column < 0 or column >= len(self.mColumnNames):
                return QModelIndex()
    
    
            if parent == self.mRootIndex:
                # TSD node
                if row < 0 or row >= len(self):
                    return QModelIndex()
                return self.createIndex(row, column, self[row])
    
            elif parent.parent() == self.mRootIndex:
                # TSS node
                tsd = self.tsdFromIdx(parent)
                if row < 0 or row >= len(tsd):
                    return QModelIndex()
                return self.createIndex(row, column, tsd[row])
    
            return QModelIndex()
    
        def tsdToIdx(self, tsd:TimeSeriesDatum)->QModelIndex:
            """
            Returns an QModelIndex pointing on a TimeSeriesDatum of interest
            :param tsd: TimeSeriesDatum
            :return: QModelIndex
            """
            row = self.mTSDs.index(tsd)
            return self.index(row, 0)
    
        def tsdFromIdx(self, index: QModelIndex) -> TimeSeriesDatum:
            """
            Returns the TimeSeriesDatum related to an QModelIndex `index`.
            :param index: QModelIndex
            :return: TreeNode
            """
    
            if index.row() == -1 and index.column() == -1:
                return None
            elif not index.isValid():
                return None
            else:
                node = index.internalPointer()
                if isinstance(node, TimeSeriesDatum):
                    return node
                elif isinstance(node, TimeSeriesSource):
                    return node.timeSeriesDatum()
    
            return None
    
        def data(self, index, role):
            """
    
            :param index: QModelIndex
            :param role: Qt.ItemRole
            :return: object
            """
            assert isinstance(index, QModelIndex)
            if not index.isValid():
                return None
    
            node = index.internalPointer()
            tsd = None
            tss = None
            if isinstance(node, TimeSeriesSource):
                tsd = node.timeSeriesDatum()
                tss = node
            elif isinstance(node, TimeSeriesDatum):
                tsd = node
    
            if role == Qt.UserRole:
                return node
    
            cName = self.mColumnNames[index.column()]
    
            if isinstance(tss, TimeSeriesSource):
                if role in [Qt.DisplayRole]:
                    if cName == self.cnDate:
                        return str(tsd.date())
                    if cName == self.cnImages:
                        return tss.uri()
                    if cName == self.cnNB:
                        return tss.nb
                    if cName == self.cnNL:
                        return tss.nl
                    if cName == self.cnNS:
                        return tss.ns
                    if cName == self.cnCRS:
                        return tss.crs().description()
    
            if isinstance(tsd, TimeSeriesDatum):
                if role in [Qt.DisplayRole]:
                    if cName == self.cnSensor:
                        return tsd.sensor().name()
                    if cName == self.cnImages:
                        return len(tsd)
                    if cName == self.cnDate:
                        return str(tsd.date())
    
                if role == Qt.BackgroundColorRole and tsd in self.mCurrentDates:
                    return QColor('yellow')
    
    
            if isinstance(node, TimeSeriesDatum) and index.column() == 0:
                if role == Qt.CheckStateRole:
                    return Qt.Checked if tsd.isVisible() else Qt.Unchecked
    
    
    
    
            return None
    
        def setData(self, index: QModelIndex, value: typing.Any, role: int):
    
            if not index.isValid():
                return False
    
            result = False
    
            node = index.internalPointer()
            if isinstance(node, TimeSeriesDatum):
                if role == Qt.CheckStateRole and index.column() == 0:
                    node.setVisibility(value == Qt.Checked)
                    result = True
    
            if result == True:
                self.dataChanged.emit(index, index, [role])
    
            return result
    
        def flags(self, index):
            assert isinstance(index, QModelIndex)
            if not index.isValid():
                return Qt.NoItemFlags
            #cName = self.mColumnNames.index(index.column())
            flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
            if isinstance(index.internalPointer(), TimeSeriesDatum) and index.column() == 0:
                flags = flags | Qt.ItemIsUserCheckable
            return flags
    
    
    
    class TimeSeriesTableModel(QAbstractTableModel):
    
    
        def __init__(self, TS:TimeSeries, parent=None, *args):
    
    
            super(TimeSeriesTableModel, self).__init__()
            assert isinstance(TS, TimeSeries)
    
            self.cnDate = 'Date'
            self.cnSensor = 'Sensor'
            self.cnNS = 'ns'
            self.cnNL = 'nl'
            self.cnNB = 'nb'
            self.cnCRS = 'CRS'
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.cnImages = 'Image(s)'
    
            self.mColumnNames = [self.cnDate, self.cnSensor,
                                 self.cnNS, self.cnNL, self.cnNB,
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                                 self.cnCRS, self.cnImages]
    
            self.mTimeSeries = TS
            self.mSensors = set()
            self.mTimeSeries.sigTimeSeriesDatesRemoved.connect(self.removeTSDs)
            self.mTimeSeries.sigTimeSeriesDatesAdded.connect(self.addTSDs)
    
    
            self.items = []
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            self.addTSDs([tsd for tsd in self.mTimeSeries])
    
        def timeSeries(self)->TimeSeries:
            """
            :return: TimeSeries
            """
            return self.mTimeSeries
    
    
        def removeTSDs(self, tsds:list):
            """
            Removes TimeSeriesDatum instances
            :param tsds: list
            """
    
            for tsd in tsds:
    
                if tsd in self.mTimeSeries:
                    self.mTimeSeries.removeTSDs([tsd])
    
                elif tsd in self.items:
                    idx = self.getIndexFromDate(tsd)
                    self.removeRows(idx.row(), 1)
    
    
            idx = self.getIndexFromDate(tsd)
            self.dataChanged.emit(idx, idx)
    
    
        def sensorsChanged(self, sensor):
    
            i = self.mColumnNames.index(self.cnSensor)
    
            idx0 = self.createIndex(0, i)
            idx1 = self.createIndex(self.rowCount(), i)
            self.dataChanged.emit(idx0, idx1)
    
    
        def addTSDs(self, tsds):
    
    
            for tsd in tsds:
                assert isinstance(tsd, TimeSeriesDatum)
                row = bisect.bisect_left(self.items, tsd)
                self.beginInsertRows(QModelIndex(), row, row)
                self.items.insert(row, tsd)
                self.endInsertRows()
    
                #self.sort(self.sortColumnIndex, self.sortOrder)
    
    
            for tsd in tsds:
                assert isinstance(tsd, TimeSeriesDatum)
                tsd.sigVisibilityChanged.connect(lambda: self.tsdChanged(tsd))
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for sensor in set([tsd.sensor() for tsd in tsds]):
    
                if sensor not in self.mSensors:
                    self.mSensors.add(sensor)
    
                    sensor.sigNameChanged.connect(self.sensorsChanged)
    
        def rowCount(self, parent = QModelIndex())->int:
    
            return len(self.items)
    
    
        def removeRows(self, row, count , parent=QModelIndex()):
            self.beginRemoveRows(parent, row, row+count-1)
            toRemove = self.items[row:row+count]
            for tsd in toRemove:
                self.items.remove(tsd)
            self.endRemoveRows()
    
    
        def getIndexFromDate(self, tsd:TimeSeriesDatum)->QModelIndex:
            assert isinstance(tsd, TimeSeriesDatum)
    
            return self.createIndex(self.items.index(tsd),0)
    
    
        def getDateFromIndex(self, index:QModelIndex)->TimeSeriesDatum:
            assert isinstance(index, QModelIndex)
    
            if index.isValid():
                return self.items[index.row()]
            return None
    
    
        def getTimeSeriesDatumFromIndex(self, index:QModelIndex)->TimeSeriesDatum:
            assert isinstance(index, QModelIndex)
    
            if index.isValid():
                i = index.row()
                if i >= 0 and i < len(self.items):
                    return self.items[i]
    
            return None
    
    
        def columnCount(self, parent = QModelIndex())->int:
    
            return len(self.mColumnNames)
    
    
        def data(self, index, role = Qt.DisplayRole):
            if role is None or not index.isValid():
                return None
    
            value = None
    
            columnName = self.mColumnNames[index.column()]
    
    
            TSD = self.getTimeSeriesDatumFromIndex(index)
    
            assert isinstance(TSD, TimeSeriesDatum)
    
            keys = list(TSD.__dict__.keys())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            tssList = TSD.sources()
    
    
            if role == Qt.DisplayRole or role == Qt.ToolTipRole:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if columnName == self.cnSensor:
    
                    if role == Qt.ToolTipRole:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        value = TSD.sensor().description()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    else:
                        value = TSD.sensor().name()
    
                elif columnName == self.cnDate:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    value = '{}'.format(TSD.date())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                elif columnName == self.cnImages:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    value = '\n'.join(TSD.sourceUris())
    
                elif columnName == self.cnCRS:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    value = '\n'.join([tss.crs().description() for tss in tssList])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                elif columnName == self.cnNB:
                    value = TSD.sensor().nb
                elif columnName == self.cnNL:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    value = '\n'.join([str(tss.nl) for tss in tssList])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                elif columnName == self.cnNS:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    value = '\n'.join([str(tss.ns) for tss in tssList])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                elif columnName == self.cnSensor:
                    value = TSD.sensor().name()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
                elif columnName in keys:
                    value = TSD.__dict__[columnName]
                else:
                    s = ""
            elif role == Qt.CheckStateRole:
    
                if columnName == self.cnDate:
    
                    value = Qt.Checked if TSD.isVisible() else Qt.Unchecked
    
            elif role == Qt.BackgroundColorRole:
                value = None
            elif role == Qt.UserRole:
                value = TSD
    
            return value
    
        def setData(self, index, value, role=None):
            if role is None or not index.isValid():
                return None
    
            if role is Qt.UserRole:
    
                s = ""
    
    
            columnName = self.mColumnNames[index.column()]
    
    
            TSD = self.getTimeSeriesDatumFromIndex(index)
    
            if columnName == self.cnDate and role == Qt.CheckStateRole:
    
                TSD.setVisibility(value != Qt.Unchecked)
                return True
            else:
                return False
    
            return False
    
        def flags(self, index):
            if index.isValid():
    
                columnName = self.mColumnNames[index.column()]
    
                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
    
                if columnName == self.cnDate: # allow check state
    
                    flags = flags | Qt.ItemIsUserCheckable
    
                return flags
    
            return None
    
        def headerData(self, col, orientation, role):
            if Qt is None:
                return None
            if orientation == Qt.Horizontal and role == Qt.DisplayRole:
    
                return self.mColumnNames[col]
    
            elif orientation == Qt.Vertical and role == Qt.DisplayRole:
                return col
            return None
    
    def getSpatialPropertiesFromDataset(ds):
        assert isinstance(ds, gdal.Dataset)
    
        nb = ds.RasterCount
        nl = ds.RasterYSize
        ns = ds.RasterXSize
        proj = ds.GetGeoTransform()
        px_x = float(abs(proj[1]))
        px_y = float(abs(proj[5]))
    
        crs = QgsCoordinateReferenceSystem(ds.GetProjection())
    
        return nb, nl, ns, crs, px_x, px_y
    
    
    
    
    
    
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    def extractWavelengths(ds):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        # see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for supported wavelength units
    
        regWLkey = re.compile('.*wavelength[_ ]*$', re.I)
        regWLUkey = re.compile('.*wavelength[_ ]*units?$', re.I)
        regNumeric = re.compile(r"([-+]?\d*\.\d+|[-+]?\d+)", re.I)
        regWLU = re.compile('((micro|nano|centi)meters)|(um|nm|mm|cm|m|GHz|MHz)', re.I)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        if isinstance(ds, QgsRasterLayer):
            lyr = ds
            md = [l.split('=') for l in str(lyr.metadata()).splitlines() if 'wavelength' in l.lower()]
            #see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for supported wavelength units
            for kv in md:
                key, value = kv
                key = key.lower()
                if key == 'center wavelength':
                    tmp = re.findall(r'\d*\.\d+|\d+', value) #find floats
                    if len(tmp) == 0:
                        tmp = re.findall(r'\d+', value) #find integers
                    if len(tmp) == lyr.bandCount():
                        wl = [float(w) for w in tmp]
    
                if key == 'wavelength units':
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        wlu = match.group()
    
                    names = ['nanometers','micrometers','millimeters','centimeters','decimenters']
                    si   = ['nm','um','mm','cm','dm']
    
                    if wlu in names:
                        wlu = si[names.index(wlu)]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        elif isinstance(ds, gdal.Dataset):
    
            for domain in ds.GetMetadataDomainList():
                md = ds.GetMetadata_Dict(domain)
                for key, value in md.items():
                    if wl is None and regWLkey.search(key):
                        numbers = regNumeric.findall(value)
                        if len(numbers) == ds.RasterCount:
                            wl = [float(n) for n in numbers]
    
                    if wlu is None and regWLUkey.search(key):
                        match = regWLU.search(value)
                        if match:
                            wlu = match.group().lower()
                        names = ['nanometers', 'micrometers', 'millimeters', 'centimeters', 'decimeters']
                        si = ['nm', 'um', 'mm', 'cm', 'dm']
                        if wlu in names:
                            wlu = si[names.index(wlu)]
    
    
    class TimeSeriesDockUI(QgsDockWidget, loadUI('timeseriesdock.ui')):
        """
        QgsDockWidget that shows the TimeSeries
        """
        def __init__(self, parent=None):
            super(TimeSeriesDockUI, self).__init__(parent)
            self.setupUi(self)
    
            #self.progressBar.setMinimum(0)
            #self.setProgressInfo(0, 100, 'Add images to fill time series')
            #self.progressBar.setValue(0)
            #self.progressInfo.setText(None)
            self.frameFilters.setVisible(False)
    
            self.mTimeSeries = None
            self.mSelectionModel = None
    
    
        def initActions(self, parent):
    
            from eotimeseriesviewer.main import TimeSeriesViewerUI
            assert isinstance(parent, TimeSeriesViewerUI)
            self.btnAddTSD.setDefaultAction(parent.actionAddTSD)
            self.btnRemoveTSD.setDefaultAction(parent.actionRemoveTSD)
            self.btnLoadTS.setDefaultAction(parent.actionLoadTS)
            self.btnSaveTS.setDefaultAction(parent.actionSaveTS)
            self.btnClearTS.setDefaultAction(parent.actionClearTS)
    
    
        def showTSD(self, tsd:TimeSeriesDatum):
            assert isinstance(self.timeSeriesTreeView, TimeSeriesTreeView)
            assert isinstance(self.mTSProxyModel, QSortFilterProxyModel)
    
            tsd.setVisibility(True)
    
            assert isinstance(self.mTimeSeries, TimeSeries)
            idxSrc = self.mTimeSeries.tsdToIdx(tsd)
    
            if isinstance(idxSrc, QModelIndex):
                idx2 = self.mTSProxyModel.mapFromSource(idxSrc)
                if isinstance(idx2, QModelIndex):
                    self.timeSeriesTreeView.setCurrentIndex(idx2)
                    self.timeSeriesTreeView.scrollTo(idx2, QAbstractItemView.PositionAtCenter)
    
        def updateSummary(self):
    
    
            if isinstance(self.mTimeSeries, TimeSeries):
                if len(self.mTimeSeries) == 0:
                    info = 'Empty Timeseries. Please add source images.'
                else:
                    nDates = self.mTimeSeries.rowCount()
                    nSensors = len(self.mTimeSeries.sensors())
                    nImages = len(list(self.mTimeSeries.sources()))
    
                    info = '{} dates, {} sensors, {} source images'.format(nDates, nSensors, nImages)
            else:
                info = ''
            self.summary.setText(info)
    
        def onSelectionChanged(self, *args):
            """
            Slot to react on user-driven changes of the selected TimeSeriesDatum rows.
            """
    
            self.btnRemoveTSD.setEnabled(
                isinstance(self.mSelectionModel, QItemSelectionModel) and
                len(self.mSelectionModel.selectedRows()) > 0)
    
        def selectedTimeSeriesDates(self)->list:
            """
            Returns the TimeSeriesDatum selected by a user.
            :return: [list-of-TimeSeriesDatum]
            """
            if isinstance(self.mSelectionModel, QItemSelectionModel):
                return [self.mTSProxyModel.data(idx, Qt.UserRole) for idx in self.mSelectionModel.selectedRows()]
            return []
    
        def setTimeSeries(self, TS:TimeSeries):
            """
            Sets the TimeSeries to be shown in the TimeSeriesDockUI
            :param TS: TimeSeries
            """
            from eotimeseriesviewer.timeseries import TimeSeries
            if isinstance(TS, TimeSeries):
                self.mTimeSeries = TS
                self.mTSProxyModel = QSortFilterProxyModel(self)
                self.mTSProxyModel.setSourceModel(self.mTimeSeries)
                self.mSelectionModel = QItemSelectionModel(self.mTSProxyModel)
                self.mSelectionModel.selectionChanged.connect(self.onSelectionChanged)
    
    
                self.timeSeriesTreeView.setModel(self.mTSProxyModel)
                self.timeSeriesTreeView.setSelectionModel(self.mSelectionModel)
    
                for c in range(self.mTSProxyModel.columnCount()):
                    self.timeSeriesTreeView.header().setSectionResizeMode(c, QHeaderView.ResizeToContents)
                self.mTimeSeries.rowsInserted.connect(self.updateSummary)
                #self.mTimeSeries.dataChanged.connect(self.updateSummary)
                self.mTimeSeries.rowsRemoved.connect(self.updateSummary)
                #TS.sigLoadingProgress.connect(self.setProgressInfo)
    
            self.onSelectionChanged()
    
    
    
    
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    if __name__ == '__main__':
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        print(convertMetricUnit(100, 'cm', 'm'))
        print(convertMetricUnit(1, 'm', 'um'))