# -*- coding: utf-8 -*- """ /*************************************************************************** HUB TimeSeriesViewer ------------------- begin : 2015-08-20 git sha : $Format:%H$ copyright : (C) 2017 by HU-Berlin email : benjamin.jakimow@geo.hu-berlin.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ """ # noinspection PyPep8Naming from __future__ import absolute_import, unicode_literals import os, sys, re, fnmatch, collections, copy, traceback, six, bisect import logging logger = logging.getLogger(__name__) from qgis.core import * from PyQt4.QtXml import * from PyQt4.QtCore import * from PyQt4.QtGui import * import numpy as np from timeseriesviewer.utils import * from timeseriesviewer.timeseries import SensorInstrument, TimeSeriesDatum, TimeSeries from timeseriesviewer.ui.docks import TsvDockWidgetBase, loadUi from timeseriesviewer.ui.widgets import TsvMimeDataUtils, maxWidgetSizes from timeseriesviewer.ui.mapviewscrollarea import MapViewScrollArea from timeseriesviewer.mapcanvas import MapCanvas from timeseriesviewer.crosshair import CrosshairStyle class MapViewUI(QFrame, loadUi('mapviewdefinition.ui')): def __init__(self, parent=None): super(MapViewUI, self).__init__(parent) self.setupUi(self) self.mSensors = collections.OrderedDict() m = QMenu(self.btnToggleCrosshair) m.addAction(self.actionSetCrosshairStyle) #a = m.addAction('Set Crosshair Style') self.btnToggleCrosshair.setMenu(m) #connect the QActions with the QgsCollapsibleGroupBoxes self.gbVectorRendering.toggled.connect(self.actionToggleVectorVisibility.toggle) self.gbRasterRendering.toggled.connect(self.actionToggleRasterVisibility.toggle) self.actionToggleVectorVisibility.toggled.connect(self.gbVectorRendering.setChecked) self.actionToggleRasterVisibility.toggled.connect(self.gbRasterRendering.setChecked) #self.actionToggleRasterVisibility.toggled.connect(self.dummy) self.btnToggleCrosshair.setDefaultAction(self.actionToggleCrosshairVisibility) self.btnToggleMapViewVisibility.setDefaultAction(self.actionToggleMapViewVisibility) def dummy(self, *args): print((self.sender(), args)) print(args) def addSensor(self, sensor): assert isinstance(sensor, SensorInstrument) w = MapViewSensorSettings(sensor) #sizePolicy = QSizePolicy(QSize) #w.ui. l = self.renderSettingsLayout assert sensor not in self.mSensors.keys() lastWidgetIndex = l.count()-1 l.insertWidget(lastWidgetIndex, w.ui) self.mSensors[sensor] = w #self.resize(self.sizeHint()) return w def removeSensor(self, sensor): assert isinstance(sensor, SensorInstrument) sensorSettings = self.mSensors.pop(sensor) assert isinstance(sensorSettings, MapViewSensorSettings) l = self.renderSettingsLayout l.removeWidget(sensorSettings.ui) sensorSettings.ui.close() #self.resize(self.sizeHint()) class MapView(QObject): sigRemoveMapView = pyqtSignal(object) sigMapViewVisibility = pyqtSignal(bool) #sigVectorVisibility = pyqtSignal(bool) #sigRasterVisibility = pyqtSignal(bool) sigTitleChanged = pyqtSignal([str],[unicode]) sigSensorRendererChanged = pyqtSignal(SensorInstrument, QgsRasterRenderer) sigCrosshairStyleChanged = pyqtSignal(CrosshairStyle) sigShowCrosshair = pyqtSignal(bool) sigVectorLayerChanged = pyqtSignal() sigShowProfiles = pyqtSignal(SpatialPoint) def __init__(self, mapViewCollection, name='Map View', recommended_bands=None, parent=None): super(MapView, self).__init__() assert isinstance(mapViewCollection, MapViewCollectionDock) self.ui = MapViewUI(mapViewCollection.stackedWidget) self.ui.show() self.ui.cbQgsVectorLayer.setFilters(QgsMapLayerProxyModel.VectorLayer) self.ui.cbQgsVectorLayer.layerChanged.connect(self.setVectorLayer) self.ui.tbName.textChanged.connect(self.sigTitleChanged) from timeseriesviewer.crosshair import getCrosshairStyle self.ui.actionSetCrosshairStyle.triggered.connect( lambda : self.setCrosshairStyle(getCrosshairStyle( parent=self.ui, crosshairStyle=self.mCrosshairStyle)) ) self.mapViewCollection = mapViewCollection self.sensorViews = collections.OrderedDict() self.mVectorLayer = None self.setVectorLayer(None) self.mCrosshairStyle = CrosshairStyle() self.mShowCrosshair = True self.mIsVisible = True self.mVectorsVisible = True self.mRastersVisible = True self.ui.actionToggleVectorVisibility.setChecked(True) self.ui.actionToggleVectorVisibility.toggled.connect(self.setVectorVisibility) self.ui.actionToggleRasterVisibility.setChecked(True) self.ui.actionToggleRasterVisibility.toggled.connect(self.setRasterVisibility) self.ui.actionToggleCrosshairVisibility.toggled.connect(self.setShowCrosshair) self.ui.actionToggleMapViewVisibility.toggled.connect(lambda b: self.setIsVisible(not b)) self.ui.actionToggleVectorVisibility.setChecked(True) self.ui.actionToggleRasterVisibility.setChecked(True) self.setTitle(name) #forward actions with reference to this band view def dummy(self, *args): print(args) def setIsVisible(self, b): assert isinstance(b, bool) changed = b != self.mIsVisible self.mIsVisible = b for mapCanvas in self.mapCanvases(): assert isinstance(mapCanvas, MapCanvas) mapCanvas.setVisible(b) if changed: self.sigMapViewVisibility.emit(b) def isVisible(self): return self.mIsVisible def mapCanvases(self): m = [] for sensor, sensorView in self.sensorViews.items(): m.extend(sensorView.mapCanvases()) return m def vectorLayerRenderer(self): if isinstance(self.mVectorLayer, QgsVectorLayer): return self.mVectorLayer.rendererV2() return None def setVectorLayerRenderer(self, renderer): if isinstance(renderer, QgsFeatureRendererV2) and \ isinstance(self.mVectorLayer, QgsVectorLayer): self.mVectorLayer.setRendererV2(renderer) def setVectorLayer(self, lyr): if isinstance(lyr, QgsVectorLayer): #add vector layer self.mVectorLayer = lyr self.mVectorLayer.rendererChanged.connect(self.sigVectorLayerChanged) for mapCanvas in self.mapCanvases(): assert isinstance(mapCanvas, MapCanvas) mapCanvas.layerModel().setVectorLayerSources([self.mVectorLayer]) #mapCanvas.setLayers([l for l in mapCanvas.layers() if isinstance(l, QgsRasterLayer)]) #mapCanvas.setLazyVectorSources([lyr]) mapCanvas.refresh() else: #remove vector layers self.mVectorLayer = None for mapCanvas in self.mapCanvases(): mapCanvas.layerModel().setVectorLayerSources([]) #mapCanvas.setLayers([l for l in mapCanvas.mLayers if not isinstance(l, QgsVectorLayer)]) mapCanvas.refresh() self.sigVectorLayerChanged.emit() def applyStyles(self): for sensorView in self.sensorViews.values(): sensorView.applyStyle() def setTitle(self, title): old = self.title() if old != title: self.ui.tbName.setText(title) def title(self): return self.ui.tbName.text() def refreshMapView(self, *args): for mapCanvas in self.mapCanvases(): assert isinstance(mapCanvas, MapCanvas) mapCanvas.refresh() def setCrosshairStyle(self, crosshairStyle): if isinstance(crosshairStyle, CrosshairStyle): old = self.mCrosshairStyle self.mCrosshairStyle = crosshairStyle if old != self.mCrosshairStyle: self.sigCrosshairStyleChanged.emit(self.mCrosshairStyle) def setHighlighted(self, b=True, timeout=1000): styleOn = """.MapCanvas { border: 4px solid red; border-radius: 4px; }""" styleOff = """""" if b is True: for mapCanvas in self.mapCanvases(): mapCanvas.setStyleSheet(styleOn) if timeout > 0: QTimer.singleShot(timeout, lambda : self.setHighlighted(False)) else: for mapCanvas in self.mapCanvases(): mapCanvas.setStyleSheet(styleOff) def setShowCrosshair(self, b): assert isinstance(b, bool) self.mShowCrosshair = b self.sigShowCrosshair.emit(b) def showCrosshair(self): return self.mShowCrosshair and self.mCrosshairStyle is not None def rasterVisibility(self): return self.mRastersVisible def vectorVisibility(self): return self.mVectorsVisible def setRasterVisibility(self, b): assert isinstance(b, bool) if self.rasterVisibility() != b: self.mRastersVisible = b self.ui.actionToggleRasterVisibility.setChecked(b) for mapCanvas in self.mapCanvases(): assert isinstance(mapCanvas, MapCanvas) mapCanvas.layerModel().setRasterLayerVisibility(b) mapCanvas.refresh() #self.sigRasterVisibility.emit(b) def setVectorVisibility(self, b): assert isinstance(b, bool) if self.vectorVisibility() != b: self.mVectorsVisible = b self.ui.actionToggleVectorVisibility.setChecked(b) for mapCanvas in self.mapCanvases(): assert isinstance(mapCanvas, MapCanvas) mapCanvas.layerModel().setVectorLayerVisibility(b) mapCanvas.refresh() def removeSensor(self, sensor): assert sensor in self.sensorViews.keys() self.sensorViews.pop(sensor) self.ui.removeSensor(sensor) return True def hasSensor(self, sensor): assert type(sensor) is SensorInstrument return sensor in self.sensorViews.keys() def registerMapCanvas(self, sensor, mapCanvas): from timeseriesviewer.mapcanvas import MapCanvas assert isinstance(mapCanvas, MapCanvas) assert isinstance(sensor, SensorInstrument) sensorView = self.sensorViews[sensor] assert isinstance(sensorView, MapViewSensorSettings) sensorView.registerMapCanvas(mapCanvas) #register signals sensor specific signals mapCanvas.setRenderer(sensorView.rasterLayerRenderer()) mapCanvas.setRenderer(self.vectorLayerRenderer()) #register non-sensor specific signals for this mpa view self.sigMapViewVisibility.connect(mapCanvas.refresh) self.sigCrosshairStyleChanged.connect(mapCanvas.setCrosshairStyle) self.sigShowCrosshair.connect(mapCanvas.setShowCrosshair) self.sigVectorLayerChanged.connect(mapCanvas.refresh) # self.sigVectorVisibility.connect(mapCanvas.refresh) def addSensor(self, sensor): """ :param sensor: :return: """ assert type(sensor) is SensorInstrument assert sensor not in self.sensorViews.keys() #w.showSensorName(False) w = self.ui.addSensor(sensor) w.sigSensorRendererChanged.connect(self.onSensorRenderingChanged) self.sensorViews[sensor] = w s ="" def onSensorRenderingChanged(self, renderer): sensorSettings = self.sender() assert isinstance(sensorSettings, MapViewSensorSettings) for mapCanvas in sensorSettings.mapCanvases(): mapCanvas.setRenderer(renderer) #mapCanvas.refresh() def getSensorWidget(self, sensor): assert type(sensor) is SensorInstrument return self.sensorViews[sensor] class MapViewRenderSettingsUI(QgsCollapsibleGroupBox, loadUi('mapviewrendersettings.ui')): def __init__(self, parent=None): """Constructor.""" super(MapViewRenderSettingsUI, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) self.btnDefaultMB.setDefaultAction(self.actionSetDefaultMB) self.btnTrueColor.setDefaultAction(self.actionSetTrueColor) self.btnCIR.setDefaultAction(self.actionSetCIR) self.btn453.setDefaultAction(self.actionSet453) self.btnSingleBandDef.setDefaultAction(self.actionSetDefaultSB) self.btnSingleBandBlue.setDefaultAction(self.actionSetB) self.btnSingleBandGreen.setDefaultAction(self.actionSetG) self.btnSingleBandRed.setDefaultAction(self.actionSetR) self.btnSingleBandNIR.setDefaultAction(self.actionSetNIR) self.btnSingleBandSWIR.setDefaultAction(self.actionSetSWIR) self.btnPasteStyle.setDefaultAction(self.actionPasteStyle) self.btnCopyStyle.setDefaultAction(self.actionCopyStyle) self.btnApplyStyle.setDefaultAction(self.actionApplyStyle) class MapViewSensorSettings(QObject): """ Describes the rendering of images of one Sensor """ sigSensorRendererChanged = pyqtSignal(QgsRasterRenderer) def __init__(self, sensor, parent=None): """Constructor.""" super(MapViewSensorSettings, self).__init__(parent) from timeseriesviewer.timeseries import SensorInstrument assert isinstance(sensor, SensorInstrument) self.sensor = sensor self.ui = MapViewRenderSettingsUI(parent) self.ui.create() self.ui.stackedWidget.currentChanged.connect(self.updateUi) self.sensor.sigNameChanged.connect(self.onSensorNameChanged) self.onSensorNameChanged(self.sensor.name()) self.mMapCanvases = [] self.ui.bandNames = sensor.bandNames self.multiBandMinValues = [self.ui.tbRedMin, self.ui.tbGreenMin, self.ui.tbBlueMin] self.multiBandMaxValues = [self.ui.tbRedMax, self.ui.tbGreenMax, self.ui.tbBlueMax] self.multiBandSliders = [self.ui.sliderRed, self.ui.sliderGreen, self.ui.sliderBlue] for tb in self.multiBandMinValues + self.multiBandMaxValues + [self.ui.tbSingleBandMin, self.ui.tbSingleBandMax]: assert isinstance(tb, QLineEdit) tb.setValidator(QDoubleValidator()) tb.textChanged.connect(self.onValuesChanged) for sl in self.multiBandSliders + [self.ui.sliderSingleBand]: sl.setMinimum(1) sl.setMaximum(sensor.nb) sl.valueChanged.connect(self.updateUi) self.ceAlgs = collections.OrderedDict() self.ceAlgs["No enhancement"] = QgsContrastEnhancement.NoEnhancement self.ceAlgs["Stretch to MinMax"] = QgsContrastEnhancement.StretchToMinimumMaximum self.ceAlgs["Stretch and clip to MinMax"] = QgsContrastEnhancement.StretchAndClipToMinimumMaximum self.ceAlgs["Clip to MinMax"] = QgsContrastEnhancement.ClipToMinimumMaximum self.colorRampType = collections.OrderedDict() self.colorRampType['Interpolated'] = QgsColorRampShader.INTERPOLATED self.colorRampType['Discrete'] = QgsColorRampShader.DISCRETE self.colorRampType['Exact'] = QgsColorRampShader.EXACT self.colorRampClassificationMode = collections.OrderedDict() self.colorRampClassificationMode['Continuous'] = 1 self.colorRampClassificationMode['Equal Interval'] = 2 self.colorRampClassificationMode['Quantile'] = 3 def populateCombobox(cb, d): for key, value in d.items(): cb.addItem(key, value) cb.setCurrentIndex(0) populateCombobox(self.ui.comboBoxContrastEnhancement, self.ceAlgs) populateCombobox(self.ui.cbSingleBandColorRampType, self.colorRampType) populateCombobox(self.ui.cbSingleBandMode, self.colorRampClassificationMode) self.ui.cbSingleBandColorRamp.populate(QgsStyleV2.defaultStyle()) nb = self.sensor.nb lyr = QgsRasterLayer(self.sensor.pathImg) #define default renderers: bands = [min([b,nb-1]) for b in range(3)] extent = lyr.extent() bandStats = [lyr.dataProvider().bandStatistics(b, QgsRasterBandStats.All, extent, 500) for b in range(nb)] def createEnhancement(bandIndex): bandIndex = min([nb - 1, bandIndex]) e = QgsContrastEnhancement(self.sensor.bandDataType) e.setMinimumValue(bandStats[bandIndex].Min) e.setMaximumValue(bandStats[bandIndex].Max) e.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum) return e self.defaultMB = QgsMultiBandColorRenderer(lyr.dataProvider(), bands[0], bands[1], bands[2]) self.defaultMB.setRedContrastEnhancement(createEnhancement(bands[0])) self.defaultMB.setGreenContrastEnhancement(createEnhancement(bands[1])) self.defaultMB.setBlueContrastEnhancement(createEnhancement(bands[2])) self.defaultSB = QgsSingleBandPseudoColorRenderer(lyr.dataProvider(), 0, None) colorRamp = self.ui.cbSingleBandColorRamp.currentColorRamp() #fix: QGIS 3.0 constructor shaderFunc = QgsColorRampShader(bandStats[0].Min, bandStats[0].Max) shaderFunc.setColorRampType(QgsColorRampShader.INTERPOLATED) shaderFunc.setClip(True) nSteps = 5 colorRampItems = [] diff = bandStats[0].Max - bandStats[0].Min for i in range(nSteps+1): f = float(i) / nSteps color = colorRamp.color(f) value = bandStats[0].Min + diff * f colorRampItems.append(QgsColorRampShader.ColorRampItem(value, color)) shaderFunc.setColorRampItemList(colorRampItems) shader = QgsRasterShader() shader.setMaximumValue(bandStats[0].Max) shader.setMinimumValue(bandStats[0].Min) shader.setRasterShaderFunction(shaderFunc) self.defaultSB.setShader(shader) self.defaultSB.setClassificationMin(shader.minimumValue()) self.defaultSB.setClassificationMax(shader.maximumValue()) #init connect signals self.ui.actionSetDefaultMB.triggered.connect(lambda : self.setBandSelection('defaultMB')) self.ui.actionSetTrueColor.triggered.connect(lambda: self.setBandSelection('TrueColor')) self.ui.actionSetCIR.triggered.connect(lambda: self.setBandSelection('CIR')) self.ui.actionSet453.triggered.connect(lambda: self.setBandSelection('453')) self.ui.actionSetDefaultSB.triggered.connect(lambda: self.setBandSelection('defaultSB')) self.ui.actionSetB.triggered.connect(lambda: self.setBandSelection('B')) self.ui.actionSetG.triggered.connect(lambda: self.setBandSelection('G')) self.ui.actionSetR.triggered.connect(lambda: self.setBandSelection('R')) self.ui.actionSetNIR.triggered.connect(lambda: self.setBandSelection('nIR')) self.ui.actionSetSWIR.triggered.connect(lambda: self.setBandSelection('swIR')) self.ui.actionApplyStyle.triggered.connect(self.applyStyle) self.ui.actionCopyStyle.triggered.connect(lambda : QApplication.clipboard().setMimeData(self.mimeDataStyle())) self.ui.actionPasteStyle.triggered.connect(lambda : self.pasteStyleFromClipboard()) #self.ui.stackedWidget if not self.sensor.wavelengthsDefined(): self.ui.btnTrueColor.setEnabled(False) self.ui.btnCIR.setEnabled(False) self.ui.btn453.setEnabled(False) self.ui.btnSingleBandBlue.setEnabled(False) self.ui.btnSingleBandGreen.setEnabled(False) self.ui.btnSingleBandRed.setEnabled(False) self.ui.btnSingleBandNIR.setEnabled(False) self.ui.btnSingleBandSWIR.setEnabled(False) #apply recent or default renderer renderer = lyr.renderer() #set defaults self.setLayerRenderer(self.defaultSB) self.setLayerRenderer(self.defaultMB) self.mLastRenderer = renderer if type(renderer) in [QgsMultiBandColorRenderer, QgsSingleBandPseudoColorRenderer]: self.setLayerRenderer(renderer) QApplication.clipboard().dataChanged.connect(self.onClipboardChange) self.onClipboardChange() def mapCanvases(self): return self.mMapCanvases[:] def registerMapCanvas(self, mapCanvas): assert isinstance(mapCanvas, MapCanvas) self.mMapCanvases.append(mapCanvas) mapCanvas.sigChangeSVRequest.connect(self.onMapCanvasRendererChangeRequest) def onSensorNameChanged(self, newName): self.ui.setTitle(self.sensor.name()) self.ui.actionApplyStyle.setToolTip('Apply style to all map view images from "{}"'.format(self.sensor.name())) def pasteStyleFromClipboard(self): utils = TsvMimeDataUtils(QApplication.clipboard().mimeData()) if utils.hasRasterStyle(): renderer = utils.rasterStyle(self.sensor.bandDataType) if renderer is not None: self.setLayerRenderer(renderer) def applyStyle(self, *args): r = self.rasterLayerRenderer() for mapCanvas in self.mMapCanvases: assert isinstance(mapCanvas, MapCanvas) mapCanvas.setRenderer(r) def onClipboardChange(self): utils = TsvMimeDataUtils(QApplication.clipboard().mimeData()) self.ui.btnPasteStyle.setEnabled(utils.hasRasterStyle()) def onMapCanvasRendererChangeRequest(self, mapCanvas, renderer): self.setLayerRenderer(renderer) def setBandSelection(self, key): if key == 'defaultMB': bands = [self.defaultMB.redBand(), self.defaultMB.greenBand(), self.defaultMB.blueBand()] elif key == 'defaultSB': bands = [self.defaultSB.band()] else: if key in ['R','G','B','nIR','swIR']: colors = [key] elif key == 'TrueColor': colors = ['R','G','B'] elif key == 'CIR': colors = ['nIR', 'R', 'G'] elif key == '453': colors = ['nIR','swIR', 'R'] bands = [self.sensor.bandClosestToWavelength(c) for c in colors] if len(bands) == 1: self.ui.sliderSingleBand.setValue(bands[0]+1) elif len(bands) == 3: for i, b in enumerate(bands): self.multiBandSliders[i].setValue(b+1) SignalizeImmediately = True def onValuesChanged(self, text): styleValid = "" styleInValid = """.QLineEdit {border-color:red; border-style: outset; border-width: 2px; background-color: yellow } """ w = self.sender() if isinstance(w, QLineEdit): validator = w.validator() assert isinstance(validator, QDoubleValidator) res = validator.validate(text, 0) if res[0] == QDoubleValidator.Acceptable: w.setStyleSheet(styleValid) else: w.setStyleSheet(styleInValid) def updateUi(self, *args): cw = self.ui.stackedWidget.currentWidget() text = '' if cw == self.ui.pageMultiBand: text = 'Multiband({} {} {})'.format( self.ui.sliderRed.value(), self.ui.sliderGreen.value(), self.ui.sliderBlue.value() ) elif cw == self.ui.pageSingleBand: text = 'Singleband({})'.format(self.ui.sliderSingleBand.value()) text = '{} - {}'.format(self.sensor.name(), text) self.ui.setTitle(text) def setLayerRenderer(self, renderer): ui = self.ui assert isinstance(renderer, QgsRasterRenderer) from timeseriesviewer.utils import niceNumberString updated = False if isinstance(renderer, QgsMultiBandColorRenderer): self.ui.cbRenderType.setCurrentIndex(0) #self.ui.stackedWidget.setcurrentWidget(self.ui.pageMultiBand) for s in self.multiBandSliders: s.blockSignals(True) ui.sliderRed.setValue(renderer.redBand()) ui.sliderGreen.setValue(renderer.greenBand()) ui.sliderBlue.setValue(renderer.blueBand()) for s in self.multiBandSliders: s.blockSignals(False) ceRed = renderer.redContrastEnhancement() ceGreen = renderer.greenContrastEnhancement() ceBlue = renderer.blueContrastEnhancement() if ceRed is None: ceRed = ceGreen = ceBlue = QgsContrastEnhancement(self.sensor.bandDataType) s = "" for i, ce in enumerate([ceRed, ceGreen, ceBlue]): vMin = ce.minimumValue() vMax = ce.maximumValue() self.multiBandMinValues[i].setText(niceNumberString(vMin)) self.multiBandMaxValues[i].setText(niceNumberString(vMax)) idx = self.ceAlgs.values().index(ceRed.contrastEnhancementAlgorithm()) ui.comboBoxContrastEnhancement.setCurrentIndex(idx) updated = True if isinstance(renderer, QgsSingleBandPseudoColorRenderer): self.ui.cbRenderType.setCurrentIndex(1) #self.ui.stackedWidget.setCurrentWidget(self.ui.pageSingleBand) self.ui.sliderSingleBand.setValue(renderer.band()) shader = renderer.shader() cmin = shader.minimumValue() cmax = shader.maximumValue() self.ui.tbSingleBandMin.setText(str(cmin)) self.ui.tbSingleBandMax.setText(str(cmax)) shaderFunc = shader.rasterShaderFunction() self.ui.cbSingleBandColorRampType.setCurrentIndex(shaderFunc.colorRampType()) updated = True self.updateUi() if updated: self.mLastRenderer = self.rasterLayerRenderer() if updated and MapViewSensorSettings.SignalizeImmediately: self.sigSensorRendererChanged.emit(renderer.clone()) self.applyStyle() def mimeDataStyle(self): mimeData = QMimeData() r = self.rasterLayerRenderer() if isinstance(r, QgsRasterRenderer): doc = QDomDocument() lyr = QgsRasterLayer(self.sensor.pathImg) lyr.setRenderer(self.rasterLayerRenderer()) err = '' lyr.exportNamedStyle(doc, err) if len(err) == 0: mimeData.setData('application/qgis.style', doc.toByteArray()) mimeData.setText(doc.toString()) return mimeData return mimeData def currentComboBoxItem(self, cb): d = cb.itemData(cb.currentIndex(), Qt.UserRole) return d def rasterLayerRenderer(self): ui = self.ui r = None if ui.stackedWidget.currentWidget() == ui.pageMultiBand: r = self.rasterRendererMultiBand(ui) if ui.stackedWidget.currentWidget() == ui.pageSingleBand: r = self.rasterRendererSingleBand(ui) return r def rasterRendererMultiBand(self, ui): r = QgsMultiBandColorRenderer(None, ui.sliderRed.value(), ui.sliderGreen.value(), ui.sliderBlue.value()) i = self.ui.comboBoxContrastEnhancement.currentIndex() alg = self.ui.comboBoxContrastEnhancement.itemData(i) if alg == QgsContrastEnhancement.NoEnhancement: r.setRedContrastEnhancement(None) r.setGreenContrastEnhancement(None) r.setBlueContrastEnhancement(None) else: rgbEnhancements = [] for i in range(3): e = QgsContrastEnhancement(self.sensor.bandDataType) minmax = [float(self.multiBandMinValues[i].text()), float(self.multiBandMaxValues[i].text())] cmin = min(minmax) cmax = max(minmax) e.setMinimumValue(cmin) e.setMaximumValue(cmax) e.setContrastEnhancementAlgorithm(alg) rgbEnhancements.append(e) r.setRedContrastEnhancement(rgbEnhancements[0]) r.setGreenContrastEnhancement(rgbEnhancements[1]) r.setBlueContrastEnhancement(rgbEnhancements[2]) return r def rasterRendererSingleBand(self, ui): r = QgsSingleBandPseudoColorRenderer(None, ui.sliderSingleBand.value(), None) minmax = [float(ui.tbSingleBandMin.text()), float(ui.tbSingleBandMax.text())] cmin = min(minmax) cmax = max(minmax) r.setClassificationMin(cmin) r.setClassificationMax(cmax) colorRamp = self.ui.cbSingleBandColorRamp.currentColorRamp() # fix: QGIS 3.0 constructor shaderFunc = QgsColorRampShader(cmin, cmax) shaderFunc.setColorRampType(self.currentComboBoxItem(ui.cbSingleBandColorRampType)) shaderFunc.setClip(True) nSteps = 10 colorRampItems = [] diff = cmax - cmin for i in range(nSteps + 1): f = float(i) / nSteps color = colorRamp.color(f) value = cmin + diff * f colorRampItems.append(QgsColorRampShader.ColorRampItem(value, color)) shaderFunc.setColorRampItemList(colorRampItems) shader = QgsRasterShader() shader.setMaximumValue(cmax) shader.setMinimumValue(cmin) shader.setRasterShaderFunction(shaderFunc) r.setShader(shader) return r class DatumViewUI(QFrame, loadUi('timeseriesdatumview.ui')): """ Widget to host the MapCanvases of all map views that relate to a single Datum-Sensor combinbation. """ def __init__(self, title='<#>', parent=None): super(DatumViewUI, self).__init__(parent) self.setupUi(self) def sizeHint(self): m = self.layout().contentsMargins() s = QSize(0, 0) map = None widgets = [self.layout().itemAt(i).widget() for i in range(self.layout().count())] widgets = [w for w in widgets if isinstance(w, QWidget)] maps = [w for w in widgets if isinstance(w, MapCanvas)] others = [w for w in widgets if not isinstance(w, MapCanvas)] s = self.layout().spacing() m = self.layout().contentsMargins() def totalHeight(widgetList): total = QSize(0,0) for w in widgetList: ws = w.size() if ws.width() == 0: ws = w.sizeHint() total.setWidth(max([total.width(), ws.width()])) total.setHeight(total.height() + ws.height()) return total baseSize = totalHeight(widgets) if baseSize.width() == 0: for o in others: baseSize.setWidth(9999) s = QSize(baseSize.width() + m.left() + m.right(), baseSize.height() + m.top() + m.bottom()) return s """ def sizeHint(self): if not self.ui.isVisible(): return QSize(0,0) else: #return self.ui.sizeHint() size = self.ui.sizeHint() s = self.ui.layout().spacing() m = self.ui.layout().contentsMargins() dx = m.left() + m.right() + s dy = self.ui.layout().spacing() n = len([m for m in self.mapCanvases.keys() if m.isVisible()]) if n > 0: baseSize = self.mapCanvases.values()[0].size() size = QSize(baseSize.width()+ dx, \ size.height()+ (n+1)*(dy+2*s)) else: s = "" return size """ class DatumView(QObject): sigRenderProgress = pyqtSignal(int,int) sigLoadingStarted = pyqtSignal(MapView, TimeSeriesDatum) sigLoadingFinished = pyqtSignal(MapView, TimeSeriesDatum) sigVisibilityChanged = pyqtSignal(bool) def __init__(self, timeSeriesDatum, stv, parent=None): assert isinstance(timeSeriesDatum, TimeSeriesDatum) assert isinstance(stv, SpatialTemporalVisualization) super(DatumView, self).__init__() self.ui = DatumViewUI(parent=parent) self.ui.create() self.showLoading(False) self.wOffset = self.ui.layout().count()-1 self.minHeight = self.ui.height() #self.minWidth = 50 self.renderProgress = dict() assert isinstance(stv, SpatialTemporalVisualization) self.STV = stv self.TSD = timeSeriesDatum self.scrollArea = stv.scrollArea self.Sensor = self.TSD.sensor self.Sensor.sigNameChanged.connect(lambda :self.setColumnInfo()) self.TSD.sigVisibilityChanged.connect(self.setVisibility) self.setColumnInfo() self.MVC = stv.MVC self.DVC = stv.DVC self.mapCanvases = dict() self.mRenderState = dict() def setColumnInfo(self): labelTxt = '{}\n{}'.format(str(self.TSD.date), self.TSD.sensor.name()) tooltip = '{}'.format(self.TSD.pathImg) self.ui.labelTitle.setText(labelTxt) self.ui.labelTitle.setToolTip(tooltip) def setVisibility(self, b): self.ui.setVisible(b) self.sigVisibilityChanged.emit(b) def setHighlighted(self, b=True, timeout=1000): styleOn = """.QFrame { border: 4px solid red; border-radius: 4px; }""" styleOff = """""" if b is True: self.ui.setStyleSheet(styleOn) if timeout > 0: QTimer.singleShot(timeout, lambda : self.setHighlighted(b=False)) else: self.ui.setStyleSheet(styleOff) def setMapViewVisibility(self, bandView, isVisible): self.mapCanvases[bandView].setVisible(isVisible) def removeMapView(self, mapView): canvas = self.mapCanvases.pop(mapView) self.ui.layout().removeWidget(canvas) canvas.close() #self.adjustBaseMinSize() def refresh(self): if self.ui.isVisible(): for c in self.mapCanvases.values(): if c.isVisible(): c.refresh() def insertMapView(self, mapView): assert isinstance(mapView, MapView) from timeseriesviewer.mapcanvas import MapCanvas mapCanvas = MapCanvas(self.ui) mapCanvas.setObjectName('MapCanvas {} {}'.format(mapView.title(), self.TSD.date)) mapCanvas.blockSignals(True) self.registerMapCanvas(mapView, mapCanvas) # register MapCanvas on MV level mapView.registerMapCanvas(self.Sensor, mapCanvas) # register MapCanvas on STV level self.STV.registerMapCanvas(mapCanvas) mapCanvas.blockSignals(False) mapCanvas.renderComplete.connect(lambda : self.onRenderingChange(False)) mapCanvas.renderStarting.connect(lambda : self.onRenderingChange(True)) mapCanvas.sigDataLoadingFinished.connect( lambda dt: self.STV.TSV.ui.dockSystemInfo.addTimeDelta('Map {}'.format(self.Sensor.name()), dt)) mapCanvas.sigDataLoadingFinished.connect( lambda dt: self.STV.TSV.ui.dockSystemInfo.addTimeDelta('All Sensors', dt)) def showLoading(self, b): if b: self.ui.progressBar.setRange(0,0) self.ui.progressBar.setValue(-1) else: self.ui.progressBar.setRange(0,1) self.ui.progressBar.setValue(0) def onRenderingChange(self, b): mc = self.sender() #assert isinstance(mc, QgsMapCanvas) self.mRenderState[mc] = b self.showLoading(any(self.mRenderState.values())) def onRendering(self, *args): renderFlags = [m.renderFlag() for m in self.mapCanvases.values()] drawFlags = [m.isDrawing() for m in self.mapCanvases.values()] # print((renderFlags, drawFlags)) isLoading = any(renderFlags) self.showLoading(isLoading) s = "" def registerMapCanvas(self, mapView, mapCanvas): from timeseriesviewer.mapcanvas import MapCanvas assert isinstance(mapCanvas, MapCanvas) self.mapCanvases[mapView] = mapCanvas mapCanvas.layerModel().setRasterLayerSources([self.TSD.pathImg]) self.ui.layout().insertWidget(self.wOffset + len(self.mapCanvases), mapCanvas) self.ui.update() #register signals handled on (this) DV level mapCanvas.renderStarting.connect(lambda: self.sigLoadingStarted.emit(mapView, self.TSD)) mapCanvas.mapCanvasRefreshed.connect(lambda: self.sigLoadingFinished.emit(mapView, self.TSD)) mapCanvas.sigShowProfiles.connect(mapView.sigShowProfiles.emit) mapCanvas.sigChangeDVRequest.connect(self.onMapCanvasRequest) def onMapCanvasRequest(self, mapCanvas, key): if key == 'hide_date': self.TSD.setVisibility(False) if key == 'copy_sensor': QApplication.clipboard().setText(self.TSD.sensor.name()) if key == 'copy_date': QApplication.clipboard().setText(str(self.TSD.date)) if key == 'copy_path': QApplication.clipboard().setText(str(self.TSD.pathImg)) def __lt__(self, other): assert isinstance(other, DatumView) return self.TSD < other.TSD def __eq__(self, other): assert isinstance(other, DatumView) return self.TSD == other.TSD class SpatialTemporalVisualization(QObject): """ """ sigLoadingStarted = pyqtSignal(DatumView, MapView) sigLoadingFinished = pyqtSignal(DatumView, MapView) sigShowProfiles = pyqtSignal(SpatialPoint) sigShowMapLayerInfo = pyqtSignal(dict) sigSpatialExtentChanged = pyqtSignal(SpatialExtent) sigMapSizeChanged = pyqtSignal(QSize) sigCRSChanged = pyqtSignal(QgsCoordinateReferenceSystem) sigActivateMapTool = pyqtSignal(str) def __init__(self, timeSeriesViewer): super(SpatialTemporalVisualization, self).__init__() #assert isinstance(timeSeriesViewer, TimeSeriesViewer), timeSeriesViewer #default map settings self.mBlockCanvasSignals = False self.mSpatialExtent = SpatialExtent.world() self.mCRS = self.mSpatialExtent.crs() self.mSize = QSize(200,200) self.mColor = Qt.black self.mMapCanvases = [] self.ui = timeSeriesViewer.ui self.scrollArea = self.ui.scrollAreaSubsets assert isinstance(self.scrollArea, MapViewScrollArea) self.mRefreshTimer = QTimer(self) self.mRefreshTimer.setInterval(1000) self.mRefreshTimer.timeout.connect(self.refresh) self.scrollArea.sigResized.connect(self.mRefreshTimer.start) self.scrollArea.horizontalScrollBar().valueChanged.connect(self.mRefreshTimer.start) self.TSV = timeSeriesViewer self.TS = timeSeriesViewer.TS self.ui.dockMapViewsV2.connectTimeSeries(self.TS) self.targetLayout = self.ui.scrollAreaSubsetContent.layout() #self.MVC = MapViewCollection(self) #self.MVC.sigShowProfiles.connect(self.sigShowProfiles.emit) self.MVC = self.ui.dockMapViewsV2 assert isinstance(self.MVC, MapViewCollectionDock) self.MVC.sigShowProfiles.connect(self.sigShowProfiles.emit) self.vectorOverlay = None self.DVC = DateViewCollection(self) self.DVC.sigResizeRequired.connect(self.adjustScrollArea) self.DVC.sigLoadingStarted.connect(self.ui.dockRendering.addStartedWork) self.DVC.sigLoadingFinished.connect(self.ui.dockRendering.addFinishedWork) #self.timeSeriesDateViewCollection.sigSpatialExtentChanged.connect(self.setSpatialExtent) self.TS.sigTimeSeriesDatesAdded.connect(self.DVC.addDates) self.TS.sigTimeSeriesDatesRemoved.connect(self.DVC.removeDates) #add dates, if already existing self.DVC.addDates(self.TS[:]) if len(self.TS) > 0: self.setSpatialExtent(self.TS.getMaxSpatialExtent()) #self.setSubsetSize(QSize(100,50)) def createMapView(self): self.MVC.createMapView() def registerMapCanvas(self, mapCanvas): from timeseriesviewer.mapcanvas import MapCanvas assert isinstance(mapCanvas, MapCanvas) self.mMapCanvases.append(mapCanvas) #set general canvas properties mapCanvas.setFixedSize(self.mSize) mapCanvas.setCrs(self.mCRS) mapCanvas.setSpatialExtent(self.mSpatialExtent) #register on map canvas signals mapCanvas.sigSpatialExtentChanged.connect(lambda e: self.setSpatialExtent(e, mapCanvas)) def setCrosshairStyle(self, crosshairStyle): from timeseriesviewer.mapcanvas import MapCanvas for mapCanvas in self.mMapCanvases: assert isinstance(mapCanvas, MapCanvas) mapCanvas.setCrosshairStyle(crosshairStyle) #self.MVC.setCrosshairStyle(crosshairStyle) def setShowCrosshair(self, b): self.MVC.setShowCrosshair(b) def setVectorLayer(self, lyr): self.MVC.setVectorLayer(lyr) def activateMapTool(self, key): from timeseriesviewer.mapcanvas import MapCanvas for mapCanvas in self.mMapCanvases: assert isinstance(mapCanvas, MapCanvas) mapCanvas.activateMapTool(key) def setMapSize(self, size): assert isinstance(size, QSize) self.mSize = size from timeseriesviewer.mapcanvas import MapCanvas for mapCanvas in self.mMapCanvases: assert isinstance(mapCanvas, MapCanvas) mapCanvas.setFixedSize(size) self.sigMapSizeChanged.emit(self.mSize) self.adjustScrollArea() def subsetSize(self): return QSize(self.mSize) def refresh(self): #print('STV REFRESH') for tsdView in self.DVC: tsdView.refresh() self.mRefreshTimer.stop() def adjustScrollArea(self): #adjust scroll area widget to fit all visible widgets m = self.targetLayout.contentsMargins() nX = len(self.DVC) w = h = 0 s = QSize() r = None tsdViews = [v for v in self.DVC if v.ui.isVisible()] mapViews = [v for v in self.MVC if v.isVisible()] nX = len(tsdViews) nY = len(mapViews) spacing = self.targetLayout.spacing() margins = self.targetLayout.contentsMargins() sizeX = 1 sizeY = 50 if nX > 0: s = tsdViews[0].ui.sizeHint().width() s = nX * (s + spacing) + margins.left() + margins.right() sizeX = s if nY > 0: if nX > 0: s = tsdViews[0].ui.sizeHint().height() s = s + margins.top() + margins.bottom() else: s = 50 sizeY = s #s = tsdViews[0].ui.sizeHint() #s = QSize(nX * (s.width() + spacing) + margins.left() + margins.right(), # s.height() + margins.top() + margins.bottom()) self.targetLayout.parentWidget().setFixedSize(QSize(sizeX, sizeY)) def setMaxTSDViews(self, n=-1): self.nMaxTSDViews = n #todo: remove views def setSpatialCenter(self, center, mapCanvas0=None): if self.mBlockCanvasSignals: return True assert isinstance(center, SpatialPoint) center = center.toCrs(self.mCRS) if not isinstance(center, SpatialPoint): return self.mBlockCanvasSignals = True self.mSpatialExtent.setCenter(center) for mapCanvas in self.mMapCanvases: if mapCanvas != mapCanvas0: oldState = mapCanvas.blockSignals(True) mapCanvas.setCenter(center) mapCanvas.blockSignals(oldState) self.mBlockCanvasSignals = False self.sigSpatialExtentChanged.emit(self.mSpatialExtent) def setSpatialExtent(self, extent, mapCanvas0=None): if self.mBlockCanvasSignals: return True assert isinstance(extent, SpatialExtent) extent = extent.toCrs(self.mCRS) if not isinstance(extent, SpatialExtent) \ or extent.isEmpty() or not extent.isFinite() \ or extent.width() <= 0 \ or extent.height() <= 0 \ or extent == self.mSpatialExtent: return self.mBlockCanvasSignals = True self.mSpatialExtent = extent for mapCanvas in self.mMapCanvases: if mapCanvas != mapCanvas0: oldState = mapCanvas.blockSignals(True) mapCanvas.setExtent(extent) mapCanvas.blockSignals(oldState) self.mBlockCanvasSignals = False #for mapCanvas in self.mMapCanvases: # mapCanvas.refresh() self.mRefreshTimer.start() self.sigSpatialExtentChanged.emit(extent) def setBackgroundColor(self, color): assert isinstance(color, QColor) self.mColor = color def backgroundColor(self): return self.mColor def mapCanvasIterator(self): return self.mMapCanvases[:] def setCrs(self, crs): assert isinstance(crs, QgsCoordinateReferenceSystem) if self.mCRS != crs: from timeseriesviewer.utils import saveTransform if saveTransform(self.mSpatialExtent, self.mCRS, crs): self.mCRS = crs for mapCanvas in self.mapCanvasIterator(): #print(('STV set CRS {} {}', str(mapCanvas), self.mCRS.description())) mapCanvas.setCrs(crs) else: pass self.sigCRSChanged.emit(self.mCRS) def crs(self): return self.mCRS def spatialExtent(self): return self.mSpatialExtent def navigateToTSD(self, TSD): assert isinstance(TSD, TimeSeriesDatum) #get widget related to TSD tsdv = self.DVC.tsdView(TSD) assert isinstance(self.scrollArea, QScrollArea) self.scrollArea.ensureWidgetVisible(tsdv.ui) class DateViewCollection(QObject): sigResizeRequired = pyqtSignal() sigLoadingStarted = pyqtSignal(MapView, TimeSeriesDatum) sigLoadingFinished = pyqtSignal(MapView, TimeSeriesDatum) sigShowProfiles = pyqtSignal(SpatialPoint) sigSpatialExtentChanged = pyqtSignal(SpatialExtent) def __init__(self, STViz): assert isinstance(STViz, SpatialTemporalVisualization) super(DateViewCollection, self).__init__() #self.tsv = tsv #self.timeSeries = tsv.TS self.views = list() self.STV = STViz self.ui = self.STV.targetLayout.parentWidget() self.scrollArea = self.ui.parentWidget().parentWidget() #potentially there are many more dates than views. #therefore we implement the addinng/removing of mapviews here #we reduce the number of layout refresh calls by #suspending signals, adding the new map view canvases, and sending sigResizeRequired self.STV.MVC.sigMapViewAdded.connect(self.addMapView) self.STV.MVC.sigMapViewRemoved.connect(self.removeMapView) self.setFocusView(None) def tsdView(self, tsd): r = [v for v in self.views if v.TSD == tsd] if len(r) == 1: return r[0] else: raise Exception('TSD not in list') def addMapView(self, mapView): assert isinstance(mapView, MapView) w = self.ui w.setUpdatesEnabled(False) for tsdv in self.views: tsdv.ui.setUpdatesEnabled(False) for tsdv in self.views: tsdv.insertMapView(mapView) for tsdv in self.views: tsdv.ui.setUpdatesEnabled(True) mapView.sigSensorRendererChanged.connect(lambda *args : self.setRasterRenderer(mapView, *args)) mapView.sigMapViewVisibility.connect(lambda: self.sigResizeRequired.emit()) w.setUpdatesEnabled(True) self.sigResizeRequired.emit() def removeMapView(self, mapView): assert isinstance(mapView, MapView) for tsdv in self.views: tsdv.removeMapView(mapView) self.sigResizeRequired.emit() def highlightDate(self, tsd): """ Highlights a time series data for a specific time our :param tsd: :return: """ tsdView = self.tsdView(tsd) if isinstance(tsdView, DatumView): tsdView.setHighlight(True) def setFocusView(self, tsd): self.focusView = tsd def orderedViews(self): #returns the if self.focusView is not None: assert isinstance(self.focusView, DatumView) return sorted(self.views,key=lambda v: np.abs(v.TSD.date - self.focusView.TSD.date)) else: return self.views """ def setSubsetSize(self, size): assert isinstance(size, QSize) self.subsetSize = size for tsdView in self.orderedViews(): tsdView.blockSignals(True) for tsdView in self.orderedViews(): tsdView.setSubsetSize(size) for tsdView in self.orderedViews(): tsdView.blockSignals(False) """ def addDates(self, tsdList): """ Create a new TSDView :param tsdList: :return: """ for tsd in tsdList: assert isinstance(tsd, TimeSeriesDatum) DV = DatumView(tsd, self.STV, parent=self.ui) #tsdView.setSubsetSize(self.subsetSize) DV.sigLoadingStarted.connect(self.sigLoadingStarted.emit) DV.sigLoadingFinished.connect(self.sigLoadingFinished.emit) DV.sigVisibilityChanged.connect(lambda: self.STV.adjustScrollArea()) for i, mapView in enumerate(self.STV.MVC): DV.insertMapView(mapView) bisect.insort(self.views, DV) i = self.views.index(DV) DV.ui.setParent(self.STV.targetLayout.parentWidget()) self.STV.targetLayout.insertWidget(i, DV.ui) DV.ui.show() if len(tsdList) > 0: self.sigResizeRequired.emit() def removeDates(self, tsdList): toRemove = [v for v in self.views if v.TSD in tsdList] removedDates = [] for DV in toRemove: self.views.remove(DV) for mapCanvas in DV.mapCanvases.values(): toRemove = mapCanvas.layers() mapCanvas.setLayers([]) toRemove = [l for l in toRemove if isinstance(l, QgsRasterLayer)] if len(toRemove) > 0: QgsMapLayerRegistry.instance().removeMapLayers(toRemove) DV.ui.parent().layout().removeWidget(DV.ui) DV.ui.hide() DV.ui.close() removedDates.append(DV.TSD) del DV if len(removedDates) > 0: self.sigResizeRequired.emit() def __len__(self): return len(self.views) def __iter__(self): return iter(self.views) def __getitem__(self, slice): return self.views[slice] def __delitem__(self, slice): self.removeDates(self.views[slice]) class MapViewListModel(QAbstractListModel): """ A model to keep a list of map views. """ sigMapViewsAdded = pyqtSignal(list) sigMapViewsRemoved = pyqtSignal(list) def __init__(self, parent=None): super(MapViewListModel, self).__init__(parent) self.mMapViewList = [] def addMapView(self, mapView): i = len(self.mMapViewList) self.insertMapView(i, mapView) def insertMapView(self, i, mapView): self.insertMapViews(i, [mapView]) def insertMapViews(self, i, mapViews): assert isinstance(mapViews, list) assert i >= 0 and i <= len(self.mMapViewList) self.beginInsertRows(QModelIndex(), i, i + len(mapViews) - 1) for j in range(len(mapViews)): mapView = mapViews[j] assert isinstance(mapView, MapView) mapView.sigTitleChanged.connect( lambda : self.doRefresh([mapView]) ) self.mMapViewList.insert(i + j, mapView) self.endInsertRows() self.sigMapViewsAdded.emit(mapViews) def doRefresh(self, mapViews): for mapView in mapViews: idx = self.mapView2idx(mapView) self.dataChanged.emit(idx, idx) def removeMapView(self, mapView): self.removeMapViews([mapView]) def removeMapViews(self, mapViews): assert isinstance(mapViews, list) for mv in mapViews: assert mv in self.mMapViewList idx = self.mapView2idx(mv) self.beginRemoveRows(idx.parent(), idx.row(), idx.row()) self.mMapViewList.remove(mv) self.endRemoveRows() self.sigMapViewsRemoved.emit(mapViews) def rowCount(self, parent=None, *args, **kwargs): return len(self.mMapViewList) def columnCount(self, QModelIndex_parent=None, *args, **kwargs): return 1 def idx2MapView(self, index): if isinstance(index, QModelIndex): if index.isValid(): index = index.row() else: return None assert index >= 0 and index < len(self.mMapViewList) return self.mMapViewList[index] def mapView2idx(self, mapView): assert isinstance(mapView, MapView) row = self.mMapViewList.index(mapView) return self.createIndex(row, 0, mapView) def __len__(self): return len(self.mMapViewList) def __iter__(self): return iter(self.mMapViewList) def __getitem__(self, slice): return self.mMapViewList[slice] def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None if (index.row() >= len(self.mMapViewList)) or (index.row() < 0): return None mapView = self.idx2MapView(index) assert isinstance(mapView, MapView) value = None if role == Qt.DisplayRole: value = '{} {}'.format(index.row() +1 , mapView.title()) #if role == Qt.DecorationRole: #value = classInfo.icon(QSize(20,20)) if role == Qt.UserRole: value = mapView return value class MapViewCollectionDock(QgsDockWidget, loadUi('mapviewdock.ui')): sigMapViewAdded = pyqtSignal(MapView) sigMapViewRemoved = pyqtSignal(MapView) sigShowProfiles = pyqtSignal(SpatialPoint) def connectTimeSeries(self, timeSeries): assert isinstance(timeSeries, TimeSeries) self.TS = timeSeries self.TS.sigSensorAdded.connect(self.addSensor) self.TS.sigSensorRemoved.connect(self.removeSensor) def __init__(self, parent=None): super(MapViewCollectionDock, self).__init__(parent) self.setupUi(self) self.baseTitle = self.windowTitle() self.btnAddMapView.setDefaultAction(self.actionAddMapView) self.btnRemoveMapView.setDefaultAction(self.actionRemoveMapView) self.btnRefresh.setDefaultAction(self.actionApplyStyles) self.btnHighlightMapView.setDefaultAction(self.actionHighlightMapView) self.actionAddMapView.triggered.connect(self.createMapView) self.actionRemoveMapView.triggered.connect(lambda : self.removeMapView(self.currentMapView())) self.actionHighlightMapView.triggered.connect(lambda : self.currentMapView().setHighlighted(True)) self.actionApplyStyles.triggered.connect(lambda : self.currentMapView().refreshMapView()) self.mMapViews = MapViewListModel() self.mMapViews.sigMapViewsRemoved.connect(self.onMapViewsRemoved) self.mMapViews.sigMapViewsAdded.connect(self.onMapViewsAdded) self.mMapViews.sigMapViewsAdded.connect(self.updateButtons) self.mMapViews.sigMapViewsRemoved.connect(self.updateButtons) self.cbMapView.setModel(self.mMapViews) self.cbMapView.currentIndexChanged[int].connect(lambda i : None if i < 0 else self.setCurrentMapView(self.mMapViews.idx2MapView(i)) ) self.TS = None def onMapViewsRemoved(self, mapViews): for mapView in mapViews: idx = self.stackedWidget.indexOf(mapView.ui) if idx >= 0: self.stackedWidget.removeWidget(mapView.ui) mapView.ui.close() else: s = "" self.actionRemoveMapView.setEnabled(len(self.mMapViews) > 0) def onMapViewsAdded(self, mapViews): nextShown = None for mapView in mapViews: self.stackedWidget.addWidget(mapView.ui) if nextShown is None: nextShown = mapView contents = mapView.ui.scrollAreaWidgetContents size = contents.size() hint = contents.sizeHint() #mapView.ui.scrollArea.update() s = "" #setMinimumSize(mapView.ui.scrollAreaWidgetContents.sizeHint()) #hint = contents.sizeHint() #contents.setMinimumSize(hint) if isinstance(nextShown, MapView): self.setCurrentMapView(nextShown) def updateButtons(self, *args): b = len(self.mMapViews) > 0 self.actionRemoveMapView.setEnabled(b) self.actionApplyStyles.setEnabled(b) self.actionHighlightMapView.setEnabled(b) def createMapView(self): mapView = MapView(self) n = len(self.mMapViews) + 1 title = 'Map View {}'.format(n) while title in [m.title() for m in self.mMapViews]: n += 1 title = 'Map View {}'.format(n) mapView.setTitle(title) for sensor in self.TS.Sensors: mapView.addSensor(sensor) self.mMapViews.addMapView(mapView) self.sigMapViewAdded.emit(mapView) return mapView def updateFromMapView(self, mapView): assert isinstance(mapView, MapView) self.btnToggleMapViewVisibility.setChecked(mapView) def removeMapView(self, mapView): assert isinstance(mapView, MapView) assert mapView in self.mMapViews i = self.mMapViews.mapView2idx(mapView) if not i == self.stackedWidget.indexOf(mapView.ui): s = "" self.mMapViews.removeMapView(mapView) mapView.ui.close() self.sigMapViewRemoved.emit(mapView) def __len__(self): return len(self.mMapViews) def __iter__(self): return iter(self.mMapViews) def __getitem__(self, slice): return self.mMapViews[slice] def __contains__(self, mapView): return mapView in self.mMapViews def index(self, mapView): assert isinstance(mapView, MapView) return self.mMapViews.index(mapView) def setVectorLayer(self, lyr): for mapView in self.mMapViews: assert isinstance(mapView, MapView) mapView.setVectorLayer(lyr) def addSensor(self, sensor): for mapView in self.mMapViews: mapView.addSensor(sensor) #self.adjustScrollArea() def removeSensor(self, sensor): for mapView in self.mMapViews: mapView.removeSensor(sensor) def applyStyles(self): for mapView in self.mMapViews: mapView.applyStyles() def setCrosshairStyle(self, crosshairStyle): for mapView in self.mMapViews: mapView.setCrosshairStyle(crosshairStyle) def setShowCrosshair(self, b): for mapView in self.mMapViews: mapView.setShowCrosshair(b) def index(self, mapView): assert isinstance(mapView, MapView) return self.mapViewsDefinitions.index(mapView) def setCurrentMapView(self, mapView): assert isinstance(mapView, MapView) and mapView in self.mMapViews idx = self.stackedWidget.indexOf(mapView.ui) if idx >= 0: self.stackedWidget.setCurrentIndex(idx) self.cbMapView.setCurrentIndex(self.mMapViews.mapView2idx(mapView).row()) def currentMapView(self): if len(self.mMapViews) == None: return None else: i = self.cbMapView.currentIndex() return self.mMapViews.idx2MapView(i) """ class MapViewDockUI(TsvDockWidgetBase, loadUi('mapviewdock.ui')): def __init__(self, parent=None): super(MapViewDockUI, self).__init__(parent) self.setupUi(self) self.baseTitle = self.windowTitle() self.btnApplyStyles.setDefaultAction(self.actionApplyStyles) #self.dockLocationChanged.connect(self.adjustLayouts) def toggleLayout(self, p): newLayout = None l = p.layout() print('toggle layout {}'.format(str(p.objectName()))) tmp = QWidget() tmp.setLayout(l) sMax = p.maximumSize() sMax.transpose() sMin = p.minimumSize() sMin.transpose() p.setMaximumSize(sMax) p.setMinimumSize(sMin) if isinstance(l, QVBoxLayout): newLayout = QHBoxLayout() else: newLayout = QVBoxLayout() print(l, '->', newLayout) while l.count() > 0: item = l.itemAt(0) l.removeItem(item) newLayout.addItem(item) p.setLayout(newLayout) return newLayout def adjustLayouts(self, area): return lOld = self.scrollAreaMapsViewDockContent.layout() if area in [Qt.LeftDockWidgetArea, Qt.RightDockWidgetArea] \ and isinstance(lOld, QVBoxLayout) or \ area in [Qt.TopDockWidgetArea, Qt.BottomDockWidgetArea] \ and isinstance(lOld, QHBoxLayout): #self.toogleLayout(self.scrollAreaMapsViewDockContent) self.toggleLayout(self.BVButtonList) """