Skip to content
Snippets Groups Projects
mapvisualization.py 69.7 KiB
Newer Older
  • Learn to ignore specific revisions
  •         self.mCrs = crs
            if isinstance(crs, QgsCoordinateReferenceSystem):
                for c in self.mapCanvases():
                    c.setCrs(crs)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            Calls the timedRefresh() routine for all MapCanvases
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            for c in self.mapCanvases():
                assert isinstance(c, MapCanvas)
                c.timedRefresh()
    
    
        def currentLayers(self):
    
            layers = set()
            for c in self.mapCanvases():
                layers = layers.union(set(c.layers()))
            return list(layers)
    
    
    
        def crs(self)->QgsCoordinateReferenceSystem:
            return self.mCrs
    
        def setTimeSeries(self, ts:TimeSeries)->TimeSeries:
            assert ts == None or isinstance(ts, TimeSeries)
            self.mTimeSeries = ts
            if isinstance(self.mTimeSeries, TimeSeries):
                self.mTimeSeries.sigVisibilityChanged.connect(self._updateCanvasDates)
                self.mTimeSeries.sigTimeSeriesDatesRemoved.connect(self._updateCanvasDates)
    
                self.mTimeSeries.sigTimeSeriesDatesAdded.connect(self._updateSliderRange)
                self.mTimeSeries.sigTimeSeriesDatesRemoved.connect(self._updateSliderRange)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if len(self.mTimeSeries) > 0:
                    self.mCurrentDate = self.mTimeSeries[0]
                else:
                    self.mTimeSeries.sigTimeSeriesDatesAdded.connect(self.onSetInitialCurrentDate)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def onSetInitialCurrentDate(self):
            if len(self.timeSeries()) > 0:
                self.setCurrentDate(self.timeSeries()[0])
                self.mTimeSeries.sigTimeSeriesDatesAdded.disconnect(self.onSetInitialCurrentDate)
    
    
        def _updateSliderRange(self):
    
            n = len(self.timeSeries())
            self.mTimeSlider.setRange(0, n)
            self.mTimeSlider.setEnabled(n > 0)
            if n > 0:
                tsd = self.currentDate()
    
                if isinstance(tsd, TimeSeriesDate) and tsd in self.timeSeries():
                    i = self.timeSeries()[:].index(tsd)
                    self.mTimeSlider.setValue(i+1)
    
        def onSliderReleased(self):
    
            i = self.mTimeSlider.value() - 1
            if isinstance(self.mTimeSeries, TimeSeries) and len(self.mTimeSeries) > 0:
                i = min(i, len(self.mTimeSeries)-1)
                i = max(i,  0)
                tsd = self.mTimeSeries[i]
                self.setCurrentDate(tsd)
    
    
    
        def timeSeries(self)->TimeSeries:
            return self.mTimeSeries
    
        def setMode(self, mode:ViewMode):
    
            if mode != self.mViewMode:
                self.mViewMode = mode
                self._updateGrid()
                self.sigViewModeChanged.emit(self.mViewMode)
    
        def setMapsPerMapView(self, n:int):
            assert n >= 0
    
            if n != self.mMpMV:
                self.mMpMV = n
                self._updateGrid()
    
                self.timeSlider().setPageStep(max(1, n))
    
                self.sigMapsPerMapViewChanged.emit(n)
    
        def setMapSize(self, size:QSize)->QSize:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            Sets the MapCanvas size
            :param size: QSite
            :return: QSize
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            if size != self.mMapSize:
                for canvas in self.mapCanvases():
                    canvas.setFixedSize(size)
    
                self.mMapSize = size
                self._updateWidgetSize()
                self.sigMapSizeChanged.emit(size)
    
        def mapSize(self)->QSize:
            """
            Returns the MapCanvas size
            :return: QSize
            """
            return self.mMapSize
    
        def mapCanvases(self)->list:
            """
            Returns all MapCanvases
            :return: [list-of-MapCanvases]
            """
            return self.findChildren(MapCanvas)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def mapViewCanvases(self, mapView:MapView)->list:
    
            Returns the MapCanvases related to a MapView
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            :param mapView: MapView
    
            :return: [list-of-MapCanvases]
    
            return self.mCanvases[mapView]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            for tsd in self.timeSeries()[:]:
                assert isinstance(tsd, TimeSeriesDate)
                if tsd > self.currentDate() and tsd.isVisible():
                    self.setCurrentDate(tsd)
                    return
            s = ""
    
        def moveToPreviousTSD(self):
            for tsd in reversed(self.timeSeries()[:]):
                if tsd < self.currentDate() and tsd.isVisible():
                    self.setCurrentDate(tsd)
                    return
            s = ""
    
        def moveToNextTSDFast(self):
            visible = list([tsd for tsd in self.timeSeries() if tsd.isVisible() and tsd > self.currentDate()])
            if len(visible) > 0 and self.mMpMV > 0:
                i = min(self.mMpMV-1, len(visible)-1)
                self.setCurrentDate(visible[i])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def moveToPreviousTSDFast(self):
            visible = list(reversed([tsd for tsd in self.timeSeries() if tsd.isVisible() and tsd < self.currentDate()]))
            if len(visible) > 0 and self.mMpMV > 0:
                i = min(self.mMpMV - 1, len(visible)-1)
                self.setCurrentDate(visible[i])
    
        def moveToFirstTSD(self):
            for tsd in self.timeSeries()[:]:
                if tsd.isVisible():
                    self.setCurrentDate(tsd)
                    return
            s = ""
    
    
        def moveToLastTSD(self):
            for tsd in reversed(self.timeSeries()[:]):
                if tsd.isVisible():
                    self.setCurrentDate(tsd)
                    return
            s  = ""
    
        def setCurrentDate(self, tsd:TimeSeriesDate)->TimeSeriesDate:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            Sets the current TimeSeriesDate, i.e. the "center" date of all dates to be shown
            :param tsd: TimeSeriesDate
            :return: TimeSeriesDate
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            assert isinstance(tsd, TimeSeriesDate)
    
            b = tsd != self.mCurrentDate
            self.mCurrentDate = tsd
    
    
                i = self.mTimeSeries[:].index(self.mCurrentDate) + 1
    
                if self.mTimeSlider.value() != i:
                    self.mTimeSlider.setValue(i)
    
                self.sigCurrentDateChanged.emit(self.mCurrentDate)
    
    
    
            if isinstance(self.currentDate(), TimeSeriesDate):
                i = self.timeSeries()[:].index(self.currentDate())
                canForward = i < len(self.mTimeSeries) - 1
                canBackward = i > 0
            else:
                canForward = canBackward = False
    
            for a in [self.actionForward, self.actionForwardFast, self.actionLastDate]:
                a.setEnabled(canForward)
    
            for a in [self.actionBackward, self.actionBackwardFast, self.actionFirstDate]:
                a.setEnabled(canBackward)
    
    
        def currentDate(self)->TimeSeriesDate:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            Returns the current TimeSeriesDate
            :return: TimeSeriesDate
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
        def addMapView(self, mapView:MapView)->MapView:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            Adds a MapView
            :param mapView: MapView
            :return: MapView
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            assert isinstance(mapView, MapView)
            if mapView not in self.mMapViews:
    
                self.mMapViews.append(mapView)
    
                # connect signals
                mapView.sigCanvasAppearanceChanged.connect(self._updateCanvasAppearance)
                mapView.sigCrosshairChanged.connect(self._updateCrosshair)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
                self._updateGrid()
                self._updateCrosshair(mapView=mapView)
                self.sigMapViewsChanged.emit()
                self.sigMapViewAdded.emit(mapView)
    
            return mapView
    
        def removeMapView(self, mapView:MapView)->MapView:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            Removes a MapView
            :param mapView: Mapview
            :return: MapView
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            if mapView in self.mMapViews:
                self.mMapViews.remove(mapView)
                mapView.setMapWidget(None)
                # disconnect signals
    
                self._updateGrid()
                self.sigMapViewsChanged.emit()
                self.sigMapViewRemoved.emit(mapView)
            return mapView
    
        def mapViews(self)->list:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            Returns a list of all MapViews
            :return: [list-of-MapViews]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def syncQGISCanvasCenter(self, qgisChanged:bool):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            iface = qgis.utils.iface
            assert isinstance(iface, QgisInterface)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            c = iface.mapCanvas()
            if not isinstance(c, QgsMapCanvas):
                return
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            tsvCenter = self.spatialExtent().spatialCenter()
            qgsCenter = SpatialExtent.fromMapCanvas(c).spatialCenter()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            if qgisChanged:
                # change EOTSV
                if tsvCenter.crs().isValid():
                    self.mSyncLock = True
                    qgsCenter = qgsCenter.toCrs(tsvCenter.crs())
                    if isinstance(qgsCenter, SpatialPoint):
                        self.setSpatialCenter(qgsCenter)
                    QApplication.processEvents()
                    self.mSyncLock = False
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            else:
                # change QGIS
                if qgsCenter.crs().isValid():
                    self.mSyncLock = True
                    tsvCenter = tsvCenter.toCrs(qgsCenter.crs())
                    if isinstance(tsvCenter, SpatialPoint):
                        c.setCenter(tsvCenter)
                    QApplication.processEvents()
                    self.mSyncLock = False
    
        def _createMapCanvas(self)->MapCanvas:
            mapCanvas = MapCanvas()
            mapCanvas.setMapLayerStore(self.mMapLayerStore)
    
            mapCanvas.mInfoItem.setTextFormat(self.mapTextFormat())
    
    
            # set general canvas properties
            mapCanvas.setFixedSize(self.mMapSize)
            mapCanvas.setDestinationCrs(self.mCrs)
            mapCanvas.setSpatialExtent(self.mSpatialExtent)
    
            # activate the current map tool
            mapTools = mapCanvas.mapTools()
            mapTools.activate(self.mMapToolKey)
    
            mt = mapCanvas.mapTool()
            if isinstance(mt, QgsMapToolSelect):
                mt.setSelectionMode(self.mMapToolMode)
    
            # connect signals
            self._connectCanvasSignals(mapCanvas)
            return mapCanvas
    
        def _connectCanvasSignals(self, mapCanvas:MapCanvas):
            mapCanvas.sigSpatialExtentChanged.connect(self.setSpatialExtent)
            mapCanvas.sigDestinationCrsChanged.connect(self.setCrs)
            mapCanvas.sigCrosshairPositionChanged.connect(self.onCrosshairPositionChanged)
    
            mapCanvas.mapTools().mtCursorLocation.sigLocationRequest[SpatialPoint, QgsMapCanvas].connect(self.sigCurrentLocationChanged)
    
    
        def _disconnectCanvasSignals(self, mapCanvas:MapCanvas):
            mapCanvas.sigSpatialExtentChanged.disconnect(self.setSpatialExtent)
            mapCanvas.sigDestinationCrsChanged.disconnect(self.setCrs)
            mapCanvas.sigCrosshairPositionChanged.disconnect(self.onCrosshairPositionChanged)
    
            mapCanvas.mapTools().mtCursorLocation.sigLocationRequest[SpatialPoint, QgsMapCanvas].disconnect(
                self.sigCurrentLocationChanged)
    
    
        def onCrosshairPositionChanged(self, spatialPoint:SpatialPoint):
    
            if self.mCrosshairPosition != spatialPoint:
                self.setCrosshairPosition(spatialPoint)
                self.sigCrosshairPositionChanged[SpatialPoint, MapCanvas].emit(self.mCrosshairPosition, canvas)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setCurrentLayer(self, layer:QgsMapLayer):
    
            for mapView in self.mapViews():
                mapView.setCurrentLayer(layer)
    
    
        def setCrosshairPosition(self, spatialPoint)->SpatialPoint:
            spatialPoint = spatialPoint.toCrs(self.crs())
            if self.mCrosshairPosition != spatialPoint:
                self.mCrosshairPosition = spatialPoint
    
                for canvas in self.mapCanvases():
                    assert isinstance(canvas, MapCanvas)
                    canvas.setCrosshairPosition(spatialPoint)
    
                self.sigCrosshairPositionChanged[SpatialPoint].emit(self.mCrosshairPosition)
            return self.crosshairPosition()
    
        def crosshairPosition(self)->SpatialPoint:
            return self.mCrosshairPosition
    
        def _updateGrid(self):
            import time
            t0 = time.time()
    
            # crop grid
            if self.mViewMode == MapWidget.ViewMode.MapViewByRows:
    
                nc = self.mMpMV
                nr = len(self.mapViews())
            else:
                raise NotImplementedError()
    
            toRemove = []
            for row in range(nr, self.mGrid.rowCount()):
                for col in range(self.mGrid.columnCount()):
                    item = self.mGrid.itemAtPosition(row, col)
                    if isinstance(item, QLayoutItem) and isinstance(item.widget(), QWidget):
                        toRemove.append(item.widget())
    
            for col in range(nc, self.mGrid.columnCount()):
                for row in range(self.mGrid.rowCount()):
                    item = self.mGrid.itemAtPosition(row, col)
                    if isinstance(item, QLayoutItem) and isinstance(item.widget(), QWidget):
                        toRemove.append(item.widget())
    
            for w in toRemove:
                self.mGrid.removeWidget(w)
                w.setParent(None)
                w.setVisible(False)
    
            self.mCanvases.clear()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            if self.mViewMode == MapWidget.ViewMode.MapViewByRows:
                for row, mv in enumerate(self.mMapViews):
                    assert isinstance(mv, MapView)
                    self.mCanvases[mv] = []
    
                    for col in range(self.mMpMV):
    
                        item = self.mGrid.itemAtPosition(row, col)
                        if isinstance(item, QLayoutItem) and isinstance(item.widget(), MapCanvas):
                            c = item.widget()
                        else:
    
                            self.mGrid.addWidget(c, row, col)
                        assert isinstance(c, MapCanvas)
    
                        #c.setFixedSize(self.mMapSize)
    
                        c.setTSD(None)
                        c.setMapView(mv)
                        usedCanvases.append(c)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        self.mCanvases[mv].append(c)
            else:
                raise NotImplementedError()
    
            t2 = time.time()
            self._updateWidgetSize()
            t3 = time.time()
    
            s = ""
            # remove old canvases
            for c in oldCanvases:
                if c not in usedCanvases:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    try:
                        self._disconnectCanvasSignals(c)
                    except:
                        pass
    
    
                print('t1: {}'.format(t1 - t0))
                print('t2: {}'.format(t2 - t1))
                print('t3: {}'.format(t3 - t2))
                print('t4: {}'.format(t4 - t3))
    
    
        def _updateWidgetSize(self):
    
            self.mGrid.update()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #self.resize(self.sizeHint())
    
            # self.setMaximumSize(self.sizeHint())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # self.setFixedSize(self.sizeHint())
    
                assert isinstance(w, QWidget)
    
                rect = QGuiApplication.primaryScreen().geometry()
    
                maxw, maxh = 0.66*rect.width(), 0.66*rect.height()
                hint = self.sizeHint()
                minw, minh = min(hint.width(), maxw), min(hint.height(), maxh)
    
                w.setMinimumSize(minw, minh)
                #w.setFixedSize(self.sizeHint())
                w.layout().update()
                w.update()
    
    
        def _updateLayerCache(self)->list:
            canvases = self.findChildren(MapCanvas)
            for c in canvases:
                assert isinstance(c, MapCanvas)
                self.mMapLayerCache[self._layerListKey(c)] = c.layers()
            return canvases
    
        def _layerListKey(self, canvas:MapCanvas):
            return (canvas.mapView(), canvas.tsd())
    
        def _updateCanvasDates(self, updateLayerCache=True):
    
    
            visibleBefore = self.visibleTSDs()
            bTSDChanged = False
    
            if not (isinstance(self.mCurrentDate, TimeSeriesDate) and isinstance(self.timeSeries(), TimeSeries)):
                for c in self.findChildren(MapCanvas):
                    assert isinstance(c, MapCanvas)
                    c.setTSD(None)
                bTSDChanged = True
            else:
    
                visible = [tsd for tsd in self.timeSeries() if tsd.isVisible()]
    
                t = self.mCurrentDate.date()
                visible = sorted(visible, key=lambda tsd: abs(tsd.date() - t))
                visible = visible[0:min(len(visible), self.mMpMV)]
                visible = sorted(visible)
    
                # set TSD of remaining canvases to None
                while len(visible) < self.mMpMV:
                    visible.append(None)
    
                for mapView in self.mapViews():
                    for tsd, canvas in zip(visible, self.mCanvases[mapView]):
                        assert isinstance(tsd, TimeSeriesDate) or tsd is None
                        assert isinstance(canvas, MapCanvas)
                        if canvas.tsd() != tsd:
                            canvas.setTSD(tsd)
    
    
                            key = self._layerListKey(canvas)
                            if key in self.mMapLayerCache.keys():
                                canvas.setLayers(self.mMapLayerCache.pop(key))
    
                            bTSDChanged = True
    
                        # canvas.setLayers()
            if bTSDChanged:
                self._updateCanvasAppearance()
    
            visible2 = self.visibleTSDs()
            if visible2 != visibleBefore:
                self.sigVisibleDatesChanged.emit(visible2)
    
    
            self._freeUnusedMapLayers()
    
        def _freeUnusedMapLayers(self):
    
            layers = [l for l in self.mMapLayerStore.mapLayers().values() if isinstance(l, SensorProxyLayer)]
            needed = self.currentLayers()
            toRemove = [l for l in layers if isinstance(l, SensorProxyLayer) and l not in needed]
    
            # todo: use a kind of caching
    
            # remove layers from MapLayerCache and MapLayerStore
            for mv in self.mMapLayerCache.keys():
                layers = [l for l in self.mMapLayerCache[mv] if l not in toRemove]
                self.mMapLayerCache[mv] = layers
            self.mMapLayerStore.removeMapLayers(toRemove)
    
    
    
    
    
        def _updateCrosshair(self, mapView=None):
    
            if isinstance(mapView, MapView):
                mapViews = [mapView]
            else:
                mapViews = self.mapViews()
    
            for mapView in mapViews:
                assert isinstance(mapView, MapView)
                style = mapView.crosshairStyle()
                assert isinstance(style, CrosshairStyle)
    
                for canvas in self.mCanvases[mapView]:
                    assert isinstance(canvas, MapCanvas)
    
                    item = canvas.mCrosshairItem
                    item.setVisibility(style.mShow)
                    assert isinstance(item, CrosshairMapCanvasItem)
                    item.setCrosshairStyle(style)
                    canvas.addToRefreshPipeLine(MapCanvas.Command.UpdateMapItems)
    
        def _updateCanvasAppearance(self, mapView=None):
    
            if isinstance(mapView, MapView):
                mapViews = [mapView]
            else:
                mapViews = self.mapViews()
    
            for mapView in mapViews:
                assert isinstance(mapView, MapView)
                v = mapView.isVisible()
                bg = mapView.mapBackgroundColor()
    
    
                showDate = mapView.optionShowDate.isChecked()
                showName = mapView.optionShowMapViewName.isChecked()
                showSensor = mapView.optionShowSensorName.isChecked()
    
                for canvas in self.mCanvases[mapView]:
                    assert isinstance(canvas, MapCanvas)
    
                    # set overall visibility
                    if canvas.isVisible() != v:
                        canvas.setVisible(v)
    
                    tsd = canvas.tsd()
    
                    if canvas.canvasColor() != bg:
                        canvas.addToRefreshPipeLine(mapView.mapBackgroundColor())
    
                    # set info text
                    info = canvas.infoItem()
                    assert isinstance(info, MapCanvasInfoItem)
    
    
                    uc = []
                    lc = []
                    if isinstance(tsd, TimeSeriesDate):
                        if showDate:
                            uc += ['{}'.format(tsd.date())]
                        if showName:
                            lc += ['{}'.format(mapView.title(maskNewLines=False))]
                        if showSensor:
                            uc += ['{}'.format(tsd.sensor().name())]
    
                    uc = '\n'.join(uc)
                    lc = '\n'.join(lc)
    
    
    
                    canvas.addToRefreshPipeLine(MapCanvas.Command.UpdateMapItems)
    
    
    
    
    class MapViewDock(QgsDockWidget, loadUI('mapviewdock.ui')):
    
        sigMapViewAdded = pyqtSignal(MapView)
        sigMapViewRemoved = pyqtSignal(MapView)
        sigShowProfiles = pyqtSignal(SpatialPoint, MapCanvas, str)
    
        sigMapCanvasColorChanged = pyqtSignal(QColor)
    
        sigMapCanvasTextFormatChanged = pyqtSignal(QgsTextFormat)
    
        sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
        sigCrsChanged = pyqtSignal(QgsCoordinateReferenceSystem)
        sigMapSizeChanged = pyqtSignal(QSize)
        sigMapsPerMapViewChanged = pyqtSignal(int)
    
        sigMapTextFormatChanged = pyqtSignal(QgsTextFormat)
    
    
        def setTimeSeries(self, timeSeries:TimeSeries):
            assert isinstance(timeSeries, TimeSeries)
            self.mTimeSeries = timeSeries
            self.mTimeSeries.sigSensorAdded.connect(self.addSensor)
            self.mTimeSeries.sigSensorRemoved.connect(self.removeSensor)
    
        def __init__(self, parent=None):
            super(MapViewDock, self).__init__(parent)
            self.setupUi(self)
    
            self.baseTitle = self.windowTitle()
    
            self.btnAddMapView.setDefaultAction(self.actionAddMapView)
            self.btnRemoveMapView.setDefaultAction(self.actionRemoveMapView)
    
            self.btnCrs.crsChanged.connect(self.sigCrsChanged)
            self.btnMapCanvasColor.colorChanged.connect(self.sigMapCanvasColorChanged)
    
            self.btnTextFormat.changed.connect(lambda *args: self.sigMapTextFormatChanged.emit(self.mapTextFormat()))
            self.btnApplySizeChanges.clicked.connect(self.onApplyButtonClicked)
    
    
            self.actionAddMapView.triggered.connect(self.createMapView)
            self.actionRemoveMapView.triggered.connect(lambda: self.removeMapView(self.currentMapView()) if self.currentMapView() else None)
    
    
            self.toolBox.currentChanged.connect(self.onToolboxIndexChanged)
    
            self.spinBoxMapSizeX.valueChanged.connect(lambda: self.onMapSizeChanged('X'))
            self.spinBoxMapSizeY.valueChanged.connect(lambda: self.onMapSizeChanged('Y'))
            self.mLastMapSize = self.mapSize()
    
    
            self.mTimeSeries = None
            self.mMapWidget = None
    
    
        def onApplyButtonClicked(self):
            self.sigMapSizeChanged.emit(QSize(self.spinBoxMapSizeX.value(), self.spinBoxMapSizeY.value()))
            self.sigMapsPerMapViewChanged.emit(self.mapsPerMapView())
    
    
    
    
        def setMapWidget(self, mw)->MapWidget:
            """
            Connects this MapViewDock with a MapWidget
            :param mw: MapWidget
            :return:
            """
            assert isinstance(mw, MapWidget)
    
            assert mw.timeSeries() == self.mTimeSeries, 'Set the time series first!'
            self.mMapWidget = mw
    
            self.sigCrsChanged.connect(mw.setCrs)
            mw.sigCRSChanged.connect(self.setCrs)
    
            self.sigMapSizeChanged.connect(mw.setMapSize)
            mw.sigMapSizeChanged.connect(self.setMapSize)
    
    
            self.sigMapTextFormatChanged.connect(mw.setMapTextFormat)
            mw.sigMapTextFormatChanged.connect(self.setMapTextFormat)
    
    
            self.sigMapsPerMapViewChanged.connect(mw.setMapsPerMapView)
            mw.sigMapsPerMapViewChanged.connect(self.setMapsPerMapView)
    
            self.sigMapViewAdded.connect(mw.addMapView)
            self.sigMapViewRemoved.connect(mw.removeMapView)
            mw.sigMapViewAdded.connect(self.addMapView)
            mw.sigMapViewRemoved.connect(self.removeMapView)
    
            for mapView in mw.mapViews():
                self.addMapView(mapView)
    
            return self.mMapWidget
    
        def mapWidget(self):
            """
            Returns the connected MapWidget
            :return: MapWidget
            """
            return self.mMapWidget
    
        def mapViews(self)->list:
            """
            Returns the defined MapViews
            :return: [list-of-MapViews]
            """
            assert isinstance(self.toolBox, QToolBox)
            mapViews = []
            for i in range(self.toolBox.count()):
                item = self.toolBox.widget(i)
                if isinstance(item, MapView):
                    mapViews.append(item)
            return mapViews
    
        def mapCanvases(self)->list:
            """
            Returns all MapCanvases from all MapViews
            :return: [list-of-MapCanvases]
            """
    
            maps = []
            for mapView in self.mapViews():
                assert isinstance(mapView, MapView)
                maps.extend(mapView.mapCanvases())
            return maps
    
        def setCrs(self, crs):
            if isinstance(crs, QgsCoordinateReferenceSystem):
                old = self.btnCrs.crs()
                if old != crs:
                    self.btnCrs.setCrs(crs)
                    self.btnCrs.setLayerCrs(crs)
    
        def mapsPerMapView(self)->int:
            return self.sbMpMV.value()
    
        def setMapsPerMapView(self, n:int):
            assert n >= 0
    
            if self.sbMpMV.value != n:
                self.sbMpMV.setValue(n)
    
                self.mLastNDatesPerMapView = n
    
        def setMapTextFormat(self, textFormat:QgsTextFormat):
    
            if not equalTextFormats(textFormat, self.mapTextFormat()):
                self.btnTextFormat.setTextFormat(textFormat)
    
        def mapTextFormat(self)->QgsTextFormat:
            return self.btnTextFormat.textFormat()
    
    
        def setMapSize(self, size):
            assert isinstance(size, QSize)
            ws = [self.spinBoxMapSizeX, self.spinBoxMapSizeY]
            oldSize = self.mapSize()
            b = oldSize != size
            for w in ws:
                w.blockSignals(True)
    
            self.spinBoxMapSizeX.setValue(size.width()),
            self.spinBoxMapSizeY.setValue(size.height())
            self.mLastMapSize = QSize(size)
            for w in ws:
                w.blockSignals(False)
            self.mLastMapSize = QSize(size)
            if b:
                self.sigMapSizeChanged.emit(size)
    
        def onMapSizeChanged(self, dim):
            newSize = self.mapSize()
            #1. set size of other dimension accordingly
            if dim is not None:
                if self.checkBoxKeepSubsetAspectRatio.isChecked():
                    if dim == 'X':
                        vOld = self.mLastMapSize.width()
                        vNew = newSize.width()
                        targetSpinBox = self.spinBoxMapSizeY
                    elif dim == 'Y':
                        vOld = self.mLastMapSize.height()
                        vNew = newSize.height()
                        targetSpinBox = self.spinBoxMapSizeX
    
                    oldState = targetSpinBox.blockSignals(True)
                    targetSpinBox.setValue(int(round(float(vNew) / vOld * targetSpinBox.value())))
                    targetSpinBox.blockSignals(oldState)
                    newSize = self.mapSize()
                if newSize != self.mLastMapSize:
                    self.btnApplySizeChanges.setEnabled(True)
            else:
                self.sigMapSizeChanged.emit(self.mapSize())
                self.btnApplySizeChanges.setEnabled(False)
            self.setMapSize(newSize)
    
        def mapSize(self)->QSize:
            return QSize(self.spinBoxMapSizeX.value(),
                         self.spinBoxMapSizeY.value())
    
    
        def dummySlot(self):
            s  =""
    
        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 mapBackgroundColor(self)->QColor:
            """
            Returns the map canvas background color
            :return: QColor
            """
            return self.btnMapCanvasColor.color()
    
    
        def setMapBackgroundColor(self, color:QColor):
            """
            Sets the MapCanvas background color
            :param color: QColor
            """
            if color != self.mapBackgroundColor():
                self.btnMapCanvasColor.setColor(color)
    
        def setMapTextColor(self, color:QColor):
            """
            Sets the map text color
            :param color: QColor
            :return: QColor
            """
            if color != self.mapTextColor():
                self.btnMapTextColor.setColor(color)
            return self.mapTextColor()
    
        def mapTextColor(self)->QColor:
            """
            Returns the map text color.
            :return: QColor
            """
            return self.btnMapTextColor.color()
    
    
        def onMapViewsAdded(self, mapViews):
            nextShown = None
            for mapView in mapViews:
                mapView.sigTitleChanged.connect(self.updateTitle)
                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)
    
            for mapView in mapViews:
                self.sigMapViewAdded.emit(mapView)
    
        def updateButtons(self, *args):
            b = len(self.mMapViews) > 0
            self.actionRemoveMapView.setEnabled(b)
            self.actionApplyStyles.setEnabled(b)
            self.actionHighlightMapView.setEnabled(b)
    
    
        def createMapView(self, name:str=None)->MapView:
            """
            Create a new MapView
            :return: MapView
            """
    
            mapView = MapView()
    
            n = len(self.mapViews()) + 1
            if isinstance(name, str) and len(name) > 0:
                title = name
            else:
                title = 'Map View {}'.format(n)
                while title in [m.title() for m in self.mapViews()]:
                    n += 1
                    title = 'Map View {}'.format(n)
    
            if n == 1:
                mapView.optionShowDate.setChecked(True)
                mapView.optionShowSensorName.setChecked(True)
    
            mapView.setTitle(title)
            mapView.sigShowProfiles.connect(self.sigShowProfiles)
            mapView.setTimeSeries(self.mTimeSeries)
            self.addMapView(mapView)
            return mapView
    
        def onInfoOptionToggled(self):
    
            self.sigMapInfoChanged.emit()
            s = ""
    
        def addMapView(self, mapView:MapView):
            """
            Adds a MapView
            :param mapView: MapView
            """
            assert isinstance(mapView, MapView)
            if mapView not in self:
                mapView.sigTitleChanged.connect(lambda *args, mv=mapView: self.onMapViewUpdated(mv))
                #mapView.sigVisibilityChanged.connect(lambda *args, mv=mapView: self.onMapViewUpdated(mv))
                mapView.sigCanvasAppearanceChanged.connect(lambda *args, mv=mapView: self.onMapViewUpdated(mv))
                self.sigMapCanvasColorChanged.connect(mapView.setMapBackgroundColor)
    
                self.sigMapCanvasTextFormatChanged.connect(mapView.setMapTextFormat)
    
                i = self.toolBox.addItem(mapView, mapView.windowIcon(), mapView.title())
                self.toolBox.setCurrentIndex(i)
    
                self.onMapViewUpdated(mapView)
    
    
                if len(self.mapViews()) == 1:
                    self.setMapTextFormat(mapView.mapTextFormat())
    
    
                self.sigMapViewAdded.emit(mapView)
    
        def onToolboxIndexChanged(self):
    
            b = isinstance(self.toolBox.currentWidget(), MapView)
            self.actionRemoveMapView.setEnabled(b)
    
        def onMapViewUpdated(self, mapView:MapView):
            """
            Handles updates that react on MapView changes
            :param mapView: MapView to make the update for
            """
            numMV = 0
            for i in range(self.toolBox.count()):
                item = self.toolBox.widget(i)
                if isinstance(item, MapView):
                    numMV += 1
                if item == mapView:
    
                    if mapView.isVisible():
                        icon = QIcon(":/timeseriesviewer/icons/mapview.svg")
                    else:
                        icon = QIcon(":/timeseriesviewer/icons/mapviewHidden.svg")
    
                    self.toolBox.setItemIcon(i, icon)
                    self.toolBox.setItemText(i, 'Map View {} "{}"'.format(numMV, mapView.title()))
                    break
    
        def removeMapView(self, mapView:MapView)->MapView:
            """
            Removes a MapView
            :param mapView: MapView
            :return: MapView
            """
            if mapView in self.mapViews():
                for i in range(self.toolBox.count()):
                    w = self.toolBox.widget(i)
                    if isinstance(w, MapView) and w == mapView:
                        self.toolBox.removeItem(i)
                        mapView.close()
                        if self.toolBox.count() >= i:
                            self.toolBox.setCurrentIndex(min(i, self.toolBox.count()-1))
    
                self.sigMapViewRemoved.emit(mapView)
            return mapView
    
        def __len__(self)->int:
            """
            Returns the number of MapViews
            :return: int
            """
            return len(self.mapViews())