diff --git a/timeseriesviewer/main.py b/timeseriesviewer/main.py index abe58628baa04472a1545bf8a5b3961f950015f0..c009cfbc10cee498884fee771346f70e54c7af63 100644 --- a/timeseriesviewer/main.py +++ b/timeseriesviewer/main.py @@ -59,6 +59,9 @@ import codecs from timeseriesviewer import jp, mkdir, DIR_SITE_PACKAGES, file_search site.addsitedir(DIR_SITE_PACKAGES) +from timeseriesviewer.ui import widgets +from timeseriesviewer.timeseries import TimeSeries, TimeSeriesDatum, SensorInstrument + #I don't know why, but this is required to run this in QGIS #todo: still required? @@ -70,9 +73,6 @@ if os.path.exists(path): #ensure that required non-standard modules are available import pyqtgraph as pg -from timeseriesviewer.ui import widgets - -regLandsatSceneID = re.compile(r"L[EMCT][1234578]{1}[12]\d{12}[a-zA-Z]{3}\d{2}") class TimeSeriesTableModel(QAbstractTableModel): @@ -109,8 +109,7 @@ class TimeSeriesTableModel(QAbstractTableModel): if index.isValid(): i = index.row() if i >= 0 and i < len(self.TS): - date = self.TS.getTSDs()[i] - return self.TS.data[date] + return self.TS.data[i] return None @@ -218,14 +217,6 @@ class TimeSeriesItemModel(QAbstractItemModel): def columnCount(self, index=QModelIndex()): return 1 -LUT_SENSORNAMES = {(6, 30., 30.): 'Landsat Legacy' \ - , (7,30.,30.): 'L8 OLI' \ - , (4,10.,10.): 'S2 MSI 10m' \ - , (6,20.,20.): 'S2 MSI 20m' \ - , (3,30.,30.): 'S2 MSI 60m' \ - , (3,30.,30.): 'S2 MSI 60m' \ - , (5,5.,5.): 'RE 5m' \ - } class BandView(QObject): @@ -243,7 +234,7 @@ class BandView(QObject): import copy for sensor in self.Sensors: - self.initSensor(copy.deepcopy(sensor), recommended_bands=recommended_bands) + self.initSensor(copy.deepcopy(sensor)) @@ -269,16 +260,7 @@ class BandView(QObject): assert type(sensor) is SensorInstrument if sensor not in self.representation.keys(): - #self.bandMappings[sensor] = ((0, 0, 5000), (1, 0, 5000), (2, 0, 5000)) - #x = imagechipviewsettings_widget.ImageChipViewSettings(sensor) - #x = tsv_widgets.BandViewSettings(sensor) x = widgets.ImageChipViewSettings(sensor) - #x.removeView.connect(lambda : self.removeView.emit(self)) - - if recommended_bands is not None: - assert min(recommended_bands) > 0 - if max(recommended_bands) < sensor.nb: - x.setBands(recommended_bands) x.create() self.representation[sensor] = x @@ -317,93 +299,6 @@ class BandView(QObject): return False -class SensorInstrument(object): - - INSTRUMENTS = dict() - - @staticmethod - def fromRasterLayer(lyr): - - assert isinstance(lyr, QgsRasterLayer) - nb = lyr.bandCount() - sx = lyr.rasterUnitsPerPixelX() - sy = lyr.rasterUnitsPerPixelY() - - bandNames = [lyr.bandName(i) for i in range(1, nb+1)] - - return SensorInstrument(nb, sx, sy, - band_names=bandNames, - wavelengths=None, - sensor_name=None) - - def fromGDALDataSet(self, ds): - assert isinstance(ds, gdal.Dataset) - nb = ds.RasterCount - - - """ - Describes a Sensor Configuration - """ - def __init__(self,nb, px_size_x,px_size_y, band_names=None, wavelengths=None, sensor_name=None): - assert nb >= 1 - - self.TS = None - self.nb = nb - self.px_size_x = float(abs(px_size_x)) - self.px_size_y = float(abs(px_size_y)) - - assert self.px_size_x > 0 - assert self.px_size_y > 0 - - if band_names is not None: - assert len(band_names) == nb - else: - band_names = ['Band {}'.format(b+1) for b in range(nb)] - - self.band_names = band_names - - if wavelengths is not None: - assert len(wavelengths) == nb - - self.wavelengths = wavelengths - - if sensor_name is None: - id = (self.nb, self.px_size_x, self.px_size_y) - if id in LUT_SENSORNAMES.keys(): - sensor_name = LUT_SENSORNAMES[id] - else: - sensor_name = '{}band@{}m'.format(self.nb, self.px_size_x) - - - self.sensor_name = sensor_name - - self.hashvalue = hash(','.join(self.band_names)) - - def __eq__(self, other): - return self.nb == other.nb and \ - self.px_size_x == other.px_size_x and \ - self.px_size_y == other.px_size_y - - def __hash__(self): - return self.hashvalue - - def __repr__(self): - return self.sensor_name - - def getDescription(self): - info = [] - info.append(self.sensor_name) - info.append('{} Bands'.format(self.nb)) - info.append('Band\tName\tWavelength') - for b in range(self.nb): - if self.wavelengths: - wl = str(self.wavelengths[b]) - else: - wl = 'unknown' - info.append('{}\t{}\t{}'.format(b+1, self.band_names[b],wl)) - - return '\n'.join(info) - class ImageChipLabel(QLabel): @@ -451,353 +346,6 @@ class ImageChipLabel(QLabel): -class TimeSeries(QObject): - datumAdded = pyqtSignal(name='datumAdded') - - #fire when a new sensor configuration is added - sensorAdded = pyqtSignal(object, name='sensorAdded') - - - changed = pyqtSignal() - progress = pyqtSignal(int,int,int, name='progress') - chipLoaded = pyqtSignal(object, name='chiploaded') - closed = pyqtSignal() - error = pyqtSignal(object) - - def __init__(self, imageFiles=None, maskFiles=None): - QObject.__init__(self) - - #define signals - - #fire when a new TSD is added - - - self.data = collections.OrderedDict() - - self.CHIP_BUFFER=dict() - - self.shape = None - - self.Sensors = collections.OrderedDict() - - self.Pool = None - - if imageFiles is not None: - self.addFiles(imageFiles) - if maskFiles is not None: - self.addMasks(maskFiles) - - _sep = ';' - - - def loadFromFile(self, path): - - images = [] - masks = [] - with open(path, 'r') as f: - lines = f.readlines() - for l in lines: - if re.match('^[ ]*[;#&]', l): - continue - - parts = re.split('[\n'+TimeSeries._sep+']', l) - parts = [p for p in parts if p != ''] - images.append(parts[0]) - if len(parts) > 1: - masks.append(parts[1]) - - self.addFiles(images) - self.addMasks(masks) - - - def saveToFile(self, path): - if path is None or len(path) == 0: - return - - lines = [] - lines.append('#Time series definition file: {}'.format(np.datetime64('now').astype(str))) - lines.append('#<image path>[;<mask path>]') - for TSD in self.data.values(): - - line = TSD.pathImg - if TSD.pathMsk is not None: - line += TimeSeries._sep + TSD.pathMsk - - lines.append(line) - - lines = [l+'\n' for l in lines] - - print('Write {}'.format(path)) - with open(path, 'w') as f: - f.writelines(lines) - - - def getSRS(self): - if len(self.data) == 0: - return 0 - else: - TSD = self.data[self.getTSDs()[0]] - return TSD.getSpatialReference() - - def getWKT(self): - srs = self.getSRS() - return srs.ExportToWkt() - - def getSceneCenter(self, srs=None): - - if srs is None: - srs = self.getSRS() - - bbs = list() - for S, TSDs in self.Sensors.items(): - x = [] - y = [] - for TSD in TSDs: - bb = TSD.getBoundingBox(srs) - x.extend([c[0] for c in bb]) - y.extend([c[1] for c in bb]) - - return None - pass - - def getMaxExtent(self, srs=None): - - x = [] - y = [] - - if srs is None: - srs = self.getSRS() - - for TSD in self.data.values(): - bb = TSD.getBoundingBox(srs) - x.extend([c[0] for c in bb]) - y.extend([c[1] for c in bb]) - - return (min(x), min(y), max(x), max(y)) - - def getObservationDates(self): - return [tsd.getDate() for tsd in self.data.keys()] - - def getTSDs(self, date_of_interest=None): - if date_of_interest: - tsds = [tsd for tsd in self.data.keys() if tsd.getDate() == date_of_interest] - else: - tsds = list(self.data.keys()) - return tsds - - def _callback_error(self, error): - six.print_(error, file=sys.stderr) - self.error.emit(error) - self._callback_progress() - - def _callback_spatialchips(self, results): - self.chipLoaded.emit(results) - self._callback_progress() - - def _callback_progress(self): - self._callback_progress_done += 1 - self.progress.emit(0, self._callback_progress_done, self._callback_progress_max) - - if self._callback_progress_done >= self._callback_progress_max: - self._callback_progress_done = 0 - self._callback_progress_max = 0 - self.progress.emit(0,0,1) - - def getSpatialChips_parallel(self, bbWkt, srsWkt, TSD_band_list, ncpu=1): - assert type(bbWkt) is str and type(srsWkt) is str - - import multiprocessing - if self.Pool is not None: - self.Pool.terminate() - - self.Pool = multiprocessing.Pool(processes=ncpu) - - - self._callback_progress_max = len(TSD_band_list) - self._callback_progress_done = 0 - - for T in TSD_band_list: - - TSD = copy.deepcopy(T[0]) - bands = T[1] - #TSD = pickle.dumps(self.data[date]) - args = (TSD, bbWkt, srsWkt) - kwds = {'bands':bands} - - if six.PY3: - self.Pool.apply_async(PFunc_TimeSeries_getSpatialChip, \ - args=args, kwds=kwds, \ - callback=self._callback_spatialchips, error_callback=self._callback_error) - else: - self.Pool.apply_async(PFunc_TimeSeries_getSpatialChip, \ - args, kwds, self._callback_spatialchips) - - s = "" - - pass - - def getImageChips(self, xy, size=50, bands=[4,5,6], dates=None): - chipCollection = collections.OrderedDict() - - if dates is None: - dates = self.data.keys() - for date in dates: - TSD = self.data[date] - chipCollection[date] = TSD.readImageChip(xy, size=size, bands=bands) - - return chipCollection - - def addMasks(self, files, raise_errors=True, mask_value=0, exclude_mask_value=True): - assert isinstance(files, list) - l = len(files) - - self.progress.emit(0,0,l) - for i, file in enumerate(files): - - try: - self.addMask(file, raise_errors=raise_errors, mask_value=mask_value, exclude_mask_value=exclude_mask_value, _quiet=True) - except: - pass - - self.progress.emit(0,i+1,l) - - self.progress.emit(0,0,1) - self.changed.emit() - - def addMask(self, pathMsk, raise_errors=True, mask_value=0, exclude_mask_value=True, _quiet=False): - print('Add mask {}...'.format(pathMsk)) - ds = getDS(pathMsk) - date = getImageDate(ds) - - if date in self.data.keys(): - TSD = self.data[date] - - if not _quiet: - self.changed.emit() - - return TSD.setMask(pathMsk, raise_errors=raise_errors, mask_value=mask_value, exclude_mask_value=exclude_mask_value) - else: - info = 'TimeSeries does not contain date {} {}'.format(date, pathMsk) - if raise_errors: - raise Exception(info) - else: - six.print_(info, file=sys.stderr) - return False - - def removeAll(self): - self.clear() - - def clear(self): - self.Sensors.clear() - self.data.clear() - self.changed.emit() - - - def removeDates(self, TSDs): - for TSD in TSDs: - self.removeTSD(TSD, _quiet=True) - self.changed.emit() - - def removeTSD(self, TSD, _quiet=False): - - assert type(TSD) is TimeSeriesDatum - S = TSD.sensor - self.Sensors[S].remove(TSD) - self.data.pop(TSD, None) - if len(self.Sensors[S]) == 0: - self.Sensors.pop(S) - if not _quiet: - self.changed.emit() - - - def addFile(self, pathImg, pathMsk=None, _quiet=False): - - six.print_(pathImg) - six.print_('Add image {}...'.format(pathImg)) - - try: - sensorAdded = False - TSD = TimeSeriesDatum(pathImg, pathMsk=pathMsk) - existingSensors = list(self.Sensors.keys()) - if TSD.sensor not in existingSensors: - self.Sensors[TSD.sensor] = list() - sensorAdded = True - else: - TSD.sensor = existingSensors[existingSensors.index(TSD.sensor)] - - if TSD in self.data.keys(): - six.print_('Time series datum already added: {}'.format(str(TSD)), file=sys.stderr) - else: - self.Sensors[TSD.sensor].append(TSD) - self.data[TSD] = TSD - - if sensorAdded: - self.sensorAdded.emit(TSD.sensor) - - if not _quiet: - self._sortTimeSeriesData() - self.changed.emit() - self.datumAdded.emit() - except: - exc_type, exc_value, exc_traceback = sys.exc_info() - traceback.print_exception(exc_type, exc_value, exc_traceback, limit=2) - six.print_('Unable to add {}'.format(file), file=sys.stderr) - pass - - - - def addFiles(self, files): - assert isinstance(files, list) - l = len(files) - assert l > 0 - - self.progress.emit(0,0,l) - for i, file in enumerate(files): - self.addFile(file, _quiet=True) - self.progress.emit(0,i+1,l) - - self._sortTimeSeriesData() - self.progress.emit(0,0,1) - self.datumAdded.emit() - self.changed.emit() - - - - def _sortTimeSeriesData(self): - self.data = collections.OrderedDict(sorted(self.data.items(), key=lambda t:t[0])) - - def __len__(self): - return len(self.data) - - def __repr__(self): - info = [] - info.append('TimeSeries:') - l = len(self) - info.append(' Scenes: {}'.format(l)) - - if l > 0: - keys = list(self.data.keys()) - info.append(' Range: {} to {}'.format(keys[0], keys[-1])) - return '\n'.join(info) - -def PFunc_TimeSeries_getSpatialChip(TSD, bbWkt, srsWkt , bands=[4,5,3]): - - chipdata = TSD.readSpatialChip(bbWkt, srs=srsWkt, bands=bands) - - return TSD, chipdata - -def px2Coordinate(gt, pxX, pxY, upper_left=True): - cx = gt[0] + pxX*gt[1] + pxY*gt[2] - cy = gt[3] + pxX*gt[4] + pxY*gt[5] - if not upper_left: - cx += gt[1]*0.5 - cy += gt[5]*0.5 - return cx, cy - -def coordinate2px(gt, cx, cy): - px = int((cx - gt[0]) / gt[1]) - py = int((cy - gt[3]) / gt[5]) - return px, py def getBoundingBoxPolygon(points, srs=None): @@ -806,21 +354,15 @@ def getBoundingBoxPolygon(points, srs=None): ring.AddPoint(point[0], point[1]) bb = ogr.Geometry(ogr.wkbPolygon) bb.AddGeometry(ring) - if srs: - bb.AssignSpatialReference(srs) + if isinstance(srs, QgsCoordinateReferenceSystem): + + _crs = osr.SpatialReference() + _crs.ImportFromWkt(srs.toWkt()) + bb.AssignSpatialReference(_crs) return bb -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) - @@ -829,25 +371,7 @@ def getDS(ds): ds = gdal.Open(ds) return ds -regAcqDate = re.compile(r'acquisition[ ]*(time|date|day)', re.I) -def getImageDate2(lyr): - assert isinstance(lyr, QgsRasterLayer) - mdLines = str(lyr.metadata()).splitlines() - date = None - #find date in metadata - for line in [l for l in mdLines if regAcqDate.search(l)]: - date = parseAcquisitionDate(line) - if date: - return date - #find date in filename - dn, fn = os.path.split(str(lyr.dataProvider().dataSourceUri())) - date = parseAcquisitionDate(fn) - if date: return date - - #find date in file directory path - date = parseAcquisitionDate(date) - return date def getBandNames(lyr): assert isinstance(lyr, QgsRasterLayer) @@ -878,239 +402,6 @@ def getImageDate(ds): raise Exception('Can not identify acquisition date of {}'.format(path)) -class TimeSeriesDatum(object): - """ - Collects all data sets related to one sensor - """ - - def __init__(self, pathImg, pathMsk=None): - self.pathImg = pathImg - self.pathMsk = None - - self.lyrImg = QgsRasterLayer(pathImg, os.path.basename(pathImg), False) - self.crs = self.lyrImg.dataProvider().crs() - self.sensor = SensorInstrument.fromRasterLayer(self.lyrImg) - - self.date = getImageDate2(self.lyrImg) - assert self.date is not None, 'Unable to find acquisition date of {}'.format(pathImg) - - self.ns = self.lyrImg.width() - self.nl = self.lyrImg.height() - self.nb = self.lyrImg.bandCount() - self.srs_wkt = str(self.crs.toWkt()) - - - if pathMsk: - self.setMask(pathMsk) - - def getdtype(self): - return gdal_array.GDALTypeCodeToNumericTypeCode(self.etype) - - def getDate(self): - return np.datetime64(self.date) - - - def getSpatialReference(self): - return self.crs - srs = osr.SpatialReference() - srs.ImportFromWkt(self.srs_wkt) - return srs - - - def getBoundingBox(self, srs=None): - - bbox = self.lyrImg.extent() - if srs: - assert isinstance(srs, QgsCoordinateReferenceSystem) - bbox = transformGeometry(bbox, self.crs, srs) - return bbox - - - def setMask(self, pathMsk, raise_errors=True, mask_value=0, exclude_mask_value=True): - dsMsk = gdal.Open(pathMsk) - mskDate = getImageDate(dsMsk) - - - errors = list() - if mskDate and mskDate != self.getDate(): - errors.append('Mask date differs from image date') - if self.ns != dsMsk.RasterXSize or self.nl != dsMsk.RasterYSize: - errors.append('Spatial size differs') - if dsMsk.RasterCount != 1: - errors.append('Mask has > 1 bands') - - projImg = self.getSpatialReference() - projMsk = osr.SpatialReference() - projMsk.ImportFromWkt(dsMsk.GetProjection()) - - if not projImg.IsSame(projMsk): - errors.append('Spatial Reference differs from image') - if self.gt != list(dsMsk.GetGeoTransform()): - errors.append('Geotransformation differs from image') - - if len(errors) > 0: - errors.insert(0, 'pathImg:{} \npathMsk:{}'.format(self.pathImg, pathMsk)) - errors = '\n'.join(errors) - if raise_errors: - raise Exception(errors) - else: - six.print_(errors, file=sys.stderr) - return False - else: - self.pathMsk = pathMsk - self.mask_value = mask_value - self.exclude_mask_value = exclude_mask_value - - return True - - def readSpatialChip(self, geometry, srs=None, bands=[4,5,3]): - - srs_img = osr.SpatialReference() - srs_img.ImportFromWkt(self.srs_wkt) - - - if type(geometry) is ogr.Geometry: - g_bb = geometry - srs_bb = g_bb.GetSpatialReference() - else: - assert srs is not None and type(srs) in [str, osr.SpatialReference] - if type(srs) is str: - srs_bb = osr.SpatialReference() - srs_bb.ImportFromWkt(srs) - else: - srs_bb = srs.Clone() - g_bb = ogr.CreateGeometryFromWkt(geometry, srs_bb) - - assert srs_bb is not None and g_bb is not None - assert g_bb.GetGeometryName() == 'POLYGON' - - if not srs_img.IsSame(srs_bb): - g_bb = g_bb.Clone() - g_bb.TransformTo(srs_img) - - cx0,cx1,cy0,cy1 = g_bb.GetEnvelope() - - ul_px = coordinate2px(self.gt, min([cx0, cx1]), max([cy0, cy1])) - lr_px = coordinate2px(self.gt, max([cx0, cx1]), min([cy0, cy1])) - lr_px = [c+1 for c in lr_px] #+1 - - return self.readImageChip([ul_px[0], lr_px[0]], [ul_px[1], lr_px[1]], bands=bands) - - def readImageChip(self, px_x, px_y, bands=[4,5,3]): - - ds = gdal.Open(self.pathImg, gdal.GA_ReadOnly) - - assert len(px_x) == 2 and px_x[0] <= px_x[1] - assert len(px_y) == 2 and px_y[0] <= px_y[1] - - ns = px_x[1]-px_x[0]+1 - nl = px_y[1]-px_y[0]+1 - assert ns >= 0 - assert nl >= 0 - - src_ns = ds.RasterXSize - src_nl = ds.RasterYSize - - - chipdata = dict() - - - - #pixel indices in source image - x0 = max([0, px_x[0]]) - y0 = max([0, px_y[0]]) - x1 = min([src_ns, px_x[1]]) - y1 = min([src_nl, px_y[1]]) - win_xsize = x1-x0+1 - win_ysize = y1-y0+1 - - #pixel indices in image chip (ideally 0 and ns-1 or nl-1) - i0 = x0 - px_x[0] - i1 = i0 + win_xsize - - j0 = y0 - px_y[0] - j1 = j0+ win_ysize - - - - - templateImg = np.zeros((nl,ns)) - if self.nodata: - templateImg *= self.nodata - - templateImg = templateImg.astype(self.getdtype()) - templateMsk = np.ones((nl,ns), dtype='bool') - - if win_xsize < 1 or win_ysize < 1: - six.print_('Selected image chip is out of raster image {}'.format(self.pathImg), file=sys.stderr) - for i, b in enumerate(bands): - chipdata[b] = np.copy(templateImg) - - else: - for i, b in enumerate(bands): - band = ds.GetRasterBand(b) - data = np.copy(templateImg) - data[j0:j1,i0:i1] = band.ReadAsArray(xoff=x0, yoff=y0, win_xsize=win_xsize,win_ysize=win_ysize) - chipdata[b] = data - nodatavalue = band.GetNoDataValue() - if nodatavalue is not None: - templateMsk[j0:j1,i0:i1] = np.logical_and(templateMsk[j0:j1,i0:i1], data[j0:j1,i0:i1] != nodatavalue) - - if self.pathMsk: - ds = gdal.Open(self.pathMsk) - tmp = ds.GetRasterBand(1).ReadAsArray(xoff=x0, yoff=y0, \ - win_xsize=win_xsize,win_ysize=win_ysize) == 0 - - templateMsk[j0:j1,i0:i1] = np.logical_and(templateMsk[j0:j1,i0:i1], tmp) - - chipdata['mask'] = templateMsk - return chipdata - - def __repr__(self): - return 'TS Datum {} {}'.format(self.date, str(self.sensor)) - - def __cmp__(self, other): - return cmp(str((self.date, self.sensor)), str((other.date, other.sensor))) - - def __eq__(self, other): - return self.date == other.date and self.sensor == other.sensor - - def __hash__(self): - return hash((self.date,self.sensor.sensor_name)) - - -regYYYYDOY = re.compile(r'(19|20)\d{5}') -regYYYYMMDD = re.compile(r'(19|20)\d{2}-\d{2}-\d{2}') -regYYYY = re.compile(r'(19|20)\d{2}') - -def parseAcquisitionDate(text): - match = regLandsatSceneID.search(text) - if match: - id = match.group() - return getDateTime64FromYYYYDOY(id[9:16]) - match = regYYYYMMDD.search(text) - if match: - return np.datetime64(match.group()) - match = regYYYYDOY.search(text) - if match: - return getDateTime64FromYYYYDOY(match.group()) - match = regYYYY.search(text) - if match: - return np.datetime64(match.group()) - return None - - - -def getDateTime64FromYYYYDOY(yyyydoy): - return getDateTime64FromDOY(yyyydoy[0:4], yyyydoy[4:7]) - -def getDateTime64FromDOY(year, doy): - if type(year) is str: - year = int(year) - if type(doy) is str: - doy = int(doy) - return np.datetime64('{:04d}-01-01'.format(year)) + np.timedelta64(doy-1, 'D') - def getChip3d(chips, rgb_idx, ranges): assert len(rgb_idx) == 3 and len(rgb_idx) == len(ranges) @@ -1138,6 +429,27 @@ def Array2Image(d3d): return QImage(d3d.data, ns, nl, QImage.Format_RGB888) + +class LayerLoader(QRunnable): + + def __init__(self, paths): + super(LayerLoader, self).__init__() + self.signals = LoaderSignals() + + self.paths = paths + + def run(self): + lyrs = [] + for path in self.paths: + lyr = QgsRasterLayer(path) + if lyr: + lyrs.append(lyr) + self.signals.sigRasterLayerLoaded.emit(lyr) + print('{} loaded'.format(path)) + else: + print('Failed to load {}'.format(path)) + self.signals.sigFinished.emit() + class VerticalLabel(QLabel): def __init__(self, text): super(self.__class__, self).__init__() @@ -1341,7 +653,7 @@ class TimeSeriesViewer: D.btn_removeTSD.clicked.connect(lambda : self.ua_removeTSD(None)) D.btn_removeTS.clicked.connect(self.ua_clear_TS) - + D.sliderDOI.sliderMoved.connect(self.setDOILabel) D.spinBox_ncpu.setRange(0, multiprocessing.cpu_count()) @@ -1366,6 +678,11 @@ class TimeSeriesViewer: self.check_enabled() s = "" + def setDOILabel(self, i): + TSD = self.TS.data[i-1] + self.dlg.labelDOI.setText(str(TSD.date)) + s = "" + @staticmethod def icon(): return QIcon(':/plugins/SenseCarbon/icon.png') @@ -1383,9 +700,11 @@ class TimeSeriesViewer: disconnect_signal(self.TS.datumAdded) disconnect_signal(self.TS.progress) disconnect_signal(self.TS.chipLoaded) + disconnect_signal(self.TS.changed) self.TS = TS self.TS.datumAdded.connect(self.ua_datumAdded) + self.TS.changed.connect(self.timeseriesChanged) self.TS.progress.connect(self.ua_TSprogress) self.TS.chipLoaded.connect(self.ua_showPxCoordinate_addChips) @@ -1393,10 +712,15 @@ class TimeSeriesViewer: D = self.dlg D.tableView_TimeSeries.setModel(TSM) D.tableView_TimeSeries.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents) - D.cb_doi.setModel(TSM) - D.cb_doi.setModelColumn(0) - D.cb_doi.currentIndexChanged.connect(self.scrollToDate) + #D.cb_doi.setModel(TSM) + #D.cb_doi.setModelColumn(0) + #D.cb_doi.currentIndexChanged.connect(self.scrollToDate) + def timeseriesChanged(self): + D = self.dlg + D.sliderDOI.setMinimum(1) + D.sliderDOI.setMaximum(len(self.TS.data)) + s = "" def ua_loadTSFile(self, path=None): if path is None or path is False: @@ -1422,12 +746,11 @@ class TimeSeriesViewer: def ua_loadExampleTS(self): - from timeseriesviewer import DIR_EXAMPLE - path_example = jp(DIR_EXAMPLE, 'testdata.txt') - if not os.path.exists(path_example): + from timeseriesviewer import PATH_EXAMPLE_TIMESERIES + if not os.path.exists(PATH_EXAMPLE_TIMESERIES): QMessageBox.information(self.dlg, 'File not found', '{} - this file describes an exemplary time series.'.format(path_example)) else: - self.ua_loadTSFile(path=path_example) + self.ua_loadTSFile(path=PATH_EXAMPLE_TIMESERIES) @@ -1440,12 +763,10 @@ class TimeSeriesViewer: self.canvas.setMapTool(self.PointMapTool) def setCanvasSRS(self,srs): - if type(srs) is osr.SpatialReference: - self.canvas_srs = srs - else: - self.canvas_srs.ImportFromWkt(srs) + assert isinstance(srs, QgsCoordinateReferenceSystem) + self.canvas_srs = srs - self.dlg.tb_bb_srs.setPlainText(self.canvas_srs.ExportToProj4()) + self.dlg.tb_bb_srs.setPlainText(self.canvas_srs.toWkt()) def ua_selectBy_Response(self, geometry, srs_wkt): D = self.dlg @@ -1512,14 +833,17 @@ class TimeSeriesViewer: self.setCanvasSRS(self.TS.getSRS()) if self.dlg.spinBox_coordinate_x.value() == 0.0 and \ self.dlg.spinBox_coordinate_y.value() == 0.0: - xmin, ymin, xmax, ymax = self.TS.getMaxExtent(srs=self.canvas_srs) - self.dlg.spinBox_coordinate_x.setRange(xmin, xmax) - self.dlg.spinBox_coordinate_y.setRange(ymin, ymax) + bbox = self.TS.getMaxExtent(srs=self.canvas_srs) + + self.dlg.spinBox_coordinate_x.setRange(bbox.xMinimum(), bbox.xMaximum()) + self.dlg.spinBox_coordinate_y.setRange(bbox.yMinimum(), bbox.yMaximum()) #x, y = self.TS.getSceneCenter() - self.dlg.spinBox_coordinate_x.setValue(0.5*(xmin+xmax)) - self.dlg.spinBox_coordinate_y.setValue(0.5*(ymin+ymax)) + c = bbox.center() + self.dlg.spinBox_coordinate_x.setValue(c.x()) + self.dlg.spinBox_coordinate_y.setValue(c.y()) s = "" - self.dlg.cb_doi.setCurrentIndex(int(len(self.TS) / 2)) + #self.dlg.sliderDOI + self.dlg.tableView_TimeSeries.resizeColumnsToContents() def check_enabled(self): @@ -1628,11 +952,8 @@ class TimeSeriesViewer: #get the dates of interes dates_of_interest = list() - doiTSD = D.cb_doi.itemData(D.cb_doi.currentIndex()) - if doiTSD is None: - idx = int(len(self.TS)/2) - doiTSD = D.cb_doi.itemData(idx) - D.cb_doi.setCurrentIndex(idx) + + doiTSD = self.TS.data[int(D.sliderDOI.value())-1] centerDate = doiTSD.getDate() allDates = self.TS.getObservationDates() i_doi = allDates.index(centerDate)