Newer
Older
# -*- coding: utf-8 -*-
"""
/***************************************************************************

Benjamin Jakimow
committed
EO Time Series Viewer
-------------------
begin : 2015-08-20
git sha : $Format:%H$
copyright : (C) 2017 by HU-Berlin
email : benjamin.jakimow@geo.hu-berlin.de
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
# noinspection PyPep8Naming
import sys, re, collections, traceback, time, json, urllib, types, enum, typing, pickle, json, uuid

Benjamin Jakimow
committed
import bisect
from qgis import *
from qgis.core import *
from qgis.gui import *

Benjamin Jakimow
committed
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *
from qgis.PyQt.QtCore import *

Benjamin Jakimow
committed
from osgeo import gdal
from eotimeseriesviewer.dateparser import DOYfromDatetime64
from eotimeseriesviewer.utils import SpatialExtent, loadUI, px2geo

benjamin.jakimow@geo.hu-berlin.de
committed
gdal.SetConfigOption('VRT_SHARED_SOURCE', '0') #!important. really. do not change this.
from eotimeseriesviewer import messageLog
from eotimeseriesviewer.dateparser import parseDateFromDataSet
def transformGeometry(geom, crsSrc, crsDst, trans=None):
if trans is None:
assert isinstance(crsSrc, QgsCoordinateReferenceSystem)
assert isinstance(crsDst, QgsCoordinateReferenceSystem)
return transformGeometry(geom, None, None, trans=QgsCoordinateTransform(crsSrc, crsDst))
else:
assert isinstance(trans, QgsCoordinateTransform)
return trans.transform(geom)
GDAL_DATATYPES = {}
for var in vars(gdal):
match = re.search(r'^GDT_(?P<type>.*)$', var)
if match:
number = getattr(gdal, var)
GDAL_DATATYPES[match.group('type')] = number
GDAL_DATATYPES[match.group()] = number
"nm": -9, "um": -6, "mm": -3, "cm": -2, "dm": -1, "m": 0, "hm": 2, "km": 3
}
#add synonyms
METRIC_EXPONENTS['nanometers'] = METRIC_EXPONENTS['nm']
METRIC_EXPONENTS['micrometers'] = METRIC_EXPONENTS['um']
METRIC_EXPONENTS['millimeters'] = METRIC_EXPONENTS['mm']
METRIC_EXPONENTS['centimeters'] = METRIC_EXPONENTS['cm']
METRIC_EXPONENTS['decimeters'] = METRIC_EXPONENTS['dm']
METRIC_EXPONENTS['meters'] = METRIC_EXPONENTS['m']
METRIC_EXPONENTS['hectometers'] = METRIC_EXPONENTS['hm']
METRIC_EXPONENTS['kilometers'] = METRIC_EXPONENTS['km']
def convertMetricUnit(value, u1, u2):
assert u1 in METRIC_EXPONENTS.keys()
assert u2 in METRIC_EXPONENTS.keys()
e1 = METRIC_EXPONENTS[u1]
e2 = METRIC_EXPONENTS[u2]
return value * 10**(e1-e2)
def getDS(pathOrDataset)->gdal.Dataset:
"""
Returns a gdal.Dataset
:param pathOrDataset: str | gdal.Dataset | QgsRasterLayer
:return:
"""
if isinstance(pathOrDataset, QgsRasterLayer):
return getDS(pathOrDataset.source())
elif isinstance(pathOrDataset, gdal.Dataset):
return pathOrDataset
ds = gdal.Open(pathOrDataset)
assert isinstance(ds, gdal.Dataset)
return ds
def sensorID(nb:int, px_size_x:float, px_size_y:float, dt:int, wl:list, wlu:str)->str:
"""
Create a sensor ID
:param nb: number of bands
:param px_size_x: pixel size x
:param px_size_y: pixel size y
:param wl: list of wavelength
:param wlu: str, wavelength unit
:return: str
"""
assert dt in GDAL_DATATYPES.values()
assert isinstance(nb, int) and nb > 0
assert isinstance(px_size_x, (int, float)) and px_size_x > 0
assert isinstance(px_size_y, (int, float)) and px_size_y > 0
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
if wl != None:
assert isinstance(wl, list)
assert len(wl) == nb
if wlu != None:
assert isinstance(wlu, str)
return json.dumps((nb, px_size_x, px_size_y, dt, wl, wlu))
def sensorIDtoProperties(idString:str)->tuple:
"""
Reads a sensor id string and returns the sensor properties. See sensorID().
:param idString: str
:return: (ns, px_size_x, px_size_y, [wl], wlu)
"""
nb, px_size_x, px_size_y, dt, wl, wlu = json.loads(idString)
assert isinstance(dt, int) and dt >= 0
assert isinstance(nb, int)
assert isinstance(px_size_x, (int,float)) and px_size_x > 0
assert isinstance(px_size_y, (int, float)) and px_size_y > 0
if wl != None:
assert isinstance(wl, list)
if wlu != None:
assert isinstance(wlu, str)
return nb, px_size_x, px_size_y, dt, wl, wlu
class SensorInstrument(QObject):
"""
Describes a Sensor Configuration
"""
SensorNameSettingsPrefix = 'SensorName.'
sigNameChanged = pyqtSignal(str)
LUT_Wavelengths = dict({'B': 480,
'G': 570,
'R': 660,
'nIR': 850,
'swIR': 1650,
'swIR1': 1650,
'swIR2': 2150
def __init__(self, sid:str, sensor_name:str=None, band_names:list = None):
super(SensorInstrument, self).__init__()
self.nb, self.px_size_x, self.px_size_y, self.dataType, self.wl, self.wlu = sensorIDtoProperties(self.mId)
if not isinstance(band_names, list):
band_names = ['Band {}'.format(b+1) for b in range(self.nb)]
assert len(band_names) == self.nb
self.bandNames = band_names
self.wlu = self.wlu
if self.wl is None:
self.wl = None
if sensor_name is None:
sensor_name = '{}bands@{}m'.format(self.nb, self.px_size_x)
import eotimeseriesviewer.settings
sensor_name = eotimeseriesviewer.settings.value(self._sensorSettingsKey(), sensor_name)
self.setName(sensor_name)
from eotimeseriesviewer.tests import TestObjects
import uuid
path = '/vsimem/mockupImage.{}.bsq'.format(uuid.uuid4())
self.mMockupDS = TestObjects.inMemoryImage(path=path, nb=self.nb, eType=self.dataType, ns=2, nl=2)
def bandIndexClosestToWavelength(self, wl, wl_unit='nm')->int:
"""
Returns the band index closets to a certain wavelength
:param wl: float | int
:param wl_unit: str
:return: int
"""
if self.wlu is None or self.wl is None:
return 0
from .utils import convertMetricUnit, LUT_WAVELENGTH
if isinstance(wl, str):
assert wl.upper() in LUT_WAVELENGTH.keys()
wl = LUT_WAVELENGTH[wl.upper()]
if self.wlu != wl_unit:
wl = convertMetricUnit(wl, wl_unit, self.wlu)
return int(np.argmin(np.abs(self.wl - wl)))
def proxyLayer(self)->QgsRasterLayer:
"""
Creates an "empty" layer that can be used as proxy for band names, data types and render styles
:return: QgsRasterLayer
"""
lyr = SensorProxyLayer(self.mMockupDS.GetFileList()[0], name=self.name(), sensor=self)
lyr.nameChanged.connect(lambda l=lyr: self.setName(l.name()))
lyr.setCustomProperty('eotsv/sensorid', self.id())
self.sigNameChanged.connect(lyr.setName)
return lyr
"""
Returns the Sensor id
:return: str
"""
def _sensorSettingsKey(self):
return SensorInstrument.SensorNameSettingsPrefix+self.mId
def setName(self, name: str):
import eotimeseriesviewer.settings
eotimeseriesviewer.settings.setValue(self._sensorSettingsKey(), name)

Benjamin Jakimow
committed
if not isinstance(other, SensorInstrument):
return False
return hash(self.id())
return str(self.__class__) +' ' + self.name()
"""
Returns a human-readable description
:return: str
"""
info.append(self.name())
info.append('{} Bands'.format(self.nb))
info.append('Band\tName\tWavelength')
for b in range(self.nb):
else:
wl = 'unknown'
info.append('{}\t{}\t{}'.format(b + 1, self.bandNames[b], wl))
return '\n'.join(info)
class SensorProxyLayer(QgsRasterLayer):
def __init__(self, *args, sensor:SensorInstrument, **kwds):
super(SensorProxyLayer, self).__init__(*args, **kwds)
self.mSensor = sensor
def sensor(self)->SensorInstrument:
"""
Returns the SensorInstrument this layer relates to
:return: SensorInstrument
"""
return self.mSensor
def verifyInputImage(datasource):
"""
Checks if an image source can be uses as TimeSeriesDate, i.e. if it can be read by gdal.Open() and
if we can extract an observation date as numpy.datetime64.
:param datasource: str with data source uri or gdal.Dataset
:return: bool
"""

benjamin.jakimow@geo.hu-berlin.de
committed
if isinstance(datasource, str):
datasource = gdal.Open(datasource)
if not isinstance(datasource, gdal.Dataset):

benjamin.jakimow@geo.hu-berlin.de
committed
if datasource.RasterCount == 0 and len(datasource.GetSubDatasets()) > 0:
#logger.error('Can not open container {}.\nPlease specify a subdataset'.format(path))
if datasource.GetDriver().ShortName == 'VRT':
files = datasource.GetFileList()
if len(files) > 0:
for f in files:
subDS = gdal.Open(f)
if not isinstance(subDS, gdal.Dataset):
return False
from eotimeseriesviewer.dateparser import parseDateFromDataSet
date = parseDateFromDataSet(datasource)

benjamin.jakimow@geo.hu-berlin.de
committed
return True
class TimeSeriesSource(object):
"""Provides some information on source images"""
@staticmethod
def fromJson(jsonData:str):
"""
Returs a TimeSeriesSource from its JSON representation
:param json:
:return:
"""
source = TimeSeriesSource(None)
state = json.loads(jsonData)
source.__setstatedictionary(state)
return source
Reads the argument and returns a TimeSeriesSource
:param source: gdal.Dataset, str, QgsRasterLayer
:return: TimeSeriesSource
ds = None
if isinstance(source, QgsRasterLayer):
lyr = source
provider = lyr.providerType()
if provider == 'gdal':
ds = gdal.Open(lyr.source())
elif provider == 'wcs':
parts = urllib.parse.parse_qs(lyr.source())
url = re.search(r'^[^?]+', parts['url'][0]).group()
identifier = re.search(r'^[^?]+', parts['identifier'][0]).group()
uri2 = 'WCS:{}?coverage={}'.format(url, identifier)
ds = gdal.Open(uri2)
if not isinstance(ds, gdal.Dataset) or ds.RasterCount == 0:
dsGetCoverage = gdal.Open('WCS:{}'.format(url))
for subdatasetUrl, id in dsGetCoverage.GetSubDatasets():
if id == identifier:
ds = gdal.Open(subdatasetUrl)
break
else:
raise Exception('Unsupported raster data provider: {}'.format(provider))
elif isinstance(source, str):
ds = gdal.Open(source)
elif isinstance(source, gdal.Dataset):
ds = source
else:
raise Exception('Unsupported source: {}'.format(source))
def __init__(self, dataset:gdal.Dataset=None):
self.mUri = None
self.mDrv = None
self.mGT = None
self.mWKT = None
self.mCRS = None
self.mWL = None
self.mWLU = None
self.nb = self.ns = self.nl = None
self.mGeoTransform = None
self.mGSD = None
self.mDataType = None
self.mSid = None
self.mMetaData = None
self.mUL = self.LR = None
self.mTimeSeriesDate = None
if isinstance(dataset, gdal.Dataset):
assert dataset.RasterCount > 0
assert dataset.RasterYSize > 0
assert dataset.RasterXSize > 0
#self.mUri = dataset.GetFileList()[0]
self.mUri = dataset.GetDescription()
self.mDate = parseDateFromDataSet(dataset)
assert self.mDate is not None, 'Unable to find acquisition date of {}'.format(self.mUri)
self.mDrv = dataset.GetDriver().ShortName
self.mWL, self.mWLU = extractWavelengths(dataset)
self.nb, self.nl, self.ns = dataset.RasterCount, dataset.RasterYSize, dataset.RasterXSize
self.mGeoTransform = dataset.GetGeoTransform()
self.mMetaData = collections.OrderedDict()
for domain in dataset.GetMetadataDomainList():
self.mMetaData[domain] = dataset.GetMetadata_Dict(domain)
self.mWKT = dataset.GetProjection()
if self.mWKT == '':
# no CRS? try with QGIS API
lyr = QgsRasterLayer(self.mUri)
if lyr.crs().isValid():
self.mWKT = lyr.crs().toWkt()
self.mCRS = QgsCoordinateReferenceSystem(self.mWKT)
px_x = float(abs(self.mGeoTransform[1]))
px_y = float(abs(self.mGeoTransform[5]))
self.mGSD = (px_x, px_y)
self.mDataType = dataset.GetRasterBand(1).DataType
self.mSid = sensorID(self.nb, px_x, px_y, self.mDataType, self.mWL, self.mWLU)
self.mUL = QgsPointXY(*px2geo(QPoint(0, 0), self.mGeoTransform, pxCenter=False))
self.mLR = QgsPointXY(*px2geo(QPoint(self.ns + 1, self.nl + 1), self.mGeoTransform, pxCenter=False))
def __reduce_ex__(self, protocol):
return self.__class__, (), self.__getstate__()
def __statedictionary(self):
"""
Returns the internal state as serializable dictionary.
:return: dict
"""
state = dict()
for name in dir(self):
if re.search('^(n|m).+', name):
value = getattr(self, name)
if isinstance(value, (str, int, float, dict, list, tuple)):
state[name] = value
elif isinstance(value, QgsPointXY):
state[name] = value.asWkt()
elif isinstance(value, np.datetime64):
state[name] = str(value)
elif name in ['mCRS', 'mTimeSeriesDate']:
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# will be derived from other variables
continue
elif callable(value):
continue
else:
s = ""
return state
def __setstatedictionary(self, state:dict):
assert isinstance(state, dict)
for k, v in state.items():
self.__dict__[k] = v
self.mCRS = QgsCoordinateReferenceSystem(self.mWKT)
assert self.mCRS.isValid()
self.mUL = QgsPointXY(QgsGeometry.fromWkt(self.mUL).asPoint())
self.mLR = QgsPointXY(QgsGeometry.fromWkt(self.mLR).asPoint())
self.mDate = np.datetime64(self.mDate)
def __getstate__(self):
dump = pickle.dumps(self.__statedictionary())
return dump
def __setstate__(self, state):
d = pickle.loads(state)
self.__setstatedictionary(d)
def json(self)->str:
"""
Returns a JSON representation
:return:
"""
return json.dumps(self.__statedictionary())
def name(self)->str:
"""
Returns a name for this data source
:return:
"""
bn = os.path.basename(self.uri())
return '{} {}'.format(bn, self.date())
def uri(self)->str:
"""
URI that can be used with GDAL to open a dataset
:return: str
"""
return self.mUri
def qgsMimeDataUtilsUri(self)->QgsMimeDataUtils.Uri:
uri = QgsMimeDataUtils.Uri()
uri.name = self.name()
uri.providerKey = 'gdal'
uri.uri = self.uri()
uri.layerType = 'raster'
return uri
def sid(self)->str:
"""
Returns the sensor id
:return: str
"""
return self.mSid
"""
Returns the parent TimeSeriesDate (if set)
:return: TimeSeriesDate
"""
return self.mTimeSeriesDate
def setTimeSeriesDate(self, tsd):
"""
Sets the parent TimeSeriesDate
:param tsd: TimeSeriesDate
"""
def date(self)->np.datetime64:
return self.mDate
def crs(self)->QgsCoordinateReferenceSystem:
return self.mCRS
def spatialExtent(self)->SpatialExtent:
return SpatialExtent(self.mCRS, self.mUL, self.mLR)
def __eq__(self, other):
if not isinstance(other, TimeSeriesSource):
return False
return self.mUri == other.mUri
class TimeSeriesDate(QAbstractTableModel):
"""
A containe to store all image source related to a single observation date and sensor.
"""
sigSourcesAdded = pyqtSignal(list)
sigSourcesRemoved = pyqtSignal(list)
cnUri = 'Source'
cnNS = 'ns'
cnNB = 'nb'
cnNL = 'nl'
cnCRS = 'crs'
ColumnNames = [cnNB, cnNL, cnNS, cnCRS, cnUri]
def __init__(self, timeSeries, date:np.datetime64, sensor:SensorInstrument):
"""
Constructor
:param timeSeries: TimeSeries, parent TimeSeries instance, optional
:param date: np.datetime64,
:param sensor: SensorInstrument
"""
super(TimeSeriesDate, self).__init__()
assert isinstance(date, np.datetime64)
assert isinstance(sensor, SensorInstrument)
self.mSensor = sensor
self.mDate = date
self.mDOY = DOYfromDatetime64(self.mDate)
self.mSources = []
self.mMasks = []
def removeSource(self, source:TimeSeriesSource):
if source in self.mSources:
i = self.mSources.index(source)
self.beginRemoveRows(QModelIndex(), i, i)
self.mSources.remove(source)
self.endRemoveRows()
self.sigSourcesRemoved.emit([source])
Adds an time series source to this TimeSeriesDate
:param path: TimeSeriesSource or any argument accepted by TimeSeriesSource.create()
:return: TimeSeriesSource, if added
"""
if not isinstance(source, TimeSeriesSource):
return self.addSource(TimeSeriesSource.create(source))
else:
assert isinstance(source, TimeSeriesSource)
source.setTimeSeriesDate(self)
i = len(self)
self.beginInsertRows(QModelIndex(), i, i)
self.endInsertRows()
self.sigSourcesAdded.emit([source])
return source
else:
return None
def setVisibility(self, b:bool):
Sets the visibility of the TimeSeriesDate, i.e. whether linked MapCanvases will be shown to the user
old = self.mVisibility
self.mVisibility = b
if old != self.mVisibility:
self.sigVisibilityChanged.emit(b)
def isVisible(self):
Returns whether the TimeSeriesDate is visible as MapCanvas
def sensor(self)->SensorInstrument:
"""
Returns the SensorInstrument
:return: SensorInsturment
"""
return self.mSensor
Returns the source images
:return: [list-of-TimeSeriesSource]
def sourceUris(self)->list:
"""
Returns all source URIs as list of strings-
:return: [list-of-str]
"""
return [tss.uri() for tss in self.sources()]
def qgsMimeDataUtilsUris(self)->list:
"""
Returns all source URIs as list of QgsMimeDataUtils.Uri
:return: [list-of-QgsMimedataUtils.Uris]
"""
return [s.qgsMimeDataUtilsUri() for s in self.sources()]
Returns the observation date
:return: numpy.datetime64

Benjamin Jakimow
committed
def decimalYear(self)->float:
"""
Returns the observation date as decimal year (year + doy / (366+1) )
:return: float
"""
return self.year() + self.doy() / (366+1)
def year(self)->int:
"""
Returns the observation year
:return: int
"""
return self.mDate.astype(object).year
def doy(self)->int:
"""
Returns the day of Year (DOY)
:return: int
"""
return int(self.mDOY)
def hasIntersectingSource(self, spatialExtent:SpatialExtent):
for source in self:
assert isinstance(source, TimeSeriesSource)
ext = source.spatialExtent()
if isinstance(ext, SpatialExtent):
ext = ext.toCrs(spatialExtent.crs())
if spatialExtent.intersects(ext):
return True
return False
ext = None
for i, tss in enumerate(self.sources()):
assert isinstance(tss, TimeSeriesSource)
if i == 0:
ext = tss.spatialExtent()
else:
ext.combineExtentWith(tss.spatialExtent())
return ext
def imageBorders(self)->QgsGeometry:
"""
Retunrs the exact border polygon
:return: QgsGeometry
"""
return None
def __repr__(self)->str:
"""
String representation
:return:
"""
return 'TimeSeriesDate({},{})'.format(str(self.mDate), str(self.mSensor))
Tow TimeSeriesDate instances are equal if they have the same date, sensor and sources.
:param other: TimeSeriesDate
if not isinstance(other, TimeSeriesDate):
return False
return self.id() == other.id() and self.mSources == other.mSources
def __contains__(self, item):
return item in self.mSources
def __getitem__(self, slice):
return self.mSources[slice]
def __iter__(self):
"""
Iterator over all sources
"""
return iter(self.mSources)
def __len__(self)->int:
"""
Returns the number of source images.
:return: int
"""
:param other: TimeSeriesDate
assert isinstance(other, TimeSeriesDate)

benjamin.jakimow@geo.hu-berlin.de
committed
return False
def rowCount(self, parent: QModelIndex = QModelIndex()):
return len(self)
def columnCount(self, parent: QModelIndex):
return len(TimeSeriesDate.ColumnNames)
def flags(self, index: QModelIndex):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
def headerData(self, section, orientation, role):
assert isinstance(section, int)
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return TimeSeriesDate.ColumnNames[section]
else:
return None
def data(self, index: QModelIndex, role: int ):
if not index.isValid():
return None
tss = self.mSources[index.row()]
assert isinstance(tss, TimeSeriesSource)
cn = TimeSeriesDate.ColumnNames[index.column()]
if role == Qt.UserRole:
return tss
if role == Qt.DisplayRole:
if cn == TimeSeriesDate.cnNB:
return tss.nb
if cn == TimeSeriesDate.cnNS:
return tss.ns
if cn == TimeSeriesDate.cnNL:
return tss.nl
if cn == TimeSeriesDate.cnCRS:
return tss.crs().description()
if cn == TimeSeriesDate.cnUri:
return tss.uri()
return None
def mimeDataUris(self)->list:
"""
Returns the sources of this TSD as list of QgsMimeDataUtils.Uris
:return: [list-of-QgsMimeDataUtils]
"""
results = []
for tss in self.sources():
assert isinstance(tss, TimeSeriesSource)
[tss.uri() for tss in self.sources()]
def __hash__(self):
class TimeSeriesTreeView(QTreeView):
sigMoveToDateRequest = pyqtSignal(TimeSeriesDate)
super(TimeSeriesTreeView, self).__init__(parent)
def contextMenuEvent(self, event: QContextMenuEvent):
Creates and shows the QMenu
:param event: QContextMenuEvent
idx = self.indexAt(event.pos())
tsd = self.model().data(idx, role=Qt.UserRole)
if isinstance(tsd, TimeSeriesDate):
a = menu.addAction('Show map for {}'.format(tsd.date()))
a.setToolTip('Shows the map related to this time series date.')
a.triggered.connect(lambda _, tsd=tsd: self.sigMoveToDateRequest.emit(tsd))
menu.addSeparator()

benjamin.jakimow@geo.hu-berlin.de
committed
a = menu.addAction('Copy value(s)')
a = menu.addAction('Hide date(s)')
a.setToolTip('Hides the selected time series dates.')
a.triggered.connect(lambda: self.onSetCheckState(Qt.Checked))
a = menu.addAction('Show date(s)')
a.setToolTip('Shows the selected time series dates.')

benjamin.jakimow@geo.hu-berlin.de
committed
a.triggered.connect(lambda: self.onSetCheckState(Qt.Unchecked))

benjamin.jakimow@geo.hu-berlin.de
committed
def onSetCheckState(self, checkState):
"""
Sets a ChecState to all selected rows
:param checkState: Qt.CheckState
"""

benjamin.jakimow@geo.hu-berlin.de
committed
indices = self.selectionModel().selectedIndexes()
rows = sorted(list(set([i.row() for i in indices])))
model = self.model()
if isinstance(model, QSortFilterProxyModel):

benjamin.jakimow@geo.hu-berlin.de
committed
for r in rows:

benjamin.jakimow@geo.hu-berlin.de
committed
model.setData(idx, checkState, Qt.CheckStateRole)
"""
Copies selected cell values to the clipboard
"""

benjamin.jakimow@geo.hu-berlin.de
committed
indices = self.selectionModel().selectedIndexes()
model = self.model()

benjamin.jakimow@geo.hu-berlin.de
committed
from collections import OrderedDict
R = OrderedDict()
for idx in indices:
if not idx.row() in R.keys():
R[idx.row()] = []
R[idx.row()].append(model.data(idx, Qt.DisplayRole))
info = []
for k, values in R.items():

benjamin.jakimow@geo.hu-berlin.de
committed
info = '\n'.join(info)
QApplication.clipboard().setText(info)

benjamin.jakimow@geo.hu-berlin.de
committed

Benjamin Jakimow
committed
class DateTimePrecision(enum.Enum):
"""
Describes the precision to pares DateTimeStamps.
"""
Year = 'Y'
Month = 'M'
Week = 'W'
Day = 'D'
Hour = 'h'
Minute = 'm'
Second = 's'
Milisecond = 'ms'
Original = 0
def doLoadTimeSeriesSourcesTask(taskWrapper:QgsTask, dump):
sources = pickle.loads(dump)
assert isinstance(taskWrapper, QgsTask)
results = []
n = len(sources)
for i, source in enumerate(sources):
if taskWrapper.isCanceled():
return pickle.dumps(results)
s = TimeSeriesSource.create(source)
if isinstance(s, TimeSeriesSource):
results.append(s)
taskWrapper.setProgress(float(i+1) / n * 100.0)
return pickle.dumps(results)
class TimeSeries(QAbstractItemModel):
"""
The sorted list of data sources that specify the time series
"""
sigTimeSeriesDatesAdded = pyqtSignal(list)
sigTimeSeriesDatesRemoved = pyqtSignal(list)
#sigLoadingProgress = pyqtSignal(int, int, str)
sigSensorAdded = pyqtSignal(SensorInstrument)
sigSensorRemoved = pyqtSignal(SensorInstrument)
sigSourcesAdded = pyqtSignal(list)
sigSourcesRemoved = pyqtSignal(list)

Benjamin Jakimow
committed
_sep = ';'
def __init__(self, imageFiles=None):
super(TimeSeries, self).__init__()
self.mTSDs = list()
self.mSensors = []
self.mShape = None

Benjamin Jakimow
committed
self.mDateTimePrecision = DateTimePrecision.Original
self.mLoadingProgressDialog = None
self.mCurrentDates = []
self.cnDate = 'Date'