diff --git a/sandbox/__init__.py b/sandbox/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/sandbox/sandbox.py b/sandbox/sandbox.py deleted file mode 100644 index 39effc710a4476cd5395c9a467162dae8e6114fb..0000000000000000000000000000000000000000 --- a/sandbox/sandbox.py +++ /dev/null @@ -1,197 +0,0 @@ -from __future__ import absolute_import -import six, sys, os, gc, re, collections, site, inspect, time -from osgeo import gdal, ogr - -from qgis import * -from qgis.core import * -from qgis.gui import * -from PyQt4.QtGui import * -from PyQt4.QtCore import * - -#from timeseriesviewer import DIR_EXAMPLES, jp, dprint -DIR_SANDBOX = os.path.dirname(__file__) - -from itertools import izip_longest - -def grouper(iterable, n, fillvalue=None): - args = [iter(iterable)] * n - return izip_longest(*args, fillvalue=fillvalue) - - - -class HiddenMapCanvas(QgsMapCanvas): - - sigPixmapCreated = pyqtSignal(QgsRasterLayer, QPixmap) - - - def __init__(self, *args, **kwds): - super(HiddenMapCanvas,self).__init__(*args, **kwds) - self.reg = QgsMapLayerRegistry.instance() - self.painter = QPainter() - self.layerQueue = list() - self.mapCanvasRefreshed.connect(self.createPixmap) - - def isBusy(self): - return len(self.layerQueue) != 0 - - def createPixmap(self, *args): - assert len(self.layerQueue) > 0 - - pixmap = QPixmap(self.size()) - self.painter.begin(pixmap) - self.map().paint(self.painter) - self.painter.end() - assert not pixmap.isNull() - lyr = self.layerQueue.pop(0) - - assert lyr.extent().intersects(self.extent()) - self.sigPixmapCreated.emit(lyr, pixmap) - self.startSingleLayerRendering() - - - def startLayerRendering(self, layers): - assert isinstance(layers, list) - self.layerQueue.extend(layers) - self.startSingleLayerRendering() - - - - def startSingleLayerRendering(self): - - if len(self.layerQueue) > 0: - mapLayer = self.layerQueue[0] - self.reg.addMapLayer(mapLayer) - lyrSet = [QgsMapCanvasLayer(mapLayer)] - self.setLayerSet(lyrSet) - - #todo: add crosshair - self.refreshAllLayers() - - - -n_PX = 0 -def newPixmap(layer, pixmap): - global n_PX - pathPNG = jp(DIR_SANDBOX, 'mapimage{}.png'.format(n_PX)) - n_PX += 1 - # .saveAsImage(pathPNG) - # pm = C.layerToPixmap(lyrRef, QSize(600,600)) - print('Write ' + pathPNG) - pixmap.toImage().save(pathPNG) - - s = "" - - -if __name__ == '__main__': - import site, sys - #add site-packages to sys.path as done by enmapboxplugin.py - - import timeseriesviewer.main - - s = "" - from timeseriesviewer import DIR_SITE_PACKAGES - site.addsitedir(DIR_SITE_PACKAGES) - - #prepare QGIS environment - if sys.platform == 'darwin': - PATH_QGS = r'/Applications/QGIS.app/Contents/MacOS' - #os.environ['GDAL_DATA'] = r'/usr/local/Cellar/gdal/1.11.3_1/share' - else: - # assume OSGeo4W startup - PATH_QGS = os.environ['QGIS_PREFIX_PATH'] - assert os.path.exists(PATH_QGS) - - qgsApp = QgsApplication([], True) - QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns') - QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns/qgis') - qgsApp.setPrefixPath(PATH_QGS, True) - qgsApp.initQgis() - - from timeseriesviewer import file_search, PATH_EXAMPLE_TIMESERIES - from timeseriesviewer.timeseries import TimeSeries, TimeSeriesDatum - pathTestTS = jp(DIR_EXAMPLES, 'ExampleTimeSeries.csv') - TS = TimeSeries() - if False or not os.path.exists(pathTestTS): - paths = file_search(jp(DIR_EXAMPLES, 'Images'), '*.bsq') - - TS.addFiles(paths) - TS.saveToFile(PATH_EXAMPLE_TIMESERIES) - else: - TS.loadFromFile(PATH_EXAMPLE_TIMESERIES) - - - - - - C = HiddenMapCanvas() - C.setAutoFillBackground(True) - C.setCanvasColor(Qt.green) - #C.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - - #l = QHBoxLayout() - #l.addWidget(C) - #w.layout().addWidget(C) - #w.show() - - - #C.setFixedSize(QSize(300,400)) - lyrRef = TS.data[24].lyrImg - layers = [TS.data[i].lyrImg for i in [23,12,16]] - QgsMapLayerRegistry.instance().addMapLayer(lyrRef, False) - C.setDestinationCrs(lyrRef.crs()) - C.setExtent(lyrRef.extent()) - C.setFixedSize(QSize(600, 600)) - C.sigPixmapCreated.connect(newPixmap) - - - C.startLayerRendering(layers) - #w.show() - qgsApp.exec_() - - pathPNG = jp(DIR_SANDBOX, 'mapimage.png') - #.saveAsImage(pathPNG) - #pm = C.layerToPixmap(lyrRef, QSize(600,600)) - #pm.toImage().save(pathPNG) - - - #qgsApp.exitQgis() - s = "" - - if False: - drvMEM = gdal.GetDriverByName('MEM') - ds = gdal.Open(TS.data[0].pathImg) - ds = drvMEM.CreateCopy('',ds) - - lyr = QgsRasterLayer(paths[0]) - finalLayerList = [] - def callback(result): - assert isinstance(result, QgsRasterLayer) - print(result) - finalLayerList.append(result) - s = "" - cnt = 0 - def callbackFin(): - cnt-=1 - - #run - #LL = LayerLoaderR(paths) - #LL.signales.sigLayerLoaded.connect(callback) - #r = LL.run() - - import numpy as np - #pool = QThreadPool() - pool = QThreadPool.globalInstance() - pool.setMaxThreadCount(4) - for files in grouper(paths, 3): - - LL = TSDLoader([f for f in files if f is not None]) - cnt += 1 - LL.signals.sigRasterLayerLoaded.connect(callback) - LL.signals.sigFinished.connect(callbackFin) - pool.start(LL) - - t0 = np.datetime64('now') - pool.waitForDone() - - - s = "" \ No newline at end of file diff --git a/timeseriesviewer/__init__.py b/timeseriesviewer/__init__.py index a5e88c502fdc9fc919a2c18b8be77d4c8854f952..29f56bf2fbb87f6c618d385f07a7f6a3387419c6 100644 --- a/timeseriesviewer/__init__.py +++ b/timeseriesviewer/__init__.py @@ -1,7 +1,30 @@ import os, sys, fnmatch, site -import six +import six, logging +logger = logging.getLogger(__name__) + from PyQt4.QtCore import QSettings from PyQt4.QtGui import QIcon + +DEBUG = True + +#initiate loggers for all pyfiles +import pkgutil +DIR = os.path.dirname(__file__) +names = [] +for m, name, ispkg in pkgutil.walk_packages(path=__file__, prefix='timeseriesviewer.'): + if name not in names: + names.append(name) + +for name in names: + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + fh = logging.StreamHandler() + fh_formatter = logging.Formatter('%(levelname)s %(lineno)d:%(filename)s%(module)s %(funcName)s \n\t%(message)s') + fh.setFormatter(fh_formatter) + fh.addFilter(logging.Filter(name)) + logger.addHandler(fh) + + jp = os.path.join dn = os.path.dirname mkdir = lambda p: os.makedirs(p, exist_ok=True) @@ -35,10 +58,6 @@ def icon(): return QIcon(':/timeseriesviewer/icons/icon.png') -def dprint(text, file=None): - if DEBUG: - six._print('DEBUG::{}'.format(text), file=file) - def file_search(rootdir, wildcard, recursive=False, ignoreCase=False): assert rootdir is not None @@ -58,11 +77,14 @@ def file_search(rootdir, wildcard, recursive=False, ignoreCase=False): return results -def findAbsolutePath(file): - if os.path.exists(file): return file - possibleRoots = [DIR_EXAMPLES, DIR_REPO, os.getcwd()] - for root in possibleRoots: - tmp = jp(root, file) - if os.path.exists(tmp): - return tmp - return None \ No newline at end of file + +def getFileAndAttributes(file): + """ + splits a GDAL valid file path into + :param file: + :return: + """ + dn = os.path.dirname(file) + bn = os.path.basename(file) + bnSplit = bn.split(':') + return os.path.join(dn,bnSplit[0]), ':'.join(bnSplit[1:]) \ No newline at end of file diff --git a/timeseriesviewer/crosshair.py b/timeseriesviewer/crosshair.py new file mode 100644 index 0000000000000000000000000000000000000000..c13e11c034bdcca22626224931b489fc2c964653 --- /dev/null +++ b/timeseriesviewer/crosshair.py @@ -0,0 +1,433 @@ + +import os + +from qgis.core import * +from qgis.gui import * +from PyQt4.QtCore import * +from PyQt4.QtGui import * +import numpy as np +from timeseriesviewer import * +from timeseriesviewer.utils import * + +from timeseriesviewer.ui.widgets import loadUIFormClass + +load = lambda p : loadUIFormClass(jp(DIR_UI,p)) + +class CrosshairStyle(object): + def __init__(self, **kwds): + + self.mColor = QColor.fromRgb(255,0,0, 125) + self.mThickness = 1 #in px + self.mSize = 1.0 #normalized + self.mGap = 0.05 #normalized + self.mShowDot = True + self.mDotSize = 1 #in px + self.mSizePixelBorder = 1 + self.mShow = True + self.mShowPixelBorder = True + + + def setColor(self, color): + assert isinstance(color, QColor) + self.mColor = color + + def setSize(self, size): + self.mSize = self._normalize(size) + + def setDotSize(self, size): + assert size >= 0 + self.mDotSize = size + + def setThickness(self, size): + """ + Crosshair thickness in px + :param size: + :return: + """ + assert size >= 0 + self.mThickness = size + + def setShowPixelBorder(self, b): + assert isinstance(b, bool) + self.mShowPixelBorder = b + + def setGap(self, gapSize): + """ + Set gap size in % [0, 100] or normalized coordinates [0,1] + :param gapSize: + :return: + """ + self.mGap = self._normalize(gapSize) + + def _normalize(self, size): + assert size >= 0 and size <= 100 + size = float(size) + if size > 1: + size /= 100 + return size + + def setShowDot(self, b): + assert isinstance(b, bool) + self.mShowDot = b + + def setShow(self, b): + assert isinstance(b, bool) + self.mShow = b + + def rendererV2(self): + """ + Returns the vector layer renderer + :return: + """ + registry = QgsSymbolLayerV2Registry.instance() + lineMeta = registry.symbolLayerMetadata("SimpleLine") + lineLayer = lineMeta.createSymbolLayer({}) + lineLayer.setColor(self.mColor) + lineLayer.setPenStyle(Qt.SolidLine) + + lineLayer.setWidth(self.mThickness) + lineLayer.setWidthUnit(2) #pixel + #lineLayer.setWidth(self.mThickness) + + """ + lineLayer = lineMeta.createSymbolLayer( + {'width': '0.26', + 'color': self.mColor, + 'offset': '0', + 'penstyle': 'solid', + 'use_custom_dash': '0'}) + """ + + # Replace the default layer with our custom layer + + symbol = QgsLineSymbolV2([]) + symbol.deleteSymbolLayer(0) + symbol.appendSymbolLayer(lineLayer) + return QgsSingleSymbolRendererV2(symbol) + +class CrosshairMapCanvasItem(QgsMapCanvasItem): + + def __init__(self, mapCanvas): + assert isinstance(mapCanvas, QgsMapCanvas) + super(CrosshairMapCanvasItem, self).__init__(mapCanvas) + + self.canvas = mapCanvas + self.rasterGridLayer = None + self.sizePixelBox = 0 + self.sizePixelBox = 1 + self.mShow = True + self.crosshairStyle = CrosshairStyle() + self.crosshairStyle.setShow(False) + self.setCrosshairStyle(self.crosshairStyle) + + def setShow(self, b): + assert isinstance(b, bool) + old = self.mShow + self.mShow = b + self.crosshairStyle.setShow(b) + if old != b: + self.canvas.update() + + + def connectRasterGrid(self, qgsRasterLayer): + + if isinstance(qgsRasterLayer): + self.rasterGridLayer = qgsRasterLayer + else: + self.rasterGridLayer = None + + def setPixelBox(self, nPx): + assert nPx >= 0 + assert nPx == 1 or nPx % 3 == 0, 'Size of pixel box must be an odd integer' + self.sizePixelBox = nPx + + + def setCrosshairStyle(self, crosshairStyle): + assert isinstance(crosshairStyle, CrosshairStyle) + self.crosshairStyle = crosshairStyle + + #apply style + self.canvas.update() + #self.updateCanvas() + + def paint(self, painter, QStyleOptionGraphicsItem=None, QWidget_widget=None): + if self.mShow and self.crosshairStyle.mShow: + #paint the crosshair + size = self.canvas.size() + centerGeo = self.canvas.center() + centerPx = self.toCanvasCoordinates(self.canvas.center()) + + x0 = centerPx.x() * (1.0 - self.crosshairStyle.mSize) + y0 = centerPx.y() * (1.0 - self.crosshairStyle.mSize) + x1 = size.width() - x0 + y1 = size.height() - y0 + gap = min([centerPx.x(), centerPx.y()]) * self.crosshairStyle.mGap + + #this is what we want to draw + lines = [] + polygons = [] + + lines.append(QLineF(x0, centerPx.y(), centerPx.x() - gap, centerPx.y())) + lines.append(QLineF(x1, centerPx.y(), centerPx.x() + gap, centerPx.y())) + lines.append(QLineF(centerPx.x(), y0, centerPx.x(), centerPx.y() - gap)) + lines.append(QLineF(centerPx.x(), y1, centerPx.x(), centerPx.y() + gap)) + + if self.crosshairStyle.mShowDot: + p = QRectF() + + d = int(round(max([1,self.crosshairStyle.mDotSize * 0.5]))) + + p.setTopLeft(QPointF(centerPx.x() - d, + centerPx.y() + d)) + p.setBottomRight(QPointF(centerPx.x() + d, + centerPx.y() - d)) + + p = QPolygonF(p) + polygons.append(p) + + if self.crosshairStyle.mShowPixelBorder: + rasterLayers = [l for l in self.canvas.layers() if isinstance(l, QgsRasterLayer) + and l.isValid()] + + if len(rasterLayers) > 0: + + lyr = rasterLayers[0] + + ns = lyr.width() # ns = number of samples = number of image columns + nl = lyr.height() # nl = number of lines + ex = lyr.extent() + xres = lyr.rasterUnitsPerPixelX() + yres = lyr.rasterUnitsPerPixelY() + + ms = self.canvas.mapSettings() + centerPxLyr = ms.mapToLayerCoordinates(lyr, centerGeo) + + + m2p = self.canvas.mapSettings().mapToPixel() + #get center pixel pixel index + pxX = int(np.floor((centerPxLyr.x() - ex.xMinimum()) / xres).astype(int)) + pxY = int(np.floor((ex.yMaximum() - centerPxLyr.y()) / yres).astype(int)) + + + def px2LayerGeo(x, y): + x2 = ex.xMinimum() + (x * xres) + y2 = ex.yMaximum() - (y * yres) + return QgsPoint(x2,y2) + lyrCoord2CanvasPx = lambda x, y, : self.toCanvasCoordinates( + ms.layerToMapCoordinates(lyr, + px2LayerGeo(x, y))) + if pxX >= 0 and pxY >= 0 and \ + pxX < ns and pxY < nl: + #get pixel edges in map canvas coordinates + + lyrGeo = px2LayerGeo(pxX, pxY) + mapGeo = ms.layerToMapCoordinates(lyr, lyrGeo) + canCor = self.toCanvasCoordinates(mapGeo) + + ul = lyrCoord2CanvasPx(pxX, pxY) + ur = lyrCoord2CanvasPx(pxX+1, pxY) + lr = lyrCoord2CanvasPx(pxX+1, pxY+1) + ll = lyrCoord2CanvasPx(pxX, pxY+1) + + pixelBorder = QPolygonF() + pixelBorder.append(ul) + pixelBorder.append(ur) + pixelBorder.append(lr) + pixelBorder.append(ll) + pixelBorder.append(ul) + + pen = QPen(Qt.SolidLine) + pen.setWidth(self.crosshairStyle.mSizePixelBorder) + pen.setColor(self.crosshairStyle.mColor) + pen.setBrush(self.crosshairStyle.mColor) + brush = QBrush(Qt.NoBrush) + brush.setColor(self.crosshairStyle.mColor) + painter.setBrush(brush) + painter.setPen(pen) + painter.drawPolygon(pixelBorder) + + pen = QPen(Qt.SolidLine) + pen.setWidth(self.crosshairStyle.mThickness) + pen.setColor(self.crosshairStyle.mColor) + pen.setBrush(self.crosshairStyle.mColor) + brush = QBrush(Qt.NoBrush) + brush.setColor(self.crosshairStyle.mColor) + painter.setBrush(brush) + painter.setPen(pen) + for p in polygons: + painter.drawPolygon(p) + for p in lines: + painter.drawLine(p) + + + + + +class CrosshairWidget(QWidget, load('crosshairwidget.ui')): + sigCrosshairStyleChanged = pyqtSignal(CrosshairStyle) + + def __init__(self, title='<#>', parent=None): + super(CrosshairWidget, self).__init__(parent) + self.setupUi(self) + + #self.crossHairReferenceLayer = CrosshairLayer() + #self.crossHairReferenceLayer.connectCanvas(self.crossHairCanvas) + + self.mapCanvas.setExtent(QgsRectangle(0, 0, 1, 1)) # + #QgsMapLayerRegistry.instance().addMapLayer(self.crossHairReferenceLayer) + #self.crossHairCanvas.setLayerSet([QgsMapCanvasLayer(self.crossHairReferenceLayer)]) + + #crs = QgsCoordinateReferenceSystem('EPSG:25832') + #self.crossHairCanvas.mapSettings().setDestinationCrs(crs) + + self.mapCanvasItem = CrosshairMapCanvasItem(self.mapCanvas) + + self.btnCrosshairColor.colorChanged.connect(self.refreshCrosshairPreview) + self.spinBoxCrosshairAlpha.valueChanged.connect(self.refreshCrosshairPreview) + self.spinBoxCrosshairThickness.valueChanged.connect(self.refreshCrosshairPreview) + self.spinBoxCrosshairSize.valueChanged.connect(self.refreshCrosshairPreview) + self.spinBoxCrosshairGap.valueChanged.connect(self.refreshCrosshairPreview) + self.spinBoxDotSize.valueChanged.connect(self.refreshCrosshairPreview) + self.cbCrosshairShowDot.toggled.connect(self.refreshCrosshairPreview) + self.cbShowPixelBoundaries.toggled.connect(self.refreshCrosshairPreview) + self.refreshCrosshairPreview() + + + + def setCanvasColor(self, color): + self.mapCanvas.setBackgroundColor(color) + self.btnMapCanvasColor.colorChanged.connect(self.onMapCanvasColorChanged) + + def onMapCanvasColorChanged(self, color): + self.sigMapCanvasColorChanged.emit(color) + self.refreshCrosshairPreview() + + def mapCanvasColor(self): + return self.btnMapCanvasColor.color() + + def refreshCrosshairPreview(self, *args): + style = self.crosshairStyle() + self.mapCanvasItem.setCrosshairStyle(style) + #self.crossHairReferenceLayer.setCrosshairStyle(style) + #self.crossHairCanvas.refreshAllLayers() + self.sigCrosshairStyleChanged.emit(style) + + def setCrosshairStyle(self, style): + assert isinstance(style, CrosshairStyle) + self.btnCrosshairColor.setColor(style.mColor) + self.spinBoxCrosshairAlpha.setValue(style.mColor.alpha()) + self.spinBoxCrosshairThickness.setValue(style.mThickness) + self.spinBoxCrosshairSize.setValue(int(style.mSize*100)) + self.spinBoxCrosshairGap.setValue(int(style.mGap*100)) + self.spinBoxDotSize.setValue(style.mDotSize) + self.cbCrosshairShowDot.setChecked(style.mShowDot) + self.cbShowPixelBoundaries.setChecked(style.mShowPixelBorder) + + def crosshairStyle(self): + style = CrosshairStyle() + c = self.btnCrosshairColor.color() + c.setAlpha(self.spinBoxCrosshairAlpha.value()) + style.setColor(c) + style.setThickness(self.spinBoxCrosshairThickness.value()) + style.setSize(self.spinBoxCrosshairSize.value()) + style.setGap(self.spinBoxCrosshairGap.value()) + style.setDotSize(self.spinBoxDotSize.value()) + style.setShowDot(self.cbCrosshairShowDot.isChecked()) + style.setShowPixelBorder(self.cbShowPixelBoundaries.isChecked()) + return style + +class CrosshairDialog(QgsDialog): + + @staticmethod + def getCrosshairStyle(*args, **kwds): + """ + Opens a CrosshairDialog. + :param args: + :param kwds: + :return: specified CrosshairStyle if accepted, else None + """ + d = CrosshairDialog(*args, **kwds) + d.exec_() + + if d.result() == QDialog.Accepted: + return d.crosshairStyle() + else: + + return None + + def __init__(self, parent=None, crosshairStyle=None, mapCanvas=None, title='Specify Crosshair'): + super(CrosshairDialog, self).__init__(parent=parent , \ + buttons=QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.w = CrosshairWidget(parent=self) + self.setWindowTitle(title) + self.btOk = QPushButton('Ok') + self.btCancel = QPushButton('Cance') + buttonBar = QHBoxLayout() + #buttonBar.addWidget(self.btCancel) + #buttonBar.addWidget(self.btOk) + l = self.layout() + l.addWidget(self.w) + l.addLayout(buttonBar) + #self.setLayout(l) + + if isinstance(mapCanvas, QgsMapCanvas): + self.setMapCanvas(mapCanvas) + + if isinstance(crosshairStyle, CrosshairStyle): + self.setCrosshairStyle(crosshairStyle) + s = "" + + def crosshairStyle(self): + return self.w.crosshairStyle() + + def setCrosshairStyle(self, crosshairStyle): + assert isinstance(crosshairStyle, CrosshairStyle) + self.w.setCrosshairStyle(crosshairStyle) + + def setMapCanvas(self, mapCanvas): + assert isinstance(mapCanvas, QgsMapCanvas) + # copy layers + canvas = self.w.mapCanvasItem.canvas + lyrs = [] + for lyr in mapCanvas.layers(): + s = "" + lyrs = mapCanvas.layers() + canvas.setLayerSet([QgsMapCanvasLayer(l) for l in lyrs]) + canvas.mapSettings().setDestinationCrs(mapCanvas.mapSettings().destinationCrs()) + canvas.setExtent(mapCanvas.extent()) + canvas.setCenter(mapCanvas.center()) + canvas.setCanvasColor(mapCanvas.canvasColor()) + canvas.refresh() + canvas.updateMap() + canvas.refreshAllLayers() + +if __name__ == '__main__': + import site, sys + #add site-packages to sys.path as done by enmapboxplugin.py + + from timeseriesviewer import sandbox + qgsApp = sandbox.initQgisEnvironment() + + if False: + c = QgsMapCanvas() + c.setExtent(QgsRectangle(0,0,1,1)) + i = CrosshairMapCanvasItem(c) + i.setShow(True) + s = CrosshairStyle() + s.setShow(True) + i.setCrosshairStyle(s) + c.show() + + + import example.Images + lyr = QgsRasterLayer(example.Images.Img_2012_05_09_LE72270652012130EDC00_BOA) + QgsMapLayerRegistry.instance().addMapLayer(lyr) + refCanvas = QgsMapCanvas() + refCanvas.setLayerSet([QgsMapCanvasLayer(lyr)]) + refCanvas.setExtent(lyr.extent()) + refCanvas.show() + + style = CrosshairDialog.getCrosshairStyle(mapCanvas=refCanvas) + + qgsApp.exec_() + qgsApp.exitQgis() diff --git a/timeseriesviewer/dateparser.py b/timeseriesviewer/dateparser.py index c6cda9fb29294b781697da59a1a86ab80306a0fb..2a73f0ee2b6f804e745b7ee53322f30787f039cb 100644 --- a/timeseriesviewer/dateparser.py +++ b/timeseriesviewer/dateparser.py @@ -67,18 +67,21 @@ class ImageDateParser(object): class ImageDateParserGeneric(ImageDateParser): def __init__(self, dataSet): super(ImageDateParserGeneric, self).__init__(dataSet) - self.regDateKeys = re.compile('(acquisition[ ]*time|date)') + self.regDateKeys = re.compile('(acquisition[ ]*time|datetime)', re.IGNORECASE) def parseDate(self): # search metadata for datetime information # see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for datetime format + dtg = None for domain in self.dataSet.GetMetadataDomainList(): md = self.dataSet.GetMetadata_Dict(domain) for key, value in md.items(): if self.regDateKeys.search(key): - dtg = extractDateTimeGroup(regISODate, value) - if dtg: + try: + dtg = np.datetime64(value) return dtg + except: + pass # search for ISO date in basename # search in basename @@ -90,22 +93,56 @@ class ImageDateParserGeneric(ImageDateParser): return dtg +class ImageDateParserPLEIADES(ImageDateParser): + def __init__(self, dataSet): + super(ImageDateParserPLEIADES, self).__init__(dataSet) + + def parseDate(self): + timeStamp = '' + ext = self.extension.lower() + + if ext == '.xml': + md = self.dataSet.GetMetadata_Dict() + if 'IMAGING_DATE' in md.keys() and 'IMAGING_TIME' in md.keys(): + timeStamp = '{}T{}'.format(md.get('IMAGING_DATE', ''), + md.get('IMAGING_TIME', '')) + elif ext == '.jp2': + timeStamp = self.dataSet.GetMetadataItem('ACQUISITIONDATETIME', 'IMAGERY') + if len(timeStamp) > 0: + return np.datetime64(timeStamp) + return None + + +class ImageDateParserSentinel2(ImageDateParser): + def __init__(self, dataSet): + super(ImageDateParserSentinel2, self).__init__(dataSet) + + def parseDate(self): + timeStamp = '' + ext = self.extension.lower() + + if ext == '.xml': + md = self.dataSet.GetMetadata_Dict() + timeStamp = md.get('DATATAKE_1_DATATAKE_SENSING_START', '') + if len(timeStamp) > 0: + return np.datetime64(timeStamp) + return None -class ImageDataParserLandsat(ImageDateParser): +class ImageDateParserLandsat(ImageDateParser): #see https://landsat.usgs.gov/what-are-naming-conventions-landsat-scene-identifiers regLandsatSceneID = re.compile(r'L[COTEM][4578]\d{3}\d{3}\d{4}\d{3}[A-Z]{2}[A-Z1]\d{2}') regLandsatProductID = re.compile(r'L[COTEM]0[78]_(L1TP|L1GT|L1GS)_\d{3}\d{3}_\d{4}\d{2}\d{2}_\d{4}\d{2}\d{2}_0\d{1}_(RT|T1|T2)') def __init__(self, dataSet): - super(ImageDataParserLandsat, self).__init__(dataSet) + super(ImageDateParserLandsat, self).__init__(dataSet) def parseDate(self): #search for LandsatSceneID (old) and Landsat Product IDs (new) - sceneID = matchOrNone(ImageDataParserLandsat.regLandsatSceneID, self.baseName) + sceneID = matchOrNone(ImageDateParserLandsat.regLandsatSceneID, self.baseName) if sceneID: return getDateTime64FromYYYYDOY(sceneID[9:16]) - productID = matchOrNone(ImageDataParserLandsat.regLandsatProductID, self.baseName) + productID = matchOrNone(ImageDateParserLandsat.regLandsatProductID, self.baseName) if productID: return np.datetim64(productID[17:25]) return None @@ -118,7 +155,6 @@ dateParserList.insert(0, dateParserList.pop(dateParserList.index(ImageDateParser def parseDateFromDataSet(dataSet): assert isinstance(dataSet, gdal.Dataset) for parser in dateParserList: - print(parser) dtg = parser(dataSet).parseDate() if dtg: return dtg diff --git a/timeseriesviewer/main.py b/timeseriesviewer/main.py index 6fa2c6db713730c63e4ea3d49e8f7702e41be3ef..1849ed2ab79aa6e748e39f2c58190dbe6aedc427 100644 --- a/timeseriesviewer/main.py +++ b/timeseriesviewer/main.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ /*************************************************************************** - EnMAPBox - A QGIS plugin - EnMAP-Box V3 + HUB TimeSeriesViewer + A QGIS based time series viewer ------------------- begin : 2015-08-20 git sha : $Format:%H$ - copyright : (C) 2015 by HU-Berlin - email : bj@geo.hu-berlin.de + copyright : (C) 2017 by HU-Berlin + email : benjamin.jakimow@geo.hu-berlin.de ***************************************************************************/ /*************************************************************************** @@ -23,38 +22,19 @@ # Import the code for the dialog import os, sys, re, fnmatch, collections, copy, traceback, six +import logging +logger = logging.getLogger(__name__) from qgis.core import * -#os.environ['PATH'] += os.pathsep + r'C:\OSGeo4W64\bin' -from osgeo import gdal, ogr, osr, gdal_array +from timeseriesviewer.utils import * -DEBUG = True -import qgis.analysis -try: - from qgis.gui import * - import qgis - qgis_available = True - - #import console.console_output - #console.show_console() - #sys.stdout = console.console_output.writeOut() - #sys.stderr = console.console_output.writeOut() -except: - print('Can not find QGIS instance') - qgis_available = False +DEBUG = True import numpy as np - -import sys, bisect, multiprocessing, site -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from PyQt4.uic.Compiler.qtproxies import QtGui, QtCore -import code -import codecs - +import multiprocessing #abbreviations -from timeseriesviewer import jp, mkdir, DIR_SITE_PACKAGES, file_search, dprint +from timeseriesviewer import jp, mkdir, DIR_SITE_PACKAGES, file_search from timeseriesviewer.timeseries import * @@ -72,93 +52,6 @@ if os.path.exists(path): import pyqtgraph as pg -class SpatialExtent(QgsRectangle): - """ - Object to keep QgsRectangle and QgsCoordinateReferenceSystem together - """ - @staticmethod - def fromMapCanvas(mapCanvas): - assert isinstance(mapCanvas, QgsMapCanvas) - extent = mapCanvas.extent() - crs = mapCanvas.mapSettings().destinationCrs() - return SpatialExtent(crs, extent) - - @staticmethod - def fromMapLayer(lyr): - assert isinstance(lyr, QgsMapLayer) - extent = lyr.extent() - crs = lyr.crs() - return SpatialExtent(crs, extent) - - - def __init__(self, crs, *args): - assert isinstance(crs, QgsCoordinateReferenceSystem) - super(SpatialExtent, self).__init__(*args) - self.mCrs = crs - - def setCrs(self, crs): - assert isinstance(crs, QgsCoordinateReferenceSystem) - self.mCrs = crs - - def crs(self): - return self.mCrs - - def toCrs(self, crs): - assert isinstance(crs, QgsCoordinateReferenceSystem) - box = QgsRectangle(self) - if self.mCrs != crs: - trans = QgsCoordinateTransform(self.mCrs, crs) - box = trans.transformBoundingBox(box) - return SpatialExtent(crs, box) - - def __copy__(self): - return SpatialExtent(self.crs(), QgsRectangle(self)) - - def combineExtentWith(self, *args): - if args is None: - return - elif isinstance(args[0], SpatialExtent): - extent2 = args[0].toCrs(self.crs()) - self.combineExtentWith(QgsRectangle(extent2)) - else: - super(SpatialExtent, self).combineExtentWith(*args) - - def setCenter(self, centerPoint, crs=None): - - if crs and crs != self.crs(): - trans = QgsCoordinateTransform(crs, self.crs()) - centerPoint = trans.transform(centerPoint) - - delta = centerPoint - self.center() - self.setXMaximum(self.xMaximum() + delta.x()) - self.setXMinimum(self.xMinimum() + delta.x()) - self.setYMaximum(self.yMaximum() + delta.y()) - self.setYMinimum(self.yMinimum() + delta.y()) - - - def __cmp__(self, other): - if other is None: return 1 - s = "" - - def __eq__(self, other): - s = "" - - def __sub__(self, other): - raise NotImplementedError() - - def __mul__(self, other): - raise NotImplementedError() - - def upperLeft(self): - return self.xMinimum(), self.yMaximum() - - def lowerRight(self): - return self.xMaximum(), self.yMinimum() - - def __repr__(self): - - return '{} {} {}'.format(self.upperLeft(), self.lowerRight(), self.crs().authid()) - class TsvMimeDataUtils(QObject): def __init__(self, mimeData): assert isinstance(mimeData, QMimeData) @@ -359,7 +252,9 @@ class MapView(QObject): sigTitleChanged = pyqtSignal(str) sigSensorRendererChanged = pyqtSignal(SensorInstrument, QgsRasterRenderer) - + from timeseriesviewer.crosshair import CrosshairStyle + sigCrosshairStyleChanged = pyqtSignal(CrosshairStyle) + sigShowCrosshair = pyqtSignal(bool) sigVectorLayerChanged = pyqtSignal() sigSpatialExtentChanged = pyqtSignal(SpatialExtent) @@ -382,6 +277,7 @@ class MapView(QObject): self.spatialExtent = None self.ui.actionRemoveMapView.triggered.connect(lambda: self.sigRemoveMapView.emit(self)) self.ui.actionApplyStyles.triggered.connect(self.applyStyles) + self.ui.actionShowCrosshair.toggled.connect(self.setShowCrosshair) self.ui.sigShowMapView.connect(lambda: self.sigMapViewVisibility.emit(True)) self.ui.sigHideMapView.connect(lambda: self.sigMapViewVisibility.emit(False)) self.ui.sigVectorVisibility.connect(self.sigVectorVisibility.emit) @@ -433,6 +329,10 @@ class MapView(QObject): def title(self): return self.mTitle + def setCrosshairStyle(self, crosshairStyle): + self.sigCrosshairStyleChanged.emit(crosshairStyle) + def setShowCrosshair(self, b): + self.sigShowCrosshair.emit(b) def removeSensor(self, sensor): assert type(sensor) is SensorInstrument @@ -564,7 +464,7 @@ class TimeSeriesDatumView(QObject): i = self.MVC.index(mapView) - from timeseriesviewer.ui.widgets import TsvMapCanvas + from timeseriesviewer.mapcanvas import TsvMapCanvas canvas = TsvMapCanvas(self, mapView, parent=self.ui) canvas.setFixedSize(self.subsetSize) @@ -627,6 +527,12 @@ class SpatialTemporalVisualization(QObject): self.setSpatialExtent(self.TS.getMaxSpatialExtent()) self.setSubsetSize(QSize(100,50)) + def setCrosshairStyle(self, crosshairStyle): + self.MVC.setCrosshairStyle(crosshairStyle) + + def setShowCrosshair(self, b): + self.MVC.setShowCrosshair(b) + def setVectorLayer(self, lyr): self.MVC.setVectorLayer(lyr) @@ -882,10 +788,19 @@ class MapViewCollection(QObject): self.mapViewsDefinitions = [] self.mapViewButtons = dict() self.adjustScrollArea() + def applyStyles(self): for mapView in self.mapViewsDefinitions: mapView.applyStyles() + def setCrosshairStyle(self, crosshairStyle): + for mapView in self.mapViewsDefinitions: + mapView.setCrosshairStyle(crosshairStyle) + + def setShowCrosshair(self, b): + for mapView in self.mapViewsDefinitions: + mapView.setShowCrosshair(b) + def index(self, mapView): assert isinstance(mapView, MapView) return self.mapViewsDefinitions.index(mapView) @@ -1079,7 +994,7 @@ class TimeSeriesViewer: D.actionIdentifyMapLayers.triggered.connect(lambda: self.spatialTemporalVis.activateMapTool('identifyMapLayers')) D.actionAddMapView.triggered.connect(self.spatialTemporalVis.createMapView) - D.actionAddTSD.triggered.connect(lambda : self.ua_addTSImages()) + D.actionAddTSD.triggered.connect(lambda : self.addTimeSeriesImages()) D.actionRemoveTSD.triggered.connect(lambda: self.TS.removeDates(self.ui.dockTimeSeries.selectedTimeSeriesDates())) D.actionRefresh.triggered.connect(self.spatialTemporalVis.refresh) D.actionLoadTS.triggered.connect(self.loadTimeSeries) @@ -1087,6 +1002,7 @@ class TimeSeriesViewer: D.actionSaveTS.triggered.connect(self.ua_saveTSFile) D.actionAddTSExample.triggered.connect(self.ua_loadExampleTS) + D.actionShowCrosshair.toggled.connect(self.spatialTemporalVis.setShowCrosshair) #connect buttons with actions from timeseriesviewer.ui.widgets import AboutDialogUI, PropertyDialogUI @@ -1304,10 +1220,13 @@ class TimeSeriesViewer: # w.widget().deleteLater() QApplication.processEvents() - def ua_addTSImages(self, files=None): + def addTimeSeriesImages(self, files=None): if files is None: files = QFileDialog.getOpenFileNames() + #collect sublayers, if existing + + if files: self.TS.addFiles(files) @@ -1423,8 +1342,8 @@ def run_tests(): filesImgLS = file_search(dirSrcLS, '20*_BOA.vrt') filesImgRE = file_search(dirSrcRE, '*.vrt', recursive=True) #filesMsk = file_search(dirSrc, '2014*_Msk.vrt') - S.ua_addTSImages(files=filesImgLS[0:2]) - S.ua_addTSImages(files=filesImgRE[0:2]) + S.addTimeSeriesImages(files=filesImgLS[0:2]) + S.addTimeSeriesImages(files=filesImgRE[0:2]) #S.ua_addTSImages(files=filesImgLS) #S.ua_addTSImages(files=filesImgRE) #S.ua_loadExampleTS() @@ -1455,7 +1374,7 @@ def run_tests(): dirSrcLS = r'O:\SenseCarbonProcessing\BJ_NOC\01_RasterData\00_VRTs\02_Cutted' filesImgLS = file_search(dirSrcLS, '2014*_BOA.vrt') filesMsk = file_search(dirSrcLS, '2014*_Msk.vrt') - S.ua_addTSImages(files=filesImgLS) + S.addTimeSeriesImages(files=filesImgLS) S.ua_addTSMasks(files=filesMsk) #S.ua_addView(bands=[4,5,3]) diff --git a/timeseriesviewer/mapcanvas.py b/timeseriesviewer/mapcanvas.py new file mode 100644 index 0000000000000000000000000000000000000000..66a36cacc527d1e1d1d0667ed4be9d1d455e4852 --- /dev/null +++ b/timeseriesviewer/mapcanvas.py @@ -0,0 +1,432 @@ + +import os, logging + +logger = logging.getLogger(__name__) + +from qgis.core import * +from qgis.gui import * +from PyQt4.QtCore import * +from PyQt4.QtGui import * +from timeseriesviewer import SETTINGS +from timeseriesviewer.utils import * +from timeseriesviewer.ui.widgets import TsvScrollArea + +class TsvMapCanvas(QgsMapCanvas): + from timeseriesviewer.main import SpatialExtent + saveFileDirectories = dict() + sigShowProfiles = pyqtSignal(QgsPoint, QgsCoordinateReferenceSystem) + sigSpatialExtentChanged = pyqtSignal(SpatialExtent) + + def __init__(self, tsdView, mapView, parent=None): + super(TsvMapCanvas, self).__init__(parent=parent) + from timeseriesviewer.main import TimeSeriesDatumView, MapView + assert isinstance(tsdView, TimeSeriesDatumView) + assert isinstance(mapView, MapView) + + #the canvas + self.setCrsTransformEnabled(True) + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.setCanvasColor(SETTINGS.value('CANVAS_BACKGROUND_COLOR', QColor(0, 0, 0))) + self.setContextMenuPolicy(Qt.DefaultContextMenu) + + self.extentsChanged.connect(lambda : self.sigSpatialExtentChanged.emit(self.spatialExtent())) + + self.scrollArea = tsdView.scrollArea + assert isinstance(self.scrollArea, TsvScrollArea) + self.scrollArea.sigResized.connect(self.setRenderMe) + self.scrollArea.horizontalScrollBar().valueChanged.connect(self.setRenderMe) + + self.tsdView = tsdView + self.mapView = mapView + + from timeseriesviewer.crosshair import CrosshairMapCanvasItem + self.crosshairItem = CrosshairMapCanvasItem(self) + + + self.vectorLayer = None + + self.mapView.sigVectorLayerChanged.connect(self.refresh) + self.mapView.sigVectorVisibility.connect(self.refresh) + + self.renderMe = False + self.setRenderMe() + + self.sensorView = self.mapView.sensorViews[self.tsdView.Sensor] + self.mapView.sigMapViewVisibility.connect(self.refresh) + self.mapView.sigSpatialExtentChanged.connect(self.setSpatialExtent) + self.mapView.sigCrosshairStyleChanged.connect(self.setCrosshairStyle) + self.mapView.sigShowCrosshair.connect(self.setShowCrosshair) + self.referenceLayer = QgsRasterLayer(self.tsdView.TSD.pathImg) + + + + + + self.sensorView.sigSensorRendererChanged.connect(self.setRenderer) + self.setRenderer(self.sensorView.layerRenderer()) + + + self.MAPTOOLS = dict() + self.MAPTOOLS['zoomOut'] = QgsMapToolZoom(self, True) + self.MAPTOOLS['zoomIn'] = QgsMapToolZoom(self, False) + self.MAPTOOLS['pan'] = QgsMapToolPan(self) + from timeseriesviewer.maptools import PointMapTool, PointLayersMapTool + mt = PointMapTool(self) + mt.sigCoordinateSelected.connect(self.sigShowProfiles.emit) + self.MAPTOOLS['identifyProfile'] = mt + + self.refresh() + + + def mapLayersToRender(self, *args): + """Returns the map layers actually to be rendered""" + mapLayers = [] + + if self.mapView.visibleVectorOverlay(): + #if necessary, register new vector layer + refLyr = self.mapView.vectorLayer + refUri = refLyr.dataProvider().dataSourceUri() + + if self.vectorLayer is None or self.vectorLayer.dataProvider().dataSourceUri() != refUri: + providerKey = refLyr.dataProvider().name() + baseName = os.path.basename(refUri) + self.vectorLayer = QgsVectorLayer(refUri, baseName, providerKey) + + #update layer style + self.vectorLayer.setRendererV2(refLyr.rendererV2().clone()) + mapLayers.append(self.vectorLayer) + + if self.referenceLayer: + mapLayers.append(self.referenceLayer) + + return mapLayers + + + + def setLayerSet(self, *args): + raise DeprecationWarning() + + def setLayers(self, mapLayers): + reg = QgsMapLayerRegistry.instance() + for l in mapLayers: + reg.addMapLayer(l) + super(TsvMapCanvas,self).setLayerSet([QgsMapCanvasLayer(l) for l in mapLayers]) + + def refresh(self): + self.setLayers(self.mapLayersToRender()) + self.setRenderMe() + super(TsvMapCanvas, self).refresh() + + + def setCrosshairStyle(self,crosshairStyle): + from timeseriesviewer.crosshair import CrosshairStyle + if crosshairStyle is None: + self.crosshairItem.crosshairStyle.setShow(False) + self.crosshairItem.update() + else: + assert isinstance(crosshairStyle, CrosshairStyle) + self.crosshairItem.setCrosshairStyle(crosshairStyle) + + def setShowCrosshair(self,b): + self.crosshairItem.setShow(b) + + def setRenderMe(self): + oldFlag = self.renderFlag() + + newFlag = self.visibleRegion().boundingRect().isValid() and self.isVisible() and self.tsdView.TSD.isVisible() + if oldFlag != newFlag: + self.setRenderFlag(newFlag) + #print((self.tsdView.TSD, self.renderFlag())) + #return b.isValid() + + def pixmap(self): + """ + Returns the current map image as pixmap + :return: + """ + return QPixmap(self.map().contentImage().copy()) + + + + def contextMenuEvent(self, event): + menu = QMenu() + # add general options + menu.addSeparator() + action = menu.addAction('Stretch using current Extent') + action.triggered.connect(self.stretchToCurrentExtent) + action = menu.addAction('Zoom to Layer') + action.triggered.connect(lambda : self.setExtent(SpatialExtent(self.referenceLayer.crs(),self.referenceLayer.extent()))) + menu.addSeparator() + + action = menu.addAction('Change crosshair style') + from timeseriesviewer.crosshair import CrosshairDialog + action.triggered.connect(lambda : self.setCrosshairStyle( + CrosshairDialog.getCrosshairStyle(parent=self, + mapCanvas=self, + crosshairStyle=self.crosshairItem.crosshairStyle) + )) + + if self.crosshairItem.crosshairStyle.mShow: + action = menu.addAction('Hide crosshair') + action.triggered.connect(lambda : self.setShowCrosshair(False)) + else: + action = menu.addAction('Show crosshair') + action.triggered.connect(lambda: self.setShowCrosshair(True)) + + menu.addSeparator() + + m = menu.addMenu('Copy...') + action = m.addAction('Map to Clipboard') + action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.pixmap())) + action = m.addAction('Image Path') + action.triggered.connect(lambda: QApplication.clipboard().setText(self.tsdView.TSD.pathImg)) + action = m.addAction('Image Style') + #action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.tsdView.TSD.pathImg)) + + m = menu.addMenu('Save as...') + action = m.addAction('PNG') + action.triggered.connect(lambda : self.saveMapImageDialog('PNG')) + action = m.addAction('JPEG') + action.triggered.connect(lambda: self.saveMapImageDialog('JPG')) + + from timeseriesviewer.main import QgisTsvBridge + bridge = QgisTsvBridge.instance() + if bridge: + assert isinstance(bridge, QgisTsvBridge) + action = m.addAction('Add layer to QGIS') + action = m.addAction('Import extent from QGIS') + action = m.addAction('Export extent to QGIS') + s = "" + + + + + menu.addSeparator() + TSD = self.tsdView.TSD + action = menu.addAction('Hide date') + action.triggered.connect(lambda : self.tsdView.TSD.setVisibility(False)) + action = menu.addAction('Remove date') + action.triggered.connect(lambda: TSD.timeSeries.removeDates([TSD])) + action = menu.addAction('Remove map view') + action.triggered.connect(lambda: self.mapView.sigRemoveMapView.emit(self.mapView)) + action = menu.addAction('Hide map view') + action.triggered.connect(lambda: self.mapView.sigHideMapView.emit()) + + + menu.exec_(event.globalPos()) + + def stretchToCurrentExtent(self): + results = dict() + se = self.spatialExtent() + + for l in self.layers(): + if isinstance(l, QgsRasterLayer): + r = l.renderer() + dp = l.dataProvider() + newRenderer = None + + extent = se.toCrs(l.crs()) + + assert isinstance(dp, QgsRasterDataProvider) + bands = None + if isinstance(r, QgsMultiBandColorRenderer): + + def getCE(band, ce): + stats = dp.bandStatistics(band, QgsRasterBandStats.All, extent, 500) + ce = QgsContrastEnhancement(ce) + ce.setMinimumValue(stats.minimumValue) + ce.setMaximumValue(stats.maximumValue) + return ce + + newRenderer = QgsMultiBandColorRenderer(None,r.redBand(), r.greenBand(), r.blueBand()) + newRenderer.setRedContrastEnhancement(getCE(r.redBand(), r.redContrastEnhancement())) + newRenderer.setGreenContrastEnhancement(getCE(r.greenBand(), r.greenContrastEnhancement())) + newRenderer.setBlueContrastEnhancement(getCE(r.blueBand(), r.blueContrastEnhancement())) + + results[self.tsdView.TSD.sensor] = newRenderer + elif isinstance(r, QgsSingleBandPseudoColorRenderer): + newRenderer = r.clone() + stats = dp.bandStatistics(newRenderer.band(), QgsRasterBandStats.All, extent, 500) + shader = newRenderer.shader() + newRenderer.setClassificationMax(stats.maximumValue) + newRenderer.setClassificationMin(stats.minimumValue) + shader.setMaximumValue(stats.maximumValue) + shader.setMinimumValue(stats.minimumValue) + s = "" + + if newRenderer is not None: + self.sensorView.setLayerRenderer(newRenderer) + s = "" + + def activateMapTool(self, key): + if key is None: + self.setMapTool(None) + elif key in self.MAPTOOLS.keys(): + self.setMapTool(self.MAPTOOLS[key]) + else: + logger.error('unknown map tool key "{}"'.format(key)) + + def saveMapImageDialog(self, fileType): + lastDir = SETTINGS.value('CANVAS_SAVE_IMG_DIR', os.path.expanduser('~')) + path = jp(lastDir, '{}.{}.{}'.format(self.tsdView.TSD.date, self.mapView.title(), fileType.lower())) + + path = QFileDialog.getSaveFileName(self, 'Save map as {}'.format(fileType), path) + if len(path) > 0: + self.saveAsImage(path, None, fileType) + SETTINGS.setValue('CANVAS_SAVE_IMG_DIR', os.path.dirname(path)) + + + def setRenderer(self, renderer, targetLayerUri=None): + if targetLayerUri is None: + targetLayerUri = str(self.referenceLayer.source()) + + lyrs = [l for l in self.mapLayersToRender() if str(l.source()) == targetLayerUri] + assert len(lyrs) <= 1 + for lyr in lyrs: + if isinstance(renderer, QgsMultiBandColorRenderer): + r = renderer.clone() + r.setInput(lyr.dataProvider()) + elif isinstance(renderer, QgsSingleBandPseudoColorRenderer): + r = renderer.clone() + #r = QgsSingleBandPseudoColorRenderer(None, renderer.band(), None) + r.setInput(lyr.dataProvider()) + cmin = renderer.classificationMin() + cmax = renderer.classificationMax() + r.setClassificationMin(cmin) + r.setClassificationMax(cmax) + #r.setShader(renderer.shader()) + s = "" + else: + raise NotImplementedError() + lyr.setRenderer(r) + + self.refresh() + + def setSpatialExtent(self, spatialExtent): + assert isinstance(spatialExtent, SpatialExtent) + if self.spatialExtent() != spatialExtent: + self.blockSignals(True) + self.setDestinationCrs(spatialExtent.crs()) + self.setExtent(spatialExtent) + self.blockSignals(False) + self.refresh() + + + def spatialExtent(self): + return SpatialExtent.fromMapCanvas(self) + + + + +class CanvasBoundingBoxItem(QgsGeometryRubberBand): + + def __init__(self, mapCanvas): + assert isinstance(mapCanvas, QgsMapCanvas) + super(CanvasBoundingBoxItem, self).__init__(mapCanvas) + + self.canvas = mapCanvas + self.mCanvasExtents = dict() + self.mShow = True + self.mShowTitles = True + self.setIconType(QgsGeometryRubberBand.ICON_NONE) + + def connectCanvas(self, canvas): + assert isinstance(canvas, QgsMapCanvas) + assert canvas != self.canvas + if canvas not in self.mCanvasExtents.keys(): + self.mCanvasExtents[canvas] = None + canvas.extentsChanged.connect(lambda : self.onExtentsChanged(canvas)) + canvas.destroyed.connect(lambda : self.disconnectCanvas(canvas)) + self.onExtentsChanged(canvas) + + def disconnectCanvas(self, canvas): + self.mCanvasExtents.pop(canvas) + + def onExtentsChanged(self, canvas): + assert isinstance(canvas, QgsMapCanvas) + + ext = SpatialExtent.fromMapCanvas(canvas) + ext = ext.toCrs(self.canvas.mapSettings().destinationCrs()) + + geom = QgsPolygonV2() + assert geom.fromWkt(ext.asWktPolygon()) + + self.mCanvasExtents[canvas] = (ext, geom) + self.refreshExtents() + + def refreshExtents(self): + multi = QgsMultiPolygonV2() + if self.mShow: + for canvas, t in self.mCanvasExtents.items(): + ext, geom = t + multi.addGeometry(geom.clone()) + self.setGeometry(multi) + + def paint(self, painter, QStyleOptionGraphicsItem=None, QWidget_widget=None): + super(CanvasBoundingBoxItem, self).paint(painter) + + if self.mShowTitles and self.mShow: + painter.setPen(Qt.blue); + painter.setFont(QFont("Arial", 30)) + + for canvas, t in self.mCanvasExtents.items(): + ext, geom = t + ULpx = self.toCanvasCoordinates(ext.center()) + txt = canvas.windowTitle() + painter.drawLine(0, 0, 200, 200); + painter.drawText(ULpx, txt) + + + def setShow(self, b): + assert isinstance(b, bool) + self.mShow = b + + def setShowTitles(self, b): + assert isinstance(b, bool) + self.mShowTitles = b + +if __name__ == '__main__': + import site, sys + #add site-packages to sys.path as done by enmapboxplugin.py + + from timeseriesviewer import sandbox + qgsApp = sandbox.initQgisEnvironment() + + + + import example.Images + lyr1 = QgsRasterLayer(example.Images.Img_2012_05_09_LE72270652012130EDC00_BOA) + lyr2 = QgsRasterLayer(example.Images.Img_2012_05_09_LE72270652012130EDC00_BOA) + lyr3 = QgsRasterLayer(example.Images.Img_2012_05_09_LE72270652012130EDC00_BOA) + + QgsMapLayerRegistry.instance().addMapLayers([lyr1, lyr2, lyr3]) + + w = QWidget() + l = QHBoxLayout() + canvas1 = QgsMapCanvas() + canvas1.setWindowTitle('Canvas1') + canvas1.setLayerSet([QgsMapCanvasLayer(lyr1)]) + canvas1.setExtent(lyr1.extent()) + canvas2 = QgsMapCanvas() + canvas2.setWindowTitle('Canvas2') + canvas2.setLayerSet([QgsMapCanvasLayer(lyr2)]) + canvas2.setExtent(lyr2.extent()) + canvas3 = QgsMapCanvas() + canvas3.setWindowTitle('Canvas3') + #canvas3.setLayerSet([QgsMapCanvasLayer(lyr3)]) + #canvas3.setExtent(lyr3.extent()) + + item = CanvasBoundingBoxItem(canvas1) + item.setShowTitles(True) + item.connectCanvas(canvas2) + item.connectCanvas(canvas3) + + l.addWidget(canvas1) + l.addWidget(canvas2) + l.addWidget(canvas3) + w.setLayout(l) + + w.show() + + qgsApp.exec_() + qgsApp.exitQgis() diff --git a/timeseriesviewer/maptools.py b/timeseriesviewer/maptools.py index 3aa2d5a4daf71ec490314879ee3ba58f40fc369e..2823c389bae2e3e12cfd0113b1492ab44033c4fa 100644 --- a/timeseriesviewer/maptools.py +++ b/timeseriesviewer/maptools.py @@ -31,91 +31,6 @@ class CursorLocationValueMapTool(QgsMapTool): self.sigLocationRequest.emit(point, crs) -def add_QgsRasterLayer(iface, path, rgb): - if iface: - - fi = QFileInfo(path) - layer = QgsRasterLayer(path, fi.baseName()) - if not layer.isValid(): - six.print_('Failed to load {}'.format(path)) - else: - rasterLyr = iface.addRasterLayer(path, fi.baseName()) - - - renderer = rasterLyr.renderer() - print(type(renderer)) - - if type(renderer) is QgsMultiBandColorRenderer: - renderer.setRedBand(rgb[0]) - renderer.setGreenBand(rgb[0]) - renderer.setBlueBand(rgb[0]) - - if hasattr(layer, "triggerRepaint"): - #layer.repaintRequested() - layer.triggerRepaint() - - - s = "" - - -paste_test = """ - <!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'> -<qgis version="2.12.3-Lyon"> - <pipe> - <rasterrenderer opacity="1" alphaBand="-1" blueBand="1" greenBand="2" type="multibandcolor" redBand="3"> - <rasterTransparency/> - <redContrastEnhancement> - <minValue>2803.02</minValue> - <maxValue>6072.21</maxValue> - <algorithm>StretchToMinimumMaximum</algorithm> - </redContrastEnhancement> - <greenContrastEnhancement> - <minValue>5103.86</minValue> - <maxValue>7228.58</maxValue> - <algorithm>StretchToMinimumMaximum</algorithm> - </greenContrastEnhancement> - <blueContrastEnhancement> - <minValue>5992.32</minValue> - <maxValue>7718.33</maxValue> - <algorithm>StretchToMinimumMaximum</algorithm> - </blueContrastEnhancement> - </rasterrenderer> - <brightnesscontrast brightness="0" contrast="0"/> - <huesaturation colorizeGreen="128" colorizeOn="0" colorizeRed="255" colorizeBlue="128" grayscaleMode="0" saturation="0" colorizeStrength="100"/> - <rasterresampler maxOversampling="2"/> - </pipe> - <blendMode>0</blendMode> -</qgis> - """ - -def paste_band_settings(txt): - - result = None - try: - import xml.etree.ElementTree as ET - tree = ET.fromstring(txt) - - renderer = tree.find('*/rasterrenderer') - if renderer is not None: - bands = list() - ranges = list() - for c in ['red','green','blue']: - name = c + 'Band' - if name not in renderer.attrib.keys(): - return result - - bands.append(int(renderer.attrib[name])) - v_min = float(renderer.find(c+'ContrastEnhancement/minValue').text) - v_max = float(renderer.find(c+'ContrastEnhancement/maxValue').text) - ranges.append((v_min, v_max)) - - result = (bands, ranges) - except: - pass - - return result - - class PointMapTool(QgsMapToolEmitPoint): sigCoordinateSelected = pyqtSignal(QgsPoint, QgsCoordinateReferenceSystem) @@ -315,13 +230,3 @@ class RectangleMapTool(QgsMapToolEmitPoint): # super(RectangleMapTool, self).deactivate() #self.deactivated.emit() - - -def tests(): - - print(paste_band_settings(paste_test)) - print(paste_band_settings('foo')) - -if __name__ == '__main__': - tests() - print('Done') \ No newline at end of file diff --git a/timeseriesviewer/profilevisualization.py b/timeseriesviewer/profilevisualization.py index d51578ab2255da61755eaaac4013f7b9d72f9f85..e454ea83ab4f6628717be2b2bce09e1ba62c0252 100644 --- a/timeseriesviewer/profilevisualization.py +++ b/timeseriesviewer/profilevisualization.py @@ -87,6 +87,9 @@ class PixelLoadWorker(QObject): self.recentFile = path lyr = QgsRasterLayer(path) + if not lyr.isValid(): + logger.debug('Layer not valid: {}'.format(path)) + continue dp = lyr.dataProvider() trans = QgsCoordinateTransform(crs, dp.crs()) diff --git a/timeseriesviewer/sandbox.py b/timeseriesviewer/sandbox.py index 2de357c53278eda395cd0acec1390613a48afe04..7333b9b78440e4486e05984b595c509d0519e87c 100644 --- a/timeseriesviewer/sandbox.py +++ b/timeseriesviewer/sandbox.py @@ -1,7 +1,8 @@ from __future__ import absolute_import import six, sys, os, gc, re, collections, site, inspect +import logging, io +logger = logging.getLogger(__name__) from osgeo import gdal, ogr - from qgis import * from qgis.core import * from qgis.gui import * @@ -9,6 +10,9 @@ from PyQt4.QtGui import * from PyQt4.QtCore import * from timeseriesviewer import * +from timeseriesviewer.utils import * +from timeseriesviewer import file_search +from timeseriesviewer.timeseries import * class SandboxObjects(object): @@ -33,40 +37,9 @@ def sandboxGui(): S.ui.show() S.run() - if False: - from timeseriesviewer import file_search - searchDir = r'H:\LandsatData\Landsat_NovoProgresso' - files = file_search(searchDir, '*band4.img', recursive=True) - - #searchDir = r'O:\SenseCarbonProcessing\BJ_NOC\01_RasterData\01_UncutVRT' - #files = file_search(searchDir, '*BOA.vrt', recursive=True) - - files = files[0:10] - S.loadImageFiles(files) - return - if False: - files = [r'E:\_EnMAP\temp\temp_bj\landsat\37S\EB\LC81720342015129LGN00\LC81720342015129LGN00_sr.tif'] - S.loadImageFiles(files) - return - if True: - from timeseriesviewer import file_search - files = file_search(r'E:\_EnMAP\temp\temp_bj\landsat\37S\EB', '*_sr.tif', recursive=True) - #files = files[0:15] - print('Load {} images...'.format(len(files))) - S.loadImageFiles(files) - return - if False: - files = [r'H:\\LandsatData\\Landsat_NovoProgresso\\LC82270652013140LGN01\\LC82270652013140LGN01_sr_band4.img'] - S.loadImageFiles(files) - return - if False: - S.spatialTemporalVis.MVC.createMapView() - S.loadTimeSeries(path=PATH_EXAMPLE_TIMESERIES, n_max=1) - return - if True: - S.loadTimeSeries(path=PATH_EXAMPLE_TIMESERIES, n_max=100) - return - pass + S.spatialTemporalVis.MVC.createMapView() + import example.Images + S.addTimeSeriesImages([example.Images.Img_2014_07_10_LC82270652014191LGN00_BOA]) class QgisFake(QgisInterface): @@ -219,15 +192,11 @@ def gdal_qgis_benchmark(): s ="" - -if __name__ == '__main__': - import site, sys - #add site-packages to sys.path as done by enmapboxplugin.py +def initQgisEnvironment(): from timeseriesviewer import DIR_SITE_PACKAGES site.addsitedir(DIR_SITE_PACKAGES) - - #prepare QGIS environment + # prepare QGIS environment if sys.platform == 'darwin': PATH_QGS = r'/Applications/QGIS.app/Contents/MacOS' os.environ['GDAL_DATA'] = r'/usr/local/Cellar/gdal/1.11.3_1/share' @@ -235,19 +204,25 @@ if __name__ == '__main__': # assume OSGeo4W startup PATH_QGS = os.environ['QGIS_PREFIX_PATH'] assert os.path.exists(PATH_QGS) - qgsApp = QgsApplication([], True) QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns') QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns/qgis') qgsApp.setPrefixPath(PATH_QGS, True) qgsApp.initQgis() + return qgsApp + + + +if __name__ == '__main__': + import site, sys + #add site-packages to sys.path as done by enmapboxplugin.py + + qgsApp = initQgisEnvironment() #run tests if False: gdal_qgis_benchmark() if False: sandboxQgisBridge() if True: sandboxGui() - if False: test_component() - #close QGIS qgsApp.exec_() diff --git a/timeseriesviewer/tests.py b/timeseriesviewer/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..c34bde98ef158ce732d19a13791b59bbf1ee1b33 --- /dev/null +++ b/timeseriesviewer/tests.py @@ -0,0 +1,83 @@ + +import os, re, io +from unittest import TestCase +from osgeo import gdal +from qgis import * +#from timeseriesviewer import * +from . import * +from timeseries import * + +class TestFileFormatLoading(TestCase): + + @classmethod + def setUpClass(cls): + cls.TS = TimeSeries() + + if False: + cls.savedStdOut = sys.stdout + cls.savedStdIn = sys.stdin + + cls.stdout = io.StringIO() + cls.stderr = io.StringIO() + sys.stdout = cls.stdout + sys.stderr = cls.stderr + + @classmethod + def tearDownClass(cls): + pass + #sys.stdout = cls.stdout + #sys.stderr = cls.stderr + + def setUp(self): + self.TS.clear() + + def tearDown(self): + self.TS.clear() + + + + def test_loadLandsat(self): + searchDir = jp(DIR_EXAMPLES, 'Images') + files = file_search(searchDir, '*_L*_BOA.bsq')[0:3] + self.TS.addFiles(files) + + self.assertEqual(len(files), len(self.TS)) + s = "" + + def test_nestedVRTs(self): + # load VRTs pointing to another VRT pointing to Landsat imagery + searchDir = r'O:\SenseCarbonProcessing\BJ_NOC\01_RasterData\02_CuttedVRT' + files = file_search(searchDir, '*BOA.vrt', recursive=True)[0:3] + self.TS.addFiles(files) + self.assertEqual(len(files), len(self.TS)) + + def test_loadRapidEye(self): + # load RapidEye + searchDir = r'H:\RapidEye\3A' + files = file_search(searchDir, '*.tif', recursive=True) + files = [f for f in files if not re.search('_(udm|browse)\.tif$', f)] + self.TS.addFiles(files) + self.assertEqual(len(files), len(self.TS)) + + def test_loadPleiades(self): + #load Pleiades data + searchDir = r'H:\Pleiades' + #files = file_search(searchDir, 'DIM*.xml', recursive=True) + files = file_search(searchDir, '*.jp2', recursive=True)[0:3] + self.TS.addFiles(files) + self.assertEqual(len(files), len(self.TS)) + + def test_loadSentinel2(self): + #load Sentinel-2 + searchDir = r'H:\Sentinel2' + files = file_search(searchDir, '*MSIL1C.xml', recursive=True) + self.TS.addFiles(files) + + #self.assertRegexpMatches(self.stderr.getvalue().strip(), 'Unable to add:') + self.assertEqual(0, len(self.TS)) # do not add a containers + subdatasets = [] + for file in files: + subs = gdal.Open(file).GetSubDatasets() + subdatasets.extend(s[0] for s in subs) + self.TS.addFiles(subdatasets) + self.assertEqual(len(subdatasets), len(self.TS)) # add subdatasets diff --git a/timeseriesviewer/timeseries.py b/timeseriesviewer/timeseries.py index b026ce9bca4011b0b6b21d44656727fad86e8917..7f80e9ff9138b82baf2e2d600e0fc9b962669f61 100644 --- a/timeseriesviewer/timeseries.py +++ b/timeseriesviewer/timeseries.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import six, sys, os, gc, re, collections, site, inspect, time, traceback, copy - +import logging +logger = logging.getLogger(__name__) import bisect, datetime from osgeo import gdal, ogr @@ -11,11 +12,15 @@ from PyQt4.QtGui import * from PyQt4.QtCore import * from PyQt4.QtXml import * -from osgeo import gdal, ogr +from osgeo import gdal, ogr, gdal_array + +gdal.SetConfigOption('VRT_SHARED_SOURCE', '0') #!important. really. do not change this. + import numpy as np -from timeseriesviewer import DIR_REPO, DIR_EXAMPLES, dprint, jp, findAbsolutePath +from timeseriesviewer import DIR_REPO, DIR_EXAMPLES, jp from timeseriesviewer.dateparser import parseDateFromDataSet + def transformGeometry(geom, crsSrc, crsDst, trans=None): if trans is None: assert isinstance(crsSrc, QgsCoordinateReferenceSystem) @@ -47,15 +52,6 @@ def convertMetricUnit(value, u1, u2): return value * 10**(e1-e2) -def verifyVRT(pathVRT): - - ds = gdal.Open(pathVRT) - if ds is None: - return False - s = "" - - return True - def getDS(pathOrDataset): if isinstance(pathOrDataset, gdal.Dataset): @@ -183,21 +179,24 @@ class SensorInstrument(QObject): def verifyInputImage(path, vrtInspection=''): - - if not os.path.exists(path): - print('{}Image does not exist: '.format(vrtInspection, path)) - return False + if path is None or not type(path) in [str, unicode]: + return None ds = gdal.Open(path) + if not ds: - print('{}GDAL unable to open: '.format(vrtInspection, path)) + logger.error('{}GDAL unable to open: '.format(vrtInspection, path)) + return False + + if ds.RasterCount == 0 and len(ds.GetSubDatasets()) > 0: + logger.error('Can not open container {}.\nPlease specify a subdataset'.format(path)) return False if ds.GetDriver().ShortName == 'VRT': vrtInspection = 'VRT Inspection {}\n'.format(path) - validSrc = [verifyInputImage(p, vrtInspection=vrtInspection) for p in set(ds.GetFileList()) - set([path])] + nextFiles = set(ds.GetFileList()) - set([path]) + validSrc = [verifyInputImage(p, vrtInspection=vrtInspection) for p in nextFiles] if not all(validSrc): return False - else: - return True + return True def pixel2coord(gt, x, y): """Returns global coordinates from pixel x, y coords""" @@ -215,12 +214,12 @@ class TimeSeriesDatum(QObject): :param path: :return: """ - p = findAbsolutePath(path) + tsd = None - if verifyInputImage(p): + if verifyInputImage(path): try: - tsd =TimeSeriesDatum(None, p) + tsd = TimeSeriesDatum(None, path) except : pass @@ -241,7 +240,7 @@ class TimeSeriesDatum(QObject): ds = getDS(pathImg) - self.pathImg = ds.GetFileList()[0] + self.pathImg = ds.GetFileList()[0] if isinstance(pathImg, gdal.Dataset) else pathImg self.timeSeries = timeSeries self.nb, self.nl, self.ns, self.crs, px_x, px_y = getSpatialPropertiesFromDataset(ds) @@ -460,7 +459,7 @@ class TimeSeries(QObject): TSD.sensor = existingSensors[existingSensors.index(TSD.sensor)] if TSD in self.data: - six.print_('Time series datum already added: {} {}'.format(str(TSD), TSD.pathImg), file=sys.stderr) + six.print_('Time series date-time already added ({} {}). \nPlease use VRTs to mosaic images with same acquisition date-time.'.format(str(TSD), TSD.pathImg), file=sys.stderr) else: self.Sensors[TSD.sensor].append(TSD) #insert sorted @@ -484,6 +483,8 @@ class TimeSeries(QObject): def addFiles(self, files): assert isinstance(files, list) + files = [f for f in files if f is not None] + nMax = len(files) nDone = 0 self.sigLoadingProgress.emit(0,nMax, 'Start loading {} files...'.format(nMax)) @@ -493,7 +494,7 @@ class TimeSeries(QObject): tsd = TimeSeriesDatum.createFromPath(file) if tsd is None: msg = 'Unable to add: {}'.format(os.path.basename(file)) - dprint(msg, file=sys.stderr) + logger.error(msg) else: self.addTimeSeriesDates([tsd]) msg = 'Added {}'.format(os.path.basename(file)) diff --git a/timeseriesviewer/ui/__init__.py b/timeseriesviewer/ui/__init__.py index a1644e95ca173dc47c0071cb0f839c11da8b14dd..5614176378901e965ade31b5ce8b8f0058556c8c 100644 --- a/timeseriesviewer/ui/__init__.py +++ b/timeseriesviewer/ui/__init__.py @@ -1,4 +1,5 @@ -import os, sys, six, importlib +import os, sys, six, importlib, logging +logger = logging.getLogger(__name__) from PyQt4.QtCore import * from PyQt4.QtXml import * from PyQt4.QtGui import * @@ -8,7 +9,7 @@ from PyQt4 import uic #dictionary to store form classes. *.ui file name is key FORM_CLASSES = dict() -from timeseriesviewer import jp, DIR_UI, dprint +from timeseriesviewer import jp, DIR_UI def loadUIFormClass(pathUi, from_imports=False): @@ -65,3 +66,4 @@ def loadUIFormClass(pathUi, from_imports=False): FORM_CLASSES[pathUi] = formClass return FORM_CLASSES[pathUi] + diff --git a/timeseriesviewer/ui/crosshairwidget.ui b/timeseriesviewer/ui/crosshairwidget.ui new file mode 100644 index 0000000000000000000000000000000000000000..8abf94794aa6bd2899b763ba8cdb6173123a3007 --- /dev/null +++ b/timeseriesviewer/ui/crosshairwidget.ui @@ -0,0 +1,242 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>234</width> + <height>179</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="1"> + <widget class="QSpinBox" name="spinBoxCrosshairThickness"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="suffix"> + <string> px</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="label_13"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Alpha</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QCheckBox" name="cbCrosshairShowDot"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Box</string> + </property> + </widget> + </item> + <item row="0" column="4"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="1"> + <widget class="QSpinBox" name="spinBoxCrosshairGap"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="suffix"> + <string> %</string> + </property> + <property name="prefix"> + <string/> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="singleStep"> + <number>5</number> + </property> + <property name="value"> + <number>10</number> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QgsColorButton" name="btnCrosshairColor"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="colorDialogTitle"> + <string>Select Map Canvas Background Color</string> + </property> + <property name="color"> + <color> + <red>255</red> + <green>0</green> + <blue>0</blue> + </color> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QSlider" name="spinBoxCrosshairAlpha"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="maximum"> + <number>255</number> + </property> + <property name="value"> + <number>255</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="7" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_14"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Color</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QSpinBox" name="spinBoxDotSize"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="suffix"> + <string> px</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="singleStep"> + <number>5</number> + </property> + <property name="value"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Thickness</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_3"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Gap</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Size</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="spinBoxCrosshairSize"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="suffix"> + <string> %</string> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="singleStep"> + <number>5</number> + </property> + <property name="value"> + <number>95</number> + </property> + </widget> + </item> + <item row="6" column="0" colspan="2"> + <widget class="QCheckBox" name="cbShowPixelBoundaries"> + <property name="text"> + <string>Pixel boundaries</string> + </property> + </widget> + </item> + <item row="1" column="2" rowspan="5" colspan="2"> + <widget class="QgsMapCanvas" name="mapCanvas"> + <property name="enabled"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QgsColorButton</class> + <extends>QPushButton</extends> + <header>qgscolorbutton.h</header> + </customwidget> + <customwidget> + <class>QgsMapCanvas</class> + <extends>QGraphicsView</extends> + <header>qgis.gui</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/timeseriesviewer/ui/docks.py b/timeseriesviewer/ui/docks.py index 0fd851c0a0688c53ac4f55bcead11890c6660d77..746ef7147c667d735e1abf0c0fedf30b6a8811ef 100644 --- a/timeseriesviewer/ui/docks.py +++ b/timeseriesviewer/ui/docks.py @@ -37,6 +37,9 @@ class TsvDockWidgetBase(QgsDockWidget): class RenderingDockUI(TsvDockWidgetBase, load('renderingdock.ui')): + from timeseriesviewer.crosshair import CrosshairStyle + + sigMapCanvasColorChanged = pyqtSignal(QColor) def __init__(self, parent=None): super(RenderingDockUI, self).__init__(parent) self.setupUi(self) @@ -50,14 +53,12 @@ class RenderingDockUI(TsvDockWidgetBase, load('renderingdock.ui')): self.subsetSizeWidgets = [self.spinBoxSubsetSizeX, self.spinBoxSubsetSizeY] - self.gbCrosshair.setVisible(False) - - def subsetSize(self): return QSize(self.spinBoxSubsetSizeX.value(), self.spinBoxSubsetSizeY.value()) + def onSubsetValueChanged(self, key): if self.checkBoxKeepSubsetAspectRatio.isChecked(): @@ -355,41 +356,9 @@ if __name__ == '__main__': import site, sys #add site-packages to sys.path as done by enmapboxplugin.py - from timeseriesviewer import DIR_SITE_PACKAGES - site.addsitedir(DIR_SITE_PACKAGES) - - #prepare QGIS environment - if sys.platform == 'darwin': - PATH_QGS = r'/Applications/QGIS.app/Contents/MacOS' - os.environ['GDAL_DATA'] = r'/usr/local/Cellar/gdal/1.11.3_1/share' - else: - # assume OSGeo4W startup - PATH_QGS = os.environ['QGIS_PREFIX_PATH'] - assert os.path.exists(PATH_QGS) - - qgsApp = QgsApplication([], True) - QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns') - QApplication.addLibraryPath(r'/Applications/QGIS.app/Contents/PlugIns/qgis') - qgsApp.setPrefixPath(PATH_QGS, True) - qgsApp.initQgis() - - #run tests - #d = AboutDialogUI() - #d.show() - - from timeseriesviewer.tests import * - - TS = TestObjects.TimeSeries() - ext = TS.getMaxSpatialExtent() - - d = ProfileViewDockUI() - d.connectTimeSeries(TS) + from timeseriesviewer import sandbox + qgsApp = sandbox.initQgisEnvironment() + d = RenderingDockUI() d.show() - d.loadCoordinate(ext.center(), ext.crs()) - - #close QGIS - try: - qgsApp.exec_() - except: - s = "" + qgsApp.exec_() qgsApp.exitQgis() diff --git a/timeseriesviewer/ui/icons/crosshair.png b/timeseriesviewer/ui/icons/crosshair.png new file mode 100644 index 0000000000000000000000000000000000000000..c611eebc36098005e8d93155104be82b246664b4 Binary files /dev/null and b/timeseriesviewer/ui/icons/crosshair.png differ diff --git a/timeseriesviewer/ui/icons/crosshair.svg b/timeseriesviewer/ui/icons/crosshair.svg new file mode 100644 index 0000000000000000000000000000000000000000..928c51c33733483052d494c73726ae27a97778fa --- /dev/null +++ b/timeseriesviewer/ui/icons/crosshair.svg @@ -0,0 +1,532 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="24" + height="24" + id="svg5692" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="crosshair.svg" + inkscape:export-filename="D:\Repositories\QGIS_Plugins\hub-timeseriesviewer\timeseriesviewer\ui\icons\crosshair.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90" + style="display:inline"> + <title + id="title2901">zoom 1 to 1</title> + <defs + id="defs5694"> + <linearGradient + id="linearGradient3657"> + <stop + style="stop-color:#fce94f;stop-opacity:1;" + offset="0" + id="stop3659" /> + <stop + style="stop-color:#e7ce04;stop-opacity:1;" + offset="1" + id="stop3661" /> + </linearGradient> + <linearGradient + id="linearGradient2877"> + <stop + style="stop-color:#edd400;stop-opacity:1;" + offset="0" + id="stop2879" /> + <stop + style="stop-color:#c2ad00;stop-opacity:1;" + offset="1" + id="stop2881" /> + </linearGradient> + <linearGradient + id="linearGradient4042"> + <stop + style="stop-color:#f2d6a9;stop-opacity:1;" + offset="0" + id="stop4044" /> + <stop + style="stop-color:#e9b96e;stop-opacity:1;" + offset="1" + id="stop4046" /> + </linearGradient> + <linearGradient + id="linearGradient2843"> + <stop + style="stop-color:#eeeeec;stop-opacity:1;" + offset="0" + id="stop2845" /> + <stop + style="stop-color:#c8c8c2;stop-opacity:1;" + offset="1" + id="stop2847" /> + </linearGradient> + <linearGradient + id="linearGradient2835"> + <stop + style="stop-color:#ccf2a6;stop-opacity:1;" + offset="0" + id="stop2837" /> + <stop + style="stop-color:#8ae234;stop-opacity:1;" + offset="1" + id="stop2839" /> + </linearGradient> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 16 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="32 : 16 : 1" + inkscape:persp3d-origin="16 : 10.666667 : 1" + id="perspective3257" /> + <inkscape:perspective + id="perspective6979" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective7934" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective8023" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective8057" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective8095" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective8219" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective8279" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective3803" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective3869" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective3929" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective3968" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective4002" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective4032" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective4053" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective2905" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective2979" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective2842" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective2978" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective3238" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042" + id="radialGradient4048" + cx="0.5" + cy="17.838446" + fx="0.5" + fy="17.838446" + r="6.158739" + gradientTransform="matrix(0.8118545,0.97422537,-1.1052481,0.9210397,19.809981,-0.4170292)" + gradientUnits="userSpaceOnUse" /> + <inkscape:perspective + id="perspective4058" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042-2" + id="radialGradient4048-2" + cx="8.5770311" + cy="3.8663561" + fx="8.5770311" + fy="3.8663561" + r="6.1587391" + gradientTransform="matrix(0.81185454,1.1365964,-1.1707271,0.83623306,20.063146,-1.4817979)" + gradientUnits="userSpaceOnUse" /> + <linearGradient + id="linearGradient4042-2"> + <stop + style="stop-color:#f2d6a9;stop-opacity:1;" + offset="0" + id="stop4044-8" /> + <stop + style="stop-color:#e9b96e;stop-opacity:1;" + offset="1" + id="stop4046-4" /> + </linearGradient> + <radialGradient + r="6.1587391" + fy="17.838446" + fx="0.5" + cy="17.838446" + cx="0.5" + gradientTransform="matrix(0.8118545,0.97422537,-1.1052481,0.9210397,19.809981,-8.4170292)" + gradientUnits="userSpaceOnUse" + id="radialGradient4067" + xlink:href="#linearGradient4042-2" + inkscape:collect="always" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042-2" + id="radialGradient4094" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.81185454,1.1365964,-1.1707271,0.83623306,20.063146,-1.4817979)" + cx="8.5770311" + cy="3.8663561" + fx="8.5770311" + fy="3.8663561" + r="6.1587391" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042-2" + id="radialGradient4097" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.81185454,1.1365964,-1.1707271,0.83623306,20.063146,-1.4817979)" + cx="8.5770311" + cy="3.8663561" + fx="8.5770311" + fy="3.8663561" + r="6.1587391" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042-2" + id="radialGradient4100" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.81185454,1.1365964,-1.1707271,0.83623306,20.063146,-1.4817979)" + cx="8.5770311" + cy="3.8663561" + fx="8.5770311" + fy="3.8663561" + r="6.1587391" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042-2" + id="radialGradient4103" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.81185454,1.1365964,-1.1707271,0.83623306,20.063146,-1.4817979)" + cx="8.5770311" + cy="3.8663561" + fx="8.5770311" + fy="3.8663561" + r="6.1587391" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042" + id="radialGradient4106" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.8118545,0.97422537,-1.1052481,0.9210397,19.809981,-0.4170292)" + cx="0.5" + cy="17.838446" + fx="0.5" + fy="17.838446" + r="6.158739" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042" + id="radialGradient4109" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.8118545,0.97422537,-1.1052481,0.9210397,19.809981,-0.4170292)" + cx="0.5" + cy="17.838446" + fx="0.5" + fy="17.838446" + r="6.158739" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042" + id="radialGradient4112" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.8118545,0.97422537,-1.1052481,0.9210397,19.809981,-0.4170292)" + cx="0.5" + cy="17.838446" + fx="0.5" + fy="17.838446" + r="6.158739" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042" + id="radialGradient4115" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.8118545,0.97422537,-1.1052481,0.9210397,19.809981,-0.4170292)" + cx="0.5" + cy="17.838446" + fx="0.5" + fy="17.838446" + r="6.158739" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4042" + id="radialGradient4118" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.8118545,0.97422537,-1.1052481,0.9210397,19.809981,-0.4170292)" + cx="0.5" + cy="17.838446" + fx="0.5" + fy="17.838446" + r="6.158739" /> + <inkscape:perspective + id="perspective8198" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3657" + id="linearGradient3663" + x1="10.5" + y1="10.5" + x2="13.5" + y2="18.5" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3657" + id="linearGradient3669" + gradientUnits="userSpaceOnUse" + x1="10.5" + y1="10.5" + x2="13.5" + y2="18.5" + gradientTransform="translate(0,-3)" /> + <inkscape:perspective + id="perspective4821" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="15.99" + inkscape:cx="12.676896" + inkscape:cy="19.181199" + inkscape:current-layer="g4212" + showgrid="true" + inkscape:grid-bbox="true" + inkscape:document-units="px" + borderlayer="false" + inkscape:window-width="1280" + inkscape:window-height="962" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + showguides="true" + inkscape:guide-bbox="true" + inkscape:snap-bbox="true" + inkscape:snap-grids="false" + inkscape:snap-object-midpoints="true"> + <inkscape:grid + type="xygrid" + id="grid5700" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + dotted="true" + originx="2.5px" + originy="2.5px" /> + </sodipodi:namedview> + <metadata + id="metadata5697"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title>zoom 1 to 1</dc:title> + <dc:date>2011-03-11</dc:date> + <dc:creator> + <cc:Agent> + <dc:title>Robert Szczepanek</dc:title> + </cc:Agent> + </dc:creator> + <dc:rights> + <cc:Agent> + <dc:title>Robert Szczepanek</dc:title> + </cc:Agent> + </dc:rights> + <dc:subject> + <rdf:Bag> + <rdf:li>icon</rdf:li> + <rdf:li>gis</rdf:li> + </rdf:Bag> + </dc:subject> + <dc:coverage>GIS icons 0.2</dc:coverage> + <cc:license + rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" /> + </cc:Work> + <cc:License + rdf:about="http://creativecommons.org/licenses/by-sa/3.0/"> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Reproduction" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Distribution" /> + <cc:requires + rdf:resource="http://creativecommons.org/ns#Notice" /> + <cc:requires + rdf:resource="http://creativecommons.org/ns#Attribution" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> + <cc:requires + rdf:resource="http://creativecommons.org/ns#ShareAlike" /> + </cc:License> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="1" + style="display:inline" + transform="translate(0,-8)"> + <path + inkscape:connector-curvature="0" + sodipodi:nodetypes="czzzz" + id="path3838" + d="m 10.434781,12.175305 c 2.086957,-3.1302127 4.635391,-3.545545 6.260871,-2.08681 1.62548,1.458739 -2.086957,1.043405 -4.173914,3.130215 -2.086957,2.086806 0,6.260424 -2.086957,6.260424 -2.0869553,0 -2.0869553,-4.173618 0,-7.303829 z" + style="opacity:0.7;fill:#fcffff;fill-rule:evenodd;stroke:none;display:inline" /> + <g + id="g4212" + transform="translate(-3.6898061,2.0950594)"> + <path + sodipodi:nodetypes="cc" + inkscape:connector-curvature="0" + id="path5046" + d="m 15.130435,4.192698 0,28.166698" + style="fill:none;fill-rule:evenodd;stroke:#e40f0f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0.89999998;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#e40f0f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0.89999998;stroke-opacity:1" + d="m 29.213784,18.276047 -28.166698,0" + id="path4210" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> + <rect + style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" + id="rect4198-0" + width="7.8173862" + height="7.8173862" + x="11.221742" + y="14.367353" /> + <rect + style="fill:#e40f0f;fill-opacity:1;stroke:#e40f0f;stroke-width:0.12808283;stroke-linecap:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" + id="rect4198" + width="1.0392317" + height="1.0595345" + x="14.61082" + y="17.746279" /> + </g> + </g> +</svg> diff --git a/timeseriesviewer/ui/labelingdock.ui b/timeseriesviewer/ui/labelingdock.ui index a7326633874fc3674c96bc28a3175a3201826180..e81ca1a1f8cea74dc9c0490eea7cbb4b81cd62ee 100644 --- a/timeseriesviewer/ui/labelingdock.ui +++ b/timeseriesviewer/ui/labelingdock.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>400</width> - <height>256</height> + <height>261</height> </rect> </property> <property name="windowTitle"> @@ -76,6 +76,16 @@ </item> </layout> </item> + <item> + <widget class="QCheckBox" name="checkBox"> + <property name="text"> + <string>Edit layer</string> + </property> + </widget> + </item> + <item> + <widget class="QgsMapLayerComboBox" name="mMapLayerComboBox"/> + </item> <item> <widget class="QGroupBox" name="groupBoxLabelProperties"> <property name="enabled"> @@ -178,6 +188,13 @@ </layout> </widget> </widget> + <customwidgets> + <customwidget> + <class>QgsMapLayerComboBox</class> + <extends>QComboBox</extends> + <header>qgsmaplayercombobox.h</header> + </customwidget> + </customwidgets> <resources> <include location="resources.qrc"/> </resources> diff --git a/timeseriesviewer/ui/mapviewdefinition.ui b/timeseriesviewer/ui/mapviewdefinition.ui index 7accd6ef5cc4cb604eaabff5bf72340b23bca03e..937749676802a31b29272404ea1eda1fca6da715 100644 --- a/timeseriesviewer/ui/mapviewdefinition.ui +++ b/timeseriesviewer/ui/mapviewdefinition.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>453</width> - <height>123</height> + <height>159</height> </rect> </property> <property name="windowTitle"> @@ -127,6 +127,13 @@ </property> </widget> </item> + <item> + <widget class="QToolButton" name="btnShowCrosshair"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> <item> <widget class="QToolButton" name="btnVectorOverlayVisibility"> <property name="sizePolicy"> @@ -255,6 +262,21 @@ <string>Toogle visibility of overlayed vector file</string> </property> </action> + <action name="actionShowCrosshair"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/timeseriesviewer/icons/crosshair.png</normaloff>:/timeseriesviewer/icons/crosshair.png</iconset> + </property> + <property name="text"> + <string>Show Crosshair</string> + </property> + </action> </widget> <resources> <include location="resources.qrc"/> diff --git a/timeseriesviewer/ui/renderingdock.ui b/timeseriesviewer/ui/renderingdock.ui index 3ac2d56e2e1a99a6cb16ce319b7b0b010dc9bcf1..300b78942a38a65e4f2f1a224a163984635f2558 100644 --- a/timeseriesviewer/ui/renderingdock.ui +++ b/timeseriesviewer/ui/renderingdock.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>266</width> - <height>368</height> + <width>281</width> + <height>256</height> </rect> </property> <property name="windowTitle"> @@ -63,7 +63,7 @@ <property name="minimumSize"> <size> <width>0</width> - <height>100</height> + <height>0</height> </size> </property> <property name="maximumSize"> @@ -73,25 +73,19 @@ </size> </property> <property name="title"> - <string>Map Size</string> + <string>Map</string> </property> <property name="checkable"> <bool>false</bool> </property> - <layout class="QFormLayout" name="formLayout_6"> + <layout class="QGridLayout" name="gridLayout"> <property name="margin"> <number>6</number> </property> - <item row="0" column="0"> - <widget class="QLabel" name="label_7"> - <property name="minimumSize"> - <size> - <width>30</width> - <height>0</height> - </size> - </property> + <item row="1" column="0"> + <widget class="QLabel" name="label_6"> <property name="text"> - <string>width</string> + <string>height</string> </property> <property name="alignment"> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> @@ -144,16 +138,13 @@ </property> </widget> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_6"> + <item row="0" column="2"> + <widget class="QCheckBox" name="checkBoxKeepSubsetAspectRatio"> <property name="text"> - <string>height</string> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + <string>keep aspect ratio</string> </property> - <property name="buddy"> - <cstring>spinBoxSubsetSizeY</cstring> + <property name="checked"> + <bool>true</bool> </property> </widget> </item> @@ -197,86 +188,59 @@ </property> </widget> </item> - <item row="2" column="0" colspan="2"> - <widget class="QCheckBox" name="checkBoxKeepSubsetAspectRatio"> - <property name="text"> - <string>keep aspect ratio</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QgsCollapsibleGroupBox" name="gbCrosshair"> - <property name="title"> - <string>Show Crosshair</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="checked"> - <bool>false</bool> - </property> - <property name="saveCheckedState"> - <bool>true</bool> - </property> - <layout class="QFormLayout" name="formLayout_4"> <item row="0" column="0"> - <widget class="QLabel" name="label_13"> - <property name="enabled"> - <bool>false</bool> + <widget class="QLabel" name="label_7"> + <property name="minimumSize"> + <size> + <width>30</width> + <height>0</height> + </size> </property> <property name="text"> - <string>Type</string> + <string>width</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>spinBoxSubsetSizeY</cstring> </property> </widget> </item> - <item row="0" column="1"> - <widget class="QComboBox" name="cbCrosshairStyle"> - <property name="enabled"> - <bool>false</bool> + <item row="0" column="3"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - <item> - <property name="text"> - <string>Cross</string> - </property> - </item> - <item> - <property name="text"> - <string>Dot</string> - </property> - </item> - <item> - <property name="text"> - <string>Cross + Dot</string> - </property> - </item> - </widget> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_14"> + <item row="2" column="0"> + <widget class="QLabel" name="label_15"> <property name="enabled"> - <bool>false</bool> + <bool>true</bool> </property> <property name="text"> <string>Color</string> </property> </widget> </item> - <item row="1" column="1"> - <widget class="QgsColorButton" name="btnCrosshairColor"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="maximumSize"> - <size> - <width>50</width> - <height>16777215</height> - </size> + <item row="2" column="1"> + <widget class="QgsColorButton" name="btnMapCanvasColor"> + <property name="colorDialogTitle"> + <string>Select Map Canvas Background Color</string> + </property> + <property name="color"> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> </property> </widget> </item> diff --git a/timeseriesviewer/ui/resources.qrc b/timeseriesviewer/ui/resources.qrc index 0037313e1fe4c46d102111280c44fe7a994e6765..c500b553915b79b14d42d04364fccd7580fcc7bf 100644 --- a/timeseriesviewer/ui/resources.qrc +++ b/timeseriesviewer/ui/resources.qrc @@ -2,6 +2,7 @@ <qresource prefix="timeseriesviewer"> <file>icons/ActionIdentifyTimeSeries.png</file> <file>icons/CRS.png</file> + <file>icons/crosshair.png</file> <file>icons/IconTimeSeries.png</file> <file>icons/copyright_label.png</file> <file>icons/general.png</file> diff --git a/timeseriesviewer/ui/timeseriesviewer.ui b/timeseriesviewer/ui/timeseriesviewer.ui index 820ab4dd8c7c92cafe4b6d5122eef200691bdca2..b90cfcb292d7cca9836223d92cecf1c98b6be824 100644 --- a/timeseriesviewer/ui/timeseriesviewer.ui +++ b/timeseriesviewer/ui/timeseriesviewer.ui @@ -162,6 +162,7 @@ <addaction name="actionRefresh"/> <addaction name="actionAddMapView"/> <addaction name="menuPanels"/> + <addaction name="actionShowCrosshair"/> </widget> <widget class="QMenu" name="menuNavigation"> <property name="title"> @@ -546,6 +547,21 @@ <string>F5</string> </property> </action> + <action name="actionShowCrosshair"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/timeseriesviewer/icons/crosshair.png</normaloff>:/timeseriesviewer/icons/crosshair.png</iconset> + </property> + <property name="text"> + <string>Show Crosshair</string> + </property> + </action> </widget> <customwidgets> <customwidget> diff --git a/timeseriesviewer/ui/tmp.ui b/timeseriesviewer/ui/tmp.ui new file mode 100644 index 0000000000000000000000000000000000000000..8bcb13acdb3d06714c847d79045323535c82d6ae --- /dev/null +++ b/timeseriesviewer/ui/tmp.ui @@ -0,0 +1,993 @@ +<?xml version='1.0' encoding='UTF-8'?> +<ui version="4.0"> + <class>MapViewRenderSettings</class> + <widget class="QFrame" name="MapViewRenderSettings"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>260</width> + <height>135</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy vsizetype="Preferred" hsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>260</width> + <height>135</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>260</width> + <height>151</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="windowTitle"> + <string>RenderSettings</string> + </property> + <property name="title" stdset="0"> + <string/> + </property> + <property name="flat" stdset="0"> + <bool>false</bool> + </property> + <property name="checkable" stdset="0"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>2</number> + </property> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="labelTitle"> + <property name="font"> + <font> + <pointsize>8</pointsize> + <weight>50</weight> + <italic>false</italic> + <bold>false</bold> + <kerning>true</kerning> + </font> + </property> + <property name="toolTip"> + <string>Sensor name</string> + </property> + <property name="text"> + <string>Sensor Name</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cbRenderType"> + <property name="maximumSize"> + <size> + <width>60</width> + <height>16777215</height> + </size> + </property> + <item> + <property name="text"> + <string>Multi</string> + </property> + </item> + <item> + <property name="text"> + <string>Single</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="labelSummary"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>15</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string><RGB Bands></string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="minimumSize"> + <size> + <width>250</width> + <height>0</height> + </size> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="currentIndex"> + <number>1</number> + </property> + <widget class="QWidget" name="pageMultiBand"> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <property name="horizontalSpacing"> + <number>2</number> + </property> + <property name="verticalSpacing"> + <number>0</number> + </property> + <item row="1" column="1"> + <widget class="QSlider" name="sliderRed"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>6</number> + </property> + <property name="value"> + <number>3</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>1</number> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="labelMax"> + <property name="text"> + <string>Min</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLineEdit" name="tbRedMin"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLineEdit" name="tbGreenMin"> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QLineEdit" name="tbRedMax"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string>5000</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="labelMin"> + <property name="text"> + <string>Max</string> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QLineEdit" name="tbGreenMax"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string>5000</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="labelGreen"> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="text"> + <string>G</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSlider" name="sliderGreen"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>6</number> + </property> + <property name="value"> + <number>2</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelRed"> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="text"> + <string>R</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="labelBlue"> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string>B</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QSlider" name="sliderBlue"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>6</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>1</number> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QLineEdit" name="tbBlueMin"> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="3" column="3"> + <widget class="QLineEdit" name="tbBlueMax"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string>5000</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QFrame" name="btnBarMB"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Preferred" hsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QToolButton" name="btnDefaultMB"> + <property name="font"> + <font> + <pointsize>8</pointsize> + <italic>false</italic> + </font> + </property> + <property name="toolTip"> + <string>default band selection</string> + </property> + <property name="text"> + <string>D</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnTrueColor"> + <property name="font"> + <font> + <pointsize>8</pointsize> + <italic>false</italic> + </font> + </property> + <property name="toolTip"> + <string>red-green-blue (true colour)</string> + </property> + <property name="text"> + <string>TC</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnCIR"> + <property name="font"> + <font> + <pointsize>8</pointsize> + <italic>false</italic> + </font> + </property> + <property name="toolTip"> + <string>swIR-red-green (coloured infra-red)</string> + </property> + <property name="text"> + <string>CIR</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btn453"> + <property name="font"> + <font> + <pointsize>8</pointsize> + <italic>false</italic> + </font> + </property> + <property name="toolTip"> + <string>swIR-mwIR-red</string> + </property> + <property name="text"> + <string>453</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="pageSingleBand"> + <layout class="QGridLayout" name="gridLayout"> + <property name="horizontalSpacing"> + <number>2</number> + </property> + <property name="verticalSpacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item row="3" column="1"> + <widget class="QSlider" name="sliderSingleBand"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>6</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksAbove</enum> + </property> + <property name="tickInterval"> + <number>1</number> + </property> + </widget> + </item> + <item row="8" column="1" colspan="4"> + <widget class="QFrame" name="frame"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <property name="horizontalSpacing"> + <number>1</number> + </property> + <property name="verticalSpacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item row="1" column="2"> + <widget class="QComboBox" name="cbSingleBandColorRampType"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>2</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Type of color ramp</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QgsColorRampComboBox" name="cbSingleBandColorRamp"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="toolTip"> + <string>Color ramp</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QComboBox" name="cbSingleBandMode"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>2</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Color classification</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="1"> + <widget class="QFrame" name="bntBarSB"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QToolButton" name="btnSingleBandDef"> + <property name="font"> + <font> + <italic>false</italic> + </font> + </property> + <property name="text"> + <string>D</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnSingleBandBlue"> + <property name="toolTip"> + <string>Select band from visible blue</string> + </property> + <property name="text"> + <string>B</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnSingleBandGreen"> + <property name="toolTip"> + <string>Select band from visible green</string> + </property> + <property name="text"> + <string>G</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnSingleBandRed"> + <property name="toolTip"> + <string>Select band from visible red</string> + </property> + <property name="text"> + <string>R</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnSingleBandNIR"> + <property name="toolTip"> + <string>Select band from near infra-red</string> + </property> + <property name="text"> + <string>nIR</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnSingleBandSWIR"> + <property name="toolTip"> + <string>Select band from shortwave infra-red</string> + </property> + <property name="text"> + <string>swIR</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="3"> + <widget class="QLabel" name="labelMin_2"> + <property name="text"> + <string>Min</string> + </property> + </widget> + </item> + <item row="3" column="3"> + <widget class="QLineEdit" name="tbSingleBandMin"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="2" column="4"> + <widget class="QLabel" name="labelMax_2"> + <property name="text"> + <string>Max</string> + </property> + </widget> + </item> + <item row="3" column="4"> + <widget class="QLineEdit" name="tbSingleBandMax"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string>5000</string> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="spacing"> + <number>6</number> + </property> + <item> + <widget class="QToolButton" name="btnApplyStyle"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/timeseriesviewer/icons/mActionRefresh.png</normaloff>:/timeseriesviewer/icons/mActionRefresh.png</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnCopyStyle"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/timeseriesviewer/icons/mActionEditCopy.png</normaloff>:/timeseriesviewer/icons/mActionEditCopy.png</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnPasteStyle"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/timeseriesviewer/icons/mActionEditPaste.png</normaloff>:/timeseriesviewer/icons/mActionEditPaste.png</iconset> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + <property name="text"> + <string>Stretch</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBoxContrastEnhancement"> + <property name="sizePolicy"> + <sizepolicy vsizetype="Fixed" hsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> + </widget> + </item> + </layout> + </item> + </layout> + <action name="actionSetDefaultMB"> + <property name="text"> + <string>Def</string> + </property> + <property name="toolTip"> + <string>Set default band selection</string> + </property> + </action> + <action name="actionSetTrueColor"> + <property name="text"> + <string>True</string> + </property> + <property name="toolTip"> + <string>Set to true color (red-green-blue)</string> + </property> + </action> + <action name="actionSetCIR"> + <property name="text"> + <string>CIR</string> + </property> + <property name="toolTip"> + <string>Set to coloured infra red (swIR-red-green)</string> + </property> + </action> + <action name="actionSet453"> + <property name="text"> + <string>453</string> + </property> + <property name="toolTip"> + <string>Set to swIR-mwIR-red (Landsat 4-5-3)</string> + </property> + </action> + <action name="actionCopyStyle"> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/timeseriesviewer/icons/mActionEditCopy.png</normaloff>:/timeseriesviewer/icons/mActionEditCopy.png</iconset> + </property> + <property name="text"> + <string>Copy style</string> + </property> + <property name="toolTip"> + <string>Copy style to clipboard</string> + </property> + </action> + <action name="actionPasteStyle"> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/timeseriesviewer/icons/mActionEditPaste.png</normaloff>:/timeseriesviewer/icons/mActionEditPaste.png</iconset> + </property> + <property name="text"> + <string>Paste Style</string> + </property> + <property name="toolTip"> + <string>Paste style from clipboard</string> + </property> + </action> + <action name="actionApplyStyle"> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/timeseriesviewer/icons/mActionRefresh.png</normaloff>:/timeseriesviewer/icons/mActionRefresh.png</iconset> + </property> + <property name="text"> + <string>ApplySettings</string> + </property> + <property name="toolTip"> + <string>Apply Style</string> + </property> + </action> + <action name="actionSetR"> + <property name="text"> + <string>R</string> + </property> + </action> + <action name="actionSetG"> + <property name="text"> + <string>G</string> + </property> + </action> + <action name="actionSetB"> + <property name="text"> + <string>B</string> + </property> + </action> + <action name="actionSetNIR"> + <property name="text"> + <string>nIR</string> + </property> + </action> + <action name="actionSetSWIR"> + <property name="text"> + <string>swIR</string> + </property> + </action> + <action name="actionSetDefaultSB"> + <property name="text"> + <string>Def</string> + </property> + </action> + </widget> + <customwidgets> + <customwidget> + <class>QgsColorRampComboBox</class> + <extends>QComboBox</extends> + <header>qgis.gui</header> + </customwidget> + </customwidgets> + <resources> + <include location="resources.qrc"/> + </resources> + <connections> + <connection> + <sender>cbRenderType</sender> + <signal>currentIndexChanged(int)</signal> + <receiver>stackedWidget</receiver> + <slot>setCurrentIndex(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>107</x> + <y>19</y> + </hint> + <hint type="destinationlabel"> + <x>144</x> + <y>54</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/timeseriesviewer/ui/widgets.py b/timeseriesviewer/ui/widgets.py index c9d42b5b190f39ee90e8eeb101e86f51b25ac54d..5925b150d184c166845f52e25e10dde42c08cb71 100644 --- a/timeseriesviewer/ui/widgets.py +++ b/timeseriesviewer/ui/widgets.py @@ -42,262 +42,6 @@ class TsvScrollArea(QScrollArea): self.sigResized.emit() -class TsvMapCanvas(QgsMapCanvas): - from timeseriesviewer.main import SpatialExtent - saveFileDirectories = dict() - sigShowProfiles = pyqtSignal(QgsPoint, QgsCoordinateReferenceSystem) - sigSpatialExtentChanged = pyqtSignal(SpatialExtent) - - def __init__(self, tsdView, mapView, parent=None): - super(TsvMapCanvas, self).__init__(parent=parent) - from timeseriesviewer.main import TimeSeriesDatumView, MapView - assert isinstance(tsdView, TimeSeriesDatumView) - assert isinstance(mapView, MapView) - - #the canvas - self.setCrsTransformEnabled(True) - self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self.setCanvasColor(SETTINGS.value('CANVAS_BACKGROUND_COLOR', QColor(0, 0, 0))) - self.setContextMenuPolicy(Qt.DefaultContextMenu) - - self.extentsChanged.connect(lambda : self.sigSpatialExtentChanged.emit(self.spatialExtent())) - - self.scrollArea = tsdView.scrollArea - assert isinstance(self.scrollArea, TsvScrollArea) - self.scrollArea.sigResized.connect(self.setRenderMe) - self.scrollArea.horizontalScrollBar().valueChanged.connect(self.setRenderMe) - - self.tsdView = tsdView - self.mapView = mapView - self.vectorLayer = None - self.mapView.sigVectorLayerChanged.connect(self.refresh) - self.mapView.sigVectorVisibility.connect(self.refresh) - self.renderMe = False - self.setRenderMe() - - self.sensorView = self.mapView.sensorViews[self.tsdView.Sensor] - self.mapView.sigMapViewVisibility.connect(self.refresh) - self.mapView.sigSpatialExtentChanged.connect(self.setSpatialExtent) - self.referenceLayer = QgsRasterLayer(self.tsdView.TSD.pathImg) - QgsMapLayerRegistry.instance().addMapLayer(self.referenceLayer, False) - - self.MapCanvasLayers = [] - self.setMapLayers() - - self.sensorView.sigSensorRendererChanged.connect(self.setRenderer) - self.setRenderer(self.sensorView.layerRenderer()) - - - self.MAPTOOLS = dict() - self.MAPTOOLS['zoomOut'] = QgsMapToolZoom(self, True) - self.MAPTOOLS['zoomIn'] = QgsMapToolZoom(self, False) - self.MAPTOOLS['pan'] = QgsMapToolPan(self) - from timeseriesviewer.maptools import PointMapTool, PointLayersMapTool - mt = PointMapTool(self) - mt.sigCoordinateSelected.connect(self.sigShowProfiles.emit) - self.MAPTOOLS['identifyProfile'] = mt - #todo: self.MAPTOOLS['identifyMapLayers'] = - - def setMapLayers(self, *args): - del self.MapCanvasLayers[:] - if self.mapView.visibleVectorOverlay(): - #if necessary, register new vector layer - refLyr = self.mapView.vectorLayer - uri = refLyr.dataProvider().dataSourceUri() - - if self.vectorLayer is None or self.vectorLayer.dataProvider().dataSourceUri() != uri: - providerKey = refLyr.dataProvider().name() - baseName = os.path.basename(uri) - self.vectorLayer = QgsVectorLayer(uri, baseName, providerKey) - QgsMapLayerRegistry.instance().addMapLayer(self.vectorLayer, False) - - #update layer style - self.vectorLayer.setRendererV2(refLyr.rendererV2().clone()) - - self.MapCanvasLayers.append(QgsMapCanvasLayer(self.vectorLayer)) - if self.referenceLayer: - self.MapCanvasLayers.append(QgsMapCanvasLayer(self.referenceLayer)) - - self.setLayerSet(self.MapCanvasLayers) - - - - def refresh(self): - self.setMapLayers() - self.setRenderMe() - super(TsvMapCanvas, self).refresh() - - - def setRenderMe(self): - oldFlag = self.renderFlag() - - newFlag = self.visibleRegion().boundingRect().isValid() and self.isVisible() and self.tsdView.TSD.isVisible() - if oldFlag != newFlag: - self.setRenderFlag(newFlag) - #print((self.tsdView.TSD, self.renderFlag())) - #return b.isValid() - - def pixmap(self): - """ - Returns the current map image as pixmap - :return: - """ - return QPixmap(self.map().contentImage().copy()) - - def contextMenuEvent(self, event): - menu = QMenu() - # add general options - menu.addSeparator() - action = menu.addAction('Stretch using current Extent') - action.triggered.connect(self.stretchToCurrentExtent) - action = menu.addAction('Zoom to Layer') - action.triggered.connect(lambda : self.setExtent(SpatialExtent(self.referenceLayer.crs(),self.referenceLayer.extent()))) - menu.addSeparator() - - - action = menu.addAction('Copy to Clipboard') - action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.pixmap())) - m = menu.addMenu('Copy...') - action = menu.addAction('image path') - #action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.tsdView.TSD.pathImg)) - action = menu.addAction('style') - #action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.tsdView.TSD.pathImg)) - - m = menu.addMenu('Save as...') - action = m.addAction('PNG') - action.triggered.connect(lambda : self.saveMapImageDialog('PNG')) - action = m.addAction('JPEG') - action.triggered.connect(lambda: self.saveMapImageDialog('JPG')) - - from timeseriesviewer.main import QgisTsvBridge - bridge = QgisTsvBridge.instance() - if bridge: - assert isinstance(bridge, QgisTsvBridge) - action = m.addAction('Add layer to QGIS') - action = m.addAction('Import extent from QGIS') - action = m.addAction('Export extent to QGIS') - s = "" - - - - - menu.addSeparator() - TSD = self.tsdView.TSD - action = menu.addAction('Hide date') - action.triggered.connect(lambda : self.tsdView.TSD.setVisibility(False)) - action = menu.addAction('Remove date') - action.triggered.connect(lambda: TSD.timeSeries.removeDates([TSD])) - action = menu.addAction('Remove map view') - action.triggered.connect(lambda: self.mapView.sigRemoveMapView.emit(self.mapView)) - action = menu.addAction('Hide map view') - action.triggered.connect(lambda: self.mapView.sigHideMapView.emit()) - - - menu.exec_(event.globalPos()) - - def stretchToCurrentExtent(self): - results = dict() - se = self.spatialExtent() - - for l in self.layers(): - if isinstance(l, QgsRasterLayer): - r = l.renderer() - dp = l.dataProvider() - newRenderer = None - - extent = se.toCrs(l.crs()) - - assert isinstance(dp, QgsRasterDataProvider) - bands = None - if isinstance(r, QgsMultiBandColorRenderer): - - def getCE(band, ce): - stats = dp.bandStatistics(band, QgsRasterBandStats.All, extent, 500) - ce = QgsContrastEnhancement(ce) - ce.setMinimumValue(stats.minimumValue) - ce.setMaximumValue(stats.maximumValue) - return ce - - newRenderer = QgsMultiBandColorRenderer(None,r.redBand(), r.greenBand(), r.blueBand()) - newRenderer.setRedContrastEnhancement(getCE(r.redBand(), r.redContrastEnhancement())) - newRenderer.setGreenContrastEnhancement(getCE(r.greenBand(), r.greenContrastEnhancement())) - newRenderer.setBlueContrastEnhancement(getCE(r.blueBand(), r.blueContrastEnhancement())) - - results[self.tsdView.TSD.sensor] = newRenderer - elif isinstance(r, QgsSingleBandPseudoColorRenderer): - newRenderer = r.clone() - stats = dp.bandStatistics(newRenderer.band(), QgsRasterBandStats.All, extent, 500) - shader = newRenderer.shader() - newRenderer.setClassificationMax(stats.maximumValue) - newRenderer.setClassificationMin(stats.minimumValue) - shader.setMaximumValue(stats.maximumValue) - shader.setMinimumValue(stats.minimumValue) - s = "" - - if newRenderer is not None: - self.sensorView.setLayerRenderer(newRenderer) - s = "" - - def activateMapTool(self, key): - if key is None: - self.setMapTool(None) - elif key in self.MAPTOOLS.keys(): - self.setMapTool(self.MAPTOOLS[key]) - else: - from timeseriesviewer import dprint - dprint('unknown map tool key "{}"'.format(key)) - - def saveMapImageDialog(self, fileType): - lastDir = SETTINGS.value('CANVAS_SAVE_IMG_DIR', os.path.expanduser('~')) - path = jp(lastDir, '{}.{}.{}'.format(self.tsdView.TSD.date, self.mapView.title(), fileType.lower())) - - path = QFileDialog.getSaveFileName(self, 'Save map as {}'.format(fileType), path) - if len(path) > 0: - self.saveAsImage(path, None, fileType) - SETTINGS.setValue('CANVAS_SAVE_IMG_DIR', os.path.dirname(path)) - - - def setRenderer(self, renderer, targetLayerUri=None): - if targetLayerUri is None: - targetLayerUri = str(self.referenceLayer.source()) - - lyrs = [mcl.layer() for mcl in self.MapCanvasLayers if str(mcl.layer().source()) == targetLayerUri] - assert len(lyrs) <= 1 - for lyr in lyrs: - if isinstance(renderer, QgsMultiBandColorRenderer): - r = renderer.clone() - r.setInput(lyr.dataProvider()) - elif isinstance(renderer, QgsSingleBandPseudoColorRenderer): - r = renderer.clone() - #r = QgsSingleBandPseudoColorRenderer(None, renderer.band(), None) - r.setInput(lyr.dataProvider()) - cmin = renderer.classificationMin() - cmax = renderer.classificationMax() - r.setClassificationMin(cmin) - r.setClassificationMax(cmax) - #r.setShader(renderer.shader()) - s = "" - else: - raise NotImplementedError() - lyr.setRenderer(r) - - self.refresh() - - def setSpatialExtent(self, spatialExtent): - assert isinstance(spatialExtent, SpatialExtent) - if self.spatialExtent() != spatialExtent: - self.blockSignals(True) - self.setDestinationCrs(spatialExtent.crs()) - self.setExtent(spatialExtent) - self.blockSignals(False) - self.refresh() - - - def spatialExtent(self): - return SpatialExtent.fromMapCanvas(self) - - - class VerticalLabel(QLabel): def __init__(self, text, orientation='vertical', forceWidth=True): QLabel.__init__(self, text) @@ -621,6 +365,9 @@ class MapViewDefinitionUI(QGroupBox, loadUIFormClass(PATH_MAPVIEWDEFINITION_UI)) self.btnMapViewVisibility.setDefaultAction(self.actionToggleVisibility) self.btnApplyStyles.setDefaultAction(self.actionApplyStyles) self.btnVectorOverlayVisibility.setDefaultAction(self.actionToggleVectorVisibility) + self.btnShowCrosshair.setDefaultAction(self.actionShowCrosshair) + + self.actionToggleVisibility.toggled.connect(lambda: self.setVisibility(not self.actionToggleVisibility.isChecked())) self.actionToggleVectorVisibility.toggled.connect(lambda : self.sigVectorVisibility.emit(self.actionToggleVectorVisibility.isChecked())) @@ -919,7 +666,8 @@ class MapViewSensorSettings(QObject): rgb = self.rgb() text = 'RGB {}-{}-{}'.format(*rgb) - if self.sensor.wavelengthsDefined(): + + if False and self.sensor.wavelengthsDefined(): text += ' ({} {})'.format( ','.join(['{:0.2f}'.format(self.sensor.wavelengths[b-1]) for b in rgb]), self.sensor.wavelengthUnits) diff --git a/timeseriesviewer/utils.py b/timeseriesviewer/utils.py index fd0fca056eeb6ae11003ac5e4b329680909b3b1d..d8f4d7d3e158c2b03620066ea3063f7564dbe625 100644 --- a/timeseriesviewer/utils.py +++ b/timeseriesviewer/utils.py @@ -1,7 +1,195 @@ from collections import defaultdict +from qgis.core import * +from qgis.gui import * +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from osgeo import gdal + import weakref + +def fileSizeString(num, suffix='B', div=1000): + """ + Returns a human-readable file size string. + thanks to Fred Cirera + http://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size + :param num: number in bytes + :param suffix: 'B' for bytes by default. + :param div: divisor of num, 1000 by default. + :return: the file size string + """ + for unit in ['','K','M','G','T','P','E','Z']: + if abs(num) < div: + return "{:3.1f}{}{}".format(num, unit, suffix) + num /= div + return "{:.1f} {}{}".format(num, unit, suffix) + + + +class SpatialPoint(QgsPoint): + """ + Object to keep QgsPoint and QgsCoordinateReferenceSystem together + """ + + @staticmethod + def fromMapCanvasCenter(mapCanvas): + assert isinstance(mapCanvas, QgsMapCanvas) + crs = mapCanvas.mapSettings().destinationCrs() + return SpatialPoint(crs, mapCanvas.center()) + + def __init__(self, crs, *args): + assert isinstance(crs, QgsCoordinateReferenceSystem) + super(SpatialPoint, self).__init__(*args) + self.mCrs = crs + + def setCrs(self, crs): + assert isinstance(crs, QgsCoordinateReferenceSystem) + self.mCrs = crs + + def crs(self): + return self.mCrs + + def toCrs(self, crs): + assert isinstance(crs, QgsCoordinateReferenceSystem) + pt = QgsPoint(self) + if self.mCrs != crs: + trans = QgsCoordinateTransform(self.mCrs, crs) + pt = trans.transform(pt) + return SpatialPoint(crs, pt) + + def __copy__(self): + return SpatialExtent(self.crs(), QgsRectangle(self)) + + def __repr__(self): + return '{} {} {}'.format(self.x(), self.y(), self.crs().authid()) + + +def findParent(qObject, parentType, checkInstance = False): + parent = qObject.parent() + if checkInstance: + while parent != None and not isinstance(parent, parentType): + parent = parent.parent() + else: + while parent != None and type(parent) != parentType: + parent = parent.parent() + return parent + +class SpatialExtent(QgsRectangle): + """ + Object to keep QgsRectangle and QgsCoordinateReferenceSystem together + """ + @staticmethod + def fromMapCanvas(mapCanvas, fullExtent=False): + assert isinstance(mapCanvas, QgsMapCanvas) + + if fullExtent: + extent = mapCanvas.fullExtent() + else: + extent = mapCanvas.extent() + crs = mapCanvas.mapSettings().destinationCrs() + return SpatialExtent(crs, extent) + + @staticmethod + def fromLayer(mapLayer): + assert isinstance(mapLayer, QgsMapLayer) + extent = mapLayer.extent() + crs = mapLayer.crs() + return SpatialExtent(crs, extent) + + def __init__(self, crs, *args): + assert isinstance(crs, QgsCoordinateReferenceSystem) + super(SpatialExtent, self).__init__(*args) + self.mCrs = crs + + def setCrs(self, crs): + assert isinstance(crs, QgsCoordinateReferenceSystem) + self.mCrs = crs + + def crs(self): + return self.mCrs + + def toCrs(self, crs): + assert isinstance(crs, QgsCoordinateReferenceSystem) + box = QgsRectangle(self) + if self.mCrs != crs: + trans = QgsCoordinateTransform(self.mCrs, crs) + box = trans.transformBoundingBox(box) + return SpatialExtent(crs, box) + + def __copy__(self): + return SpatialExtent(self.crs(), QgsRectangle(self)) + + def combineExtentWith(self, *args): + if args is None: + return + elif isinstance(args[0], SpatialExtent): + extent2 = args[0].toCrs(self.crs()) + self.combineExtentWith(QgsRectangle(extent2)) + else: + super(SpatialExtent, self).combineExtentWith(*args) + + return self + + def setCenter(self, centerPoint, crs=None): + + if crs and crs != self.crs(): + trans = QgsCoordinateTransform(crs, self.crs()) + centerPoint = trans.transform(centerPoint) + + delta = centerPoint - self.center() + self.setXMaximum(self.xMaximum() + delta.x()) + self.setXMinimum(self.xMinimum() + delta.x()) + self.setYMaximum(self.yMaximum() + delta.y()) + self.setYMinimum(self.yMinimum() + delta.y()) + + return self + + def __cmp__(self, other): + if other is None: return 1 + s = "" + + def __eq__(self, other): + s = "" + + def __sub__(self, other): + raise NotImplementedError() + + def __mul__(self, other): + raise NotImplementedError() + + def upperRightPt(self): + return QgsPoint(*self.upperRight()) + + def upperLeftPt(self): + return QgsPoint(*self.upperLeft()) + + def lowerRightPt(self): + return QgsPoint(*self.lowerRight()) + + def lowerLeftPt(self): + return QgsPoint(*self.lowerLeft()) + + + def upperRight(self): + return self.xMaximum(), self.yMaximum() + + def upperLeft(self): + return self.xMinimum(), self.yMaximum() + + def lowerRight(self): + return self.xMaximum(), self.yMinimum() + + def lowerLeft(self): + return self.xMinimum(), self.yMinimum() + + + def __repr__(self): + + return '{} {} {}'.format(self.upperLeft(), self.lowerRight(), self.crs().authid()) + + class KeepRefs(object): __refs__ = defaultdict(list) def __init__(self): @@ -12,4 +200,46 @@ class KeepRefs(object): for inst_ref in cls.__refs__[cls]: inst = inst_ref() if inst is not None: - yield inst \ No newline at end of file + yield inst + + + +def filterSubLayers(filePaths, subLayerEndings): + """ + Returns sub layers endings from all gdal Datasets within filePaths + :param filePaths: + :param subLayerEndings: + :return: + """ + results = [] + if len(subLayerEndings) == 0: + return filePaths[:] + + for path in filePaths: + try: + ds = gdal.Open(path) + if ds.RasterCount == 0: + for s in ds.GetSubDatasets(): + for ending in subLayerEndings: + if s[0].endswith(ending): + results.append(s[0]) + else: + results.append(path) + except: + pass + return results + +def getSubLayerEndings(files): + subLayerEndings = [] + for file in files: + try: + ds = gdal.Open(file) + for subLayer in ds.GetSubDatasets(): + ending = subLayer[0].split(':')[-2:] + if ending not in subLayerEndings: + subLayerEndings.append(':'.join(ending)) + except: + s = "" + pass + + return subLayerEndings diff --git a/timeseriesviewerplugin.py b/timeseriesviewerplugin.py index e6336744a33e63d730fd71dfbe5ab5c4ace7e4ec..e2369f21f4f5bf18c07bbe5d68fd6edbf53a8a55 100644 --- a/timeseriesviewerplugin.py +++ b/timeseriesviewerplugin.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import inspect import os import six @@ -7,7 +6,8 @@ import sys import importlib import re import site - +import logging +logger = logging.getLogger(__name__) from qgis.gui import * from qgis.core import * from PyQt4.QtCore import * @@ -40,7 +40,7 @@ class TimeSeriesViewerPlugin: import timeseriesviewer # init main UI - from timeseriesviewer import dprint, DIR_UI, jp + from timeseriesviewer import DIR_UI, jp icon = QIcon(jp(DIR_UI, *['icons', 'icon.png'])) action = QAction(icon, 'HUB Time Series Viewer', self.iface) action.triggered.connect(self.run) @@ -60,9 +60,8 @@ class TimeSeriesViewerPlugin: self.tsv.run() def unload(self): - from timeseriesviewer import dprint from timeseriesviewer.main import TimeSeriesViewer - dprint('UNLOAD TimeSeriesViewer Plugin') + for action in self.toolbarActions: print(action)