Commit 2190a87f authored by Benjamin Jakimow's avatar Benjamin Jakimow
Browse files

in general: refactoring

settings.py and settingsdialog.ui:
- added QgsTaskAsync
- added QgsTaskBlockSize
- added BandStatsSampleSize

mapcanvas.py: MapCanvas
- stretchToCurrentExtent returns bool if stretch found an bbox intersecting raster layer

mapvisualization.py
- fixed default sensor specific stretching

updated QPS:
- crosshair shows unit even in case of CRS without description
- small relative import fix in utils.py
- improved checkWavelengthUnit

temporalprofiles.py
- fixed invalid index error for PlotStyle combobox in case TemporalProfiles are deleted
- modified profile background loading, should be much faster now

timeseries.py
- added TimeSeriesSource.clone()
- added TimeSeriesFindOverlapTask to find images with valid pixels within a spatial extent
- added convenience accessors
parent c5d4fc15
......@@ -194,7 +194,6 @@ class CrosshairMapCanvasItem(QgsMapCanvasItem):
assert nPx == 1 or nPx % 3 == 0, 'Size of pixel box must be an odd integer value (1,3,5...)'
self.mSizePixelBox = nPx
def setCrosshairStyle(self, crosshairStyle:CrosshairStyle):
"""
Sets the CrosshairStyle
......@@ -268,14 +267,11 @@ class CrosshairMapCanvasItem(QgsMapCanvasItem):
crs = self.mCanvas.mapSettings().destinationCrs()
assert isinstance(crs, QgsCoordinateReferenceSystem)
if crs.description() == '':
labelText = 'CRS unspecified'
unitString = str(QgsUnitTypes.encodeUnit(crs.mapUnits()))
if unitString == 'meters':
labelText = scaledUnitString(pred, suffix='m')
else:
unitString = str(QgsUnitTypes.encodeUnit(crs.mapUnits()))
if unitString == 'meters':
labelText = scaledUnitString(pred, suffix='m')
else:
labelText = '{}{}'.format(pred, unitString)
labelText = '{}{}'.format(pred, unitString)
pen = QPen(Qt.SolidLine)
pen.setWidth(self.mCrosshairStyle.mThickness)
......@@ -308,7 +304,6 @@ class CrosshairMapCanvasItem(QgsMapCanvasItem):
p = QPolygonF(p)
polygons.append(p)
if self.mCrosshairStyle.mShowPixelBorder:
lyr = self.rasterGridLayer()
......@@ -323,8 +318,6 @@ class CrosshairMapCanvasItem(QgsMapCanvasItem):
ms = self.mCanvas.mapSettings()
centerPxLyr = ms.mapToLayerCoordinates(lyr, centerGeo)
#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))
......@@ -337,9 +330,7 @@ class CrosshairMapCanvasItem(QgsMapCanvasItem):
lyrCoord2CanvasPx = lambda x, y, : self.toCanvasCoordinates(
ms.layerToMapCoordinates(lyr,
px2LayerGeo(x, y)))
if pxX >= 0 and pxY >= 0 and \
pxX < ns and pxY < nl:
if pxX >= 0 and pxY >= 0 and pxX < ns and pxY < nl:
#get pixel edges in map canvas coordinates
lyrGeo = px2LayerGeo(pxX, pxY)
......@@ -382,8 +373,6 @@ class CrosshairMapCanvasItem(QgsMapCanvasItem):
painter.drawLine(p)
def nicePredecessor(l):
mul = -1 if l < 0 else 1
l = np.abs(l)
......@@ -437,14 +426,12 @@ class CrosshairWidget(QWidget):
self.cbShowDistanceMarker.toggled.connect(self.refreshCrosshairPreview)
self.refreshCrosshairPreview()
def copyCanvas(self,mapCanvas:QgsMapCanvas):
def copyCanvas(self,mapCanvas: QgsMapCanvas):
"""
Copys layers,crs, extent and background color
:param mapCanvas:
:return:
"""
assert isinstance(mapCanvas, QgsMapCanvas)
# copy layers
canvas = self.mapCanvas
......@@ -457,8 +444,6 @@ class CrosshairWidget(QWidget):
self.mapCanvasItem.setPosition(canvas.center())
self.refreshCrosshairPreview()
def setCanvasColor(self, color):
self.mapCanvas.setBackgroundColor(color)
self.btnMapCanvasColor.colorChanged.connect(self.onMapCanvasColorChanged)
......@@ -503,9 +488,11 @@ class CrosshairWidget(QWidget):
style.setShowDistanceMarker(self.cbShowDistanceMarker.isChecked())
return style
def getCrosshairStyle(*args, **kwds):
return CrosshairDialog.getCrosshairStyle(*args, **kwds)
class CrosshairDialog(QgsDialog):
@staticmethod
......@@ -552,7 +539,7 @@ class CrosshairDialog(QgsDialog):
"""
return self.w.crosshairStyle()
def setCrosshairStyle(self, crosshairStyle:CrosshairStyle):
def setCrosshairStyle(self, crosshairStyle: CrosshairStyle):
"""
Sets a new Crosshair Style
:param crosshairStyle: CrosshairStyle
......@@ -561,7 +548,7 @@ class CrosshairDialog(QgsDialog):
assert isinstance(crosshairStyle, CrosshairStyle)
self.w.setCrosshairStyle(crosshairStyle)
def copyCanvas(self, mapCanvas:QgsMapCanvas):
def copyCanvas(self, mapCanvas: QgsMapCanvas):
"""
Copies the map canvas layers and background color
:param mapCanvas: QgsMapCanvas
......
......@@ -14,7 +14,7 @@ from qgis.PyQt import uic
from osgeo import gdal, ogr
import numpy as np
from qgis.PyQt.QtWidgets import QAction, QMenu, QToolButton, QDialogButtonBox, QLabel, QGridLayout, QMainWindow
from . import DIR_UI_FILES
# dictionary to store form classes and avoid multiple calls to read <myui>.ui
QGIS_RESOURCE_WARNINGS = set()
......@@ -1029,48 +1029,93 @@ def parseWavelength(dataset) -> typing.Tuple[np.ndarray, str]:
except:
pass
def checkWavelengthUnit(key:str, value:str) -> str:
wlu = None
if re.search(r'wavelength.units?', key, re.I):
if re.search(r'(Micrometers?|um|μm)', values, re.I):
wlu = 'μm' # fix with python 3 UTF
elif re.search(r'(Nanometers?|nm)', values, re.I):
wlu = 'nm'
elif re.search(r'(Millimeters?|mm)', values, re.I):
wlu = 'nm'
elif re.search(r'(Centimeters?|cm)', values, re.I):
wlu = 'nm'
elif re.search(r'(Meters?|m)', values, re.I):
wlu = 'nm'
elif re.search(r'Wavenumber', values, re.I):
wlu = '-'
elif re.search(r'GHz', values, re.I):
wlu = 'GHz'
elif re.search(r'MHz', values, re.I):
wlu = 'MHz'
elif re.search(r'(Date|DTG|DateTimeGroup|DateStamp|TimeStamp)', values, re.I):
wlu = 'DateTime'
elif re.search(r'Index', values, re.I):
wlu = None
else:
wlu = None
return wlu
def checkWavelength(key: str, values:str):
wl = None
if re.search(r'wavelengths?$', key, re.I):
# remove trailing / ending { } and whitespace
values = re.sub('[{}]', '', values).strip()
if ',' not in values:
sep = ' '
else:
sep = ','
try:
wl = np.fromstring(values, sep=sep)
except Exception as ex:
pass
return wl
if isinstance(dataset, gdal.Dataset):
for domain in dataset.GetMetadataDomainList():
# see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for supported wavelength units
mdDict = dataset.GetMetadata_Dict(domain)
for key, values in mdDict.items():
key = key.lower()
if re.search(r'wavelength$', key, re.I):
tmp = re.findall(r'\d*\.\d+|\d+', values) # find floats
if len(tmp) != dataset.RasterCount:
tmp = re.findall(r'\d+', values) # find integers
if len(tmp) == dataset.RasterCount:
wl = np.asarray([float(w) for w in tmp])
if wl is None and len(tmp) > 0 and len(tmp) != dataset.RasterCount:
print('Wavelength definition in "{}" contains {} instead {} values'
.format(key, len(tmp), dataset.RasterCount), file=sys.stderr)
if re.search(r'wavelength.units?', key):
if re.search(r'(Micrometers?|um|μm)', values, re.I):
wlu = 'μm' # fix with python 3 UTF
elif re.search(r'(Nanometers?|nm)', values, re.I):
wlu = 'nm'
elif re.search(r'(Millimeters?|mm)', values, re.I):
wlu = 'nm'
elif re.search(r'(Centimeters?|cm)', values, re.I):
wlu = 'nm'
elif re.search(r'(Meters?|m)', values, re.I):
wlu = 'nm'
elif re.search(r'Wavenumber', values, re.I):
wlu = '-'
elif re.search(r'GHz', values, re.I):
wlu = 'GHz'
elif re.search(r'MHz', values, re.I):
wlu = 'MHz'
elif re.search(r'Index', values, re.I):
wlu = '-'
else:
wlu = '-'
if wl is not None and len(wl) > dataset.RasterCount:
wl = wl[0:dataset.RasterCount]
# 1. check on dataset level
domains = dataset.GetMetadataDomainList()
if isinstance(domains, list):
for domain in domains:
# see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for supported wavelength units
mdDict = dataset.GetMetadata_Dict(domain)
for key, values in mdDict.items():
if wl is None:
wl = checkWavelength(key, values)
if wlu is None:
wlu = checkWavelengthUnit(key, values)
if wl is not None and wlu is not None:
break
if wl is not None and wlu is not None:
if len(wl) > dataset.RasterCount:
wl = wl[0:dataset.RasterCount]
return wl, wlu
# 2. check on band level
wl = []
for b in range(dataset.RasterCount):
band: gdal.Band = dataset.GetRasterBand(b+1)
domains = band.GetMetadataDomainList()
if isinstance(domains, list):
for domain in domains:
# see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for supported wavelength units
mdDict = band.GetMetadata_Dict(domain)
_wl = None
for key, values in mdDict.items():
if wlu is None:
wlu = checkWavelengthUnit(key, values)
if _wl is None:
_wl = checkWavelength(key, values)
if wlu and _wl:
wl.append(_wl[0])
break
if len(wl) == 0:
wl = None
else:
wl = np.asarray(wl)
return wl, wlu
......@@ -1638,6 +1683,27 @@ def setToolButtonDefaultActionMenu(toolButton:QToolButton, actions:list):
menu.triggered.connect(toolButton.setDefaultAction)
toolButton.setMenu(menu)
class SearchFilesDialog(QgsDialog):
"""
A dialog to select multiple files
"""
sigFilesFound = pyqtSignal(list)
def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
loadUi(DIR_UI_FILES / 'searchfilesdialog.ui', self)
self.btnMatchCase.setDefaultAction(self.optionMatchCase)
self.btnRegex.setDefaultAction(self.optionRegex)
self.btnRecursive.setDefaultAction(self.optionRecursive)
self.plainTextEdit: QPlainTextEdit
def validate(self):
s = ""
class SelectMapLayersDialog(QgsDialog):
......
......@@ -297,10 +297,11 @@ class TaskManagerStatusButton(QToolButton):
self.setLayout(QHBoxLayout())
from eotimeseriesviewer.temporalprofiles import TemporalProfileLoaderTask
from eotimeseriesviewer.timeseries import TimeSeriesLoadingTask
from eotimeseriesviewer.timeseries import TimeSeriesLoadingTask, TimeSeriesFindOverlapTask
self.mTrackedTasks = [
TemporalProfileLoaderTask,
TimeSeriesFindOverlapTask,
TimeSeriesLoadingTask
]
......@@ -340,7 +341,7 @@ class TaskManagerStatusButton(QToolButton):
from eotimeseriesviewer.temporalprofiles import TemporalProfileLoaderTask
from eotimeseriesviewer.timeseries import TimeSeriesLoadingTask
if isinstance(task, (TemporalProfileLoaderTask, TimeSeriesLoadingTask)):
if isinstance(task, (TemporalProfileLoaderTask, TimeSeriesFindOverlapTask, TimeSeriesLoadingTask)):
task.progressChanged.connect(self.updateTaskInfo)
task.taskCompleted.connect(self.updateTaskInfo)
task.taskTerminated.connect(self.updateTaskInfo)
......@@ -924,18 +925,14 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
ca.setChecked(ca == a)
self.setMapTool(MapTools.SelectFeature)
def onSelectFeatureTriggered(self):
self.setMapTool(MapTools.SelectFeature)
def initQGISConnection(self):
"""
Initializes interactions between TimeSeriesViewer and the QGIS instances
:return:
"""
iface = qgis.utils.iface
assert isinstance(iface, QgisInterface)
......@@ -944,11 +941,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
self.ui.actionExportCenter.triggered.connect(lambda: iface.mapCanvas().setCenter(self.spatialCenter().toCrs(iface.mapCanvas().mapSettings().destinationCrs())))
self.ui.actionImportCenter.triggered.connect(lambda: self.setSpatialCenter(SpatialPoint.fromMapCanvasCenter(iface.mapCanvas())))
def onSyncRequest(qgisChanged:bool):
if self.ui.optionSyncMapCenter.isChecked():
self.ui.mMapWidget.syncQGISCanvasCenter(qgisChanged)
self.mapWidget().setCrs(iface.mapCanvas().mapSettings().destinationCrs())
def onShowSettingsDialog(self):
from eotimeseriesviewer.settings import SettingsDialog
......@@ -1236,7 +1229,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
if path is not None and os.path.exists(path):
s.setValue('file_ts_definition', path)
self.clearTimeSeries()
# self.clearTimeSeries()
self.mTimeSeries.loadFromFile(path, n_max=n_max, runAsync=runAsync)
def currentLayer(self) -> QgsMapLayer:
......
......@@ -417,13 +417,14 @@ class MapCanvas(QgsMapCanvas):
self.mapCanvasRefreshed.connect(onMapCanvasRefreshed)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
bg = eotimeseriesviewer.settings.value(eotimeseriesviewer.settings.Keys.MapBackgroundColor, default=QColor(0, 0, 0))
bg = eotimeseriesviewer.settings.value(
eotimeseriesviewer.settings.Keys.MapBackgroundColor, default=QColor(0, 0, 0))
self.setCanvasColor(bg)
self.setContextMenuPolicy(Qt.DefaultContextMenu)
self.extentsChanged.connect(lambda : self.sigSpatialExtentChanged.emit(self.spatialExtent()))
self.extentsChanged.connect(lambda *args: self.sigSpatialExtentChanged.emit(self.spatialExtent()))
self.destinationCrsChanged.connect(lambda : self.sigDestinationCrsChanged.emit(self.crs()))
self.destinationCrsChanged.connect(lambda *args: self.sigDestinationCrsChanged.emit(self.crs()))
def userInputWidget(self) -> QgsUserInputWidget:
"""
......@@ -432,7 +433,6 @@ class MapCanvas(QgsMapCanvas):
"""
return self.mUserInputWidget
def infoItem(self) -> MapCanvasInfoItem:
"""
Returns the MapCanvasInfoItem, e.g. to plot text on top of the map canvas
......@@ -573,7 +573,7 @@ class MapCanvas(QgsMapCanvas):
"""
return self.mTSD
def setSpatialExtent(self, extent:SpatialExtent):
def setSpatialExtent(self, extent: SpatialExtent):
"""
Sets the spatial extent
:param extent: SpatialExtent
......@@ -582,7 +582,7 @@ class MapCanvas(QgsMapCanvas):
extent = extent.toCrs(self.crs())
self.setExtent(extent)
def setSpatialCenter(self, center:SpatialPoint):
def setSpatialCenter(self, center: SpatialPoint):
"""
Sets the SpatialCenter
:param center: SpatialPoint
......@@ -600,7 +600,7 @@ class MapCanvas(QgsMapCanvas):
if self.size() != size:
super(MapCanvas, self).setFixedSize(size)
def setCrs(self, crs:QgsCoordinateReferenceSystem):
def setCrs(self, crs: QgsCoordinateReferenceSystem):
"""
Sets the
:param crs:
......@@ -847,7 +847,7 @@ class MapCanvas(QgsMapCanvas):
"""
return self.grab()
def contextMenu(self, pos:QPoint) -> QMenu:
def contextMenu(self, pos: QPoint) -> QMenu:
"""
Creates the MapCanvas context menu with options relevant for pixel position ``pos``.
:param pos: QPoint
......@@ -935,8 +935,6 @@ class MapCanvas(QgsMapCanvas):
action = m.addAction('Gaussian')
action.triggered.connect(lambda *args, lyr=refSensorLayer: self.stretchToExtent(self.spatialExtent(), 'gaussian', layer=lyr, n=3))
menu.addSeparator()
from .externals.qps.layerproperties import pasteStyleFromClipboard, pasteStyleToClipboard
......@@ -982,7 +980,7 @@ class MapCanvas(QgsMapCanvas):
sub.setIcon(QIcon(r':/images/themes/default/mIconPointLayer.svg'))
a = sub.addAction('Properties...')
a.triggered.connect(lambda *args, lyr = mapLayer: self.onSetLayerProperties(lyr))
a.triggered.connect(lambda *args, lyr=mapLayer: self.onSetLayerProperties(lyr))
a = sub.addAction('Zoom to Layer')
......@@ -1130,8 +1128,9 @@ class MapCanvas(QgsMapCanvas):
ts = eotsv.timeSeries()
action = menu.addAction('Focus on Spatial Extent')
action.triggered.connect(ts.focusVisibilityToExtent)
action = menu.addAction('Focus to Spatial Extent')
action.setToolTip('Sets the image visibility on True for images with valid pixels in the current extent.')
action.triggered.connect(lambda *args, ext=self.spatialExtent(): ts.focusVisibilityToExtent(ext=ext))
action = menu.addAction('Hide Date')
action.triggered.connect(lambda *args: ts.hideTSDs([tsd]))
......@@ -1144,7 +1143,7 @@ class MapCanvas(QgsMapCanvas):
def onSetLayerProperties(self, lyr:QgsRasterLayer):
# b = isinstance(mapLayer, SensorProxyLayer) == False:
result = showLayerPropertiesDialog(lyr, self, useQGISDialog=True)
result = showLayerPropertiesDialog(lyr, self, useQGISDialog=False)
if result == QDialog.Accepted and isinstance(lyr, SensorProxyLayer):
r = lyr.renderer().clone()
......@@ -1187,25 +1186,26 @@ class MapCanvas(QgsMapCanvas):
lqgis = iface.addVectorLayer(l.source(), l.name(), 'ogr')
lqgis.setRenderer(l.renderer().clone())
def stretchToCurrentExtent(self):
def stretchToCurrentExtent(self) -> bool:
"""
Stretches the top-raster layer band to the current spatial extent
return: True, if a QgsRasterLayer was found to perform the stretch
"""
se = self.spatialExtent()
self.stretchToExtent(se, stretchType='linear_minmax', p=0.05)
return self.stretchToExtent(se, stretchType='linear_minmax', p=0.05)
def stretchToExtent(self,
spatialExtent:SpatialExtent = None,
stretchType='linear_minmax',
layer:QgsRasterLayer = None,
**stretchArgs):
**stretchArgs) -> bool:
"""
:param spatialExtent: rectangle to get the image statistics for
:param stretchType: ['linear_minmax' (default), 'gaussian']
:param stretchArgs:
linear_minmax: 'p' percentage from min/max, e.g. +- 5 %
gaussian: 'n' mean +- n* standard deviations
:return:
:return: True, if a QgsRasterLayer was found to perform the stretch
"""
if not isinstance(layer, QgsRasterLayer):
layers = [l for l in self.layers() if isinstance(l, SensorProxyLayer)]
......@@ -1290,7 +1290,6 @@ class MapCanvas(QgsMapCanvas):
elif isinstance(layer, QgsRasterLayer):
layer.setRenderer(newRenderer)
def saveMapImageDialog(self, fileType):
"""
Opens a dialog to save the map as local file
......
......@@ -47,7 +47,7 @@ from .timeseries import SensorInstrument, TimeSeriesDate, TimeSeries, SensorProx
from .utils import loadUi
from .mapviewscrollarea import MapViewScrollArea
from .mapcanvas import MapCanvas, MapTools, MapCanvasInfoItem, MapCanvasMapTools, KEY_LAST_CLICKED
from eotimeseriesviewer import debugLog
from .externals.qps.crosshair.crosshair import getCrosshairStyle, CrosshairStyle, CrosshairMapCanvasItem
from .externals.qps.layerproperties import showLayerPropertiesDialog
from .externals.qps.maptools import *
......@@ -96,6 +96,8 @@ class MapView(QFrame):
self.mMapTextFormat = DEFAULT_VALUES[Keys.MapTextFormat]
self.mMapWidget = None
self.mInitialStretch: typing.Dict[SensorInstrument, bool] = dict()
self.mTimeSeries = None
self.mSensorLayerList = list()
self.mCrossHairStyle = CrosshairStyle()
......@@ -530,11 +532,13 @@ class MapView(QFrame):
layerTreeLayer.setCustomProperty(KEY_LOCKED_LAYER, True)
layerTreeLayer.setCustomProperty(KEY_SENSOR_LAYER, True)
self.mSensorLayerList.append((sensor, dummyLayer))
self.mInitialStretch[sensor] = False
def onSensorRendererChanged(self, sensor:SensorInstrument):
def onSensorRendererChanged(self, sensor: SensorInstrument):
for c in self.sensorCanvases(sensor):
assert isinstance(c, MapCanvas)
c.addToRefreshPipeLine(MapCanvas.Command.RefreshRenderer)
self.mInitialStretch[sensor] = True
def sensorCanvases(self, sensor:SensorInstrument) -> list:
"""
......@@ -568,6 +572,9 @@ class MapView(QFrame):
:param sensor:
:return:
"""
self.mInitialStretch.pop(sensor)
toRemove = []
for t in self.mSensorLayerList:
if t[0] == sensor:
......@@ -925,7 +932,6 @@ class MapWidget(QFrame):
self.mCanvasSignals = dict()
self.mTimeSeries = None
self.mMapToolKey = MapTools.Pan
self.mViewMode = MapWidget.ViewMode.MapViewByRows
......@@ -987,8 +993,6 @@ class MapWidget(QFrame):
def mapTextFormat(self) -> QgsTextFormat:
return self.mMapTextFormat
def setMapTool(self, mapToolKey:MapTools):
if self.mMapToolKey != mapToolKey:
......@@ -1004,7 +1008,6 @@ class MapWidget(QFrame):
Returns the list of currently shown TimeSeriesDates.
:return: [list-of-TimeSeriesDates]
"""
for mv in self.mMapViews:
tsds = []
for c in self.mCanvases[mv]:
......@@ -1021,18 +1024,22 @@ class MapWidget(QFrame):
"""
return self.mSpatialExtent
def setSpatialExtent(self, extent:SpatialExtent) -> SpatialExtent:
def setSpatialExtent(self, extent: SpatialExtent) -> SpatialExtent:
"""
Sets a SpatialExtent to all MapCanvases.
:param extent: SpatialExtent
:return: SpatialExtent the current SpatialExtent
"""
if type(extent) == QgsRectangle:
extent = SpatialExtent(self.crs(), extent)
try:
assert isinstance(extent, SpatialExtent), 'Expected SpatialExtent, but got {} {}'.format(type(extent), extent)
except Exception as ex:
info = [traceback.format_exc()]
info.append(str(ex))
debugLog('\n'.join(info))
traceback.print_exception(*sys.exc_info())
raise ex
return None
if self.mSpatialExtent != extent:
self.mSpatialExtent = extent
......@@ -1059,7 +1066,6 @@ class MapWidget(QFrame):
extent.setCenter(centerNew)
self.setSpatialExtent(extent)
def spatialCenter(self) -> SpatialPoint:
"""
Return the center of all map canvas
......@@ -1067,8 +1073,7 @@ class MapWidget(QFrame):
"""
return self.spatialExtent().spatialCenter()
def setCrs(self, crs:QgsCoordinateReferenceSystem) -> QgsCoordinateReferenceSystem:
def setCrs(self, crs: QgsCoordinateReferenceSystem) -> QgsCoordinateReferenceSystem:
"""
Sets the MapCanvas CRS.
:param crs: QgsCoordinateReferenceSystem
......@@ -1089,10 +1094,29 @@ class MapWidget(QFrame):
if self.mSyncQGISMapCanvasCenter:
self.syncQGISCanvasCenter()
canvases = self.mapCanvases()
if len(canvases) != len(self.mapViews()) * self.mMpMV:
canvases2 = []
for mv in self.mapViews():
canvases2.extend(self.mapViewCanvases(mv))