Commit a3540426 authored by Benjamin Jakimow's avatar Benjamin Jakimow
Browse files

MapWidget: is not the central widget, added slider + actions to control the current date


MapCanvasInfoItem: uses QgsTextFormat to control text
Settings: added SensorNames and MapTextFormat keys
TimeSeries: added findDate()
Signed-off-by: Benjamin Jakimow's avatarbenjamin.jakimow <benjamin.jakimow@geo.hu-berlin.de>
parent 7bfb661e
......@@ -21,7 +21,7 @@
# noinspection PyPep8Naming
__version__ = '1.5' # sub-subversion number is added automatically
__version__ = '1.6' # sub-subversion number is added automatically
LICENSE = 'GNU GPL-3'
TITLE = 'EO Time Series Viewer'
DESCRIPTION = 'Visualization of multi-sensor Earth observation time series data.'
......
......@@ -127,7 +127,7 @@ class TimeSeriesViewerUI(QMainWindow,
# #widgets-and-dialogs-with-auto-connect
self.setupUi(self)
self.setCentralWidget(self.mCentralWidget)
self.setCentralWidget(self.mMapWidget)
self.addActions(self.findChildren(QAction))
from eotimeseriesviewer import TITLE, icon, __version__
......@@ -348,7 +348,7 @@ class TimeSeriesViewer(QgisInterface, QObject):
mvd = self.ui.dockMapViews
dts = self.ui.dockTimeSeries
mw = self.ui.mapWidget
mw = self.ui.mMapWidget
from eotimeseriesviewer.timeseries import TimeSeriesDock
from eotimeseriesviewer.mapvisualization import MapViewDock, MapWidget
assert isinstance(mvd, MapViewDock)
......@@ -387,13 +387,14 @@ class TimeSeriesViewer(QgisInterface, QObject):
mw.sigMapViewAdded.connect(self.onMapViewAdded)
mw.sigCurrentLocationChanged.connect(self.setCurrentLocation)
self.ui.actionNextTSD.triggered.connect(mw.moveToNextTSD)
self.ui.actionPreviousTSD.triggered.connect(mw.moveToPreviousTSD)
self.ui.actionNextTSDFast.triggered.connect(mw.moveToNextTSDFast)
self.ui.actionPreviousTSDFast.triggered.connect(mw.moveToPreviousTSDFast)
self.ui.actionFirstTSD.triggered.connect(mw.moveToFirstTSD)
self.ui.actionLastTSD.triggered.connect(mw.moveToLastTSD)
tb = self.ui.toolBarTimeControl
assert isinstance(tb, QToolBar)
tb.addAction(mw.actionFirstDate)
tb.addAction(mw.actionBackwardFast)
tb.addAction(mw.actionBackward)
tb.addAction(mw.actionForward)
tb.addAction(mw.actionForwardFast)
tb.addAction(mw.actionLastDate)
tstv = self.ui.dockTimeSeries.timeSeriesTreeView
assert isinstance(tstv, TimeSeriesTreeView)
......@@ -693,7 +694,7 @@ class TimeSeriesViewer(QgisInterface, QObject):
Moves the viewport of the scroll window to a specific TimeSeriesDate
:param tsd: TimeSeriesDate
"""
self.ui.mapWidget.setCurrentDate(tsd)
self.ui.mMapWidget.setCurrentDate(tsd)
def mapCanvases(self)->list:
......@@ -701,7 +702,7 @@ class TimeSeriesViewer(QgisInterface, QObject):
Returns all MapCanvases of the spatial visualization
:return: [list-of-MapCanvases]
"""
return self.ui.mapWidget.mapCanvases()
return self.ui.mMapWidget.mapCanvases()
def mapLayerStore(self)->QgsMapLayerStore:
"""
......@@ -722,7 +723,7 @@ class TimeSeriesViewer(QgisInterface, QObject):
x, y = c.asPoint()
crs = layer.crs()
center = SpatialPoint(crs, x, y)
self.ui.mapWidget.setSpatialCenter(center)
self.ui.mMapWidget.setSpatialCenter(center)
self.ui.actionRefresh.trigger()
def onSelectFeatureOptionTriggered(self):
......@@ -760,9 +761,9 @@ class TimeSeriesViewer(QgisInterface, QObject):
def onSyncRequest(qgisChanged:bool):
if self.ui.optionSyncMapCenter.isChecked():
self.ui.mapWidget.syncQGISCanvasCenter(qgisChanged)
self.ui.mMapWidget.syncQGISCanvasCenter(qgisChanged)
self.ui.mapWidget.sigSpatialExtentChanged.connect(lambda: onSyncRequest(False))
self.ui.mMapWidget.sigSpatialExtentChanged.connect(lambda: onSyncRequest(False))
iface.mapCanvas().extentsChanged.connect(lambda: onSyncRequest(True))
......@@ -780,7 +781,7 @@ class TimeSeriesViewer(QgisInterface, QObject):
def applySettings(self):
"""
Reads the QSettings object and applies its value to related widget components
Reads the QSettings object and applies its values to related widget components
"""
......@@ -798,19 +799,21 @@ class TimeSeriesViewer(QgisInterface, QObject):
v = value(Keys.MapUpdateInterval)
if isinstance(v, int) and v > 0:
self.ui.mapWidget.mMapRefreshTimer.start(v)
self.ui.mMapWidget.mMapRefreshTimer.start(v)
v = value(Keys.MapBackgroundColor)
if isinstance(v, QColor):
self.ui.dockMapViews.setMapBackgroundColor(v)
v = value(Keys.MapTextColor)
if isinstance(v, QColor):
self.ui.dockMapViews.setMapTextColor(v)
v = value(Keys.MapTextFormat)
if isinstance(v, QgsTextFormat):
self.ui.dockMapViews.setMapTextFormat(v)
v = value(Keys.MapSize)
if isinstance(v, QSize):
self.ui.mapWidget.setMapSize(v)
self.ui.mMapWidget.setMapSize(v)
def setMapTool(self, mapToolKey, *args, **kwds):
"""
......@@ -831,7 +834,7 @@ class TimeSeriesViewer(QgisInterface, QObject):
elif self.ui.optionSelectFeaturesRadius.isChecked():
mapToolKey = MapTools.SelectFeatureByRadius
self.ui.mapWidget.setMapTool(mapToolKey, *args)
self.ui.mMapWidget.setMapTool(mapToolKey, *args)
kwds = {}
......@@ -984,7 +987,7 @@ class TimeSeriesViewer(QgisInterface, QObject):
Returns the QgsMessageBar that is used to show messages in the TimeSeriesViewer UI.
:return: QgsMessageBar
"""
return self.ui.messageBar
return self.ui.mMapWidget.messageBar()
def loadTimeSeriesDefinition(self, path:str=None, n_max:int=None):
......@@ -1069,8 +1072,8 @@ class TimeSeriesViewer(QgisInterface, QObject):
extent = self.mTimeSeries.maxSpatialExtent()
self.ui.mapWidget.setCrs(extent.crs())
self.ui.mapWidget.setSpatialExtent(extent)
self.ui.mMapWidget.setCrs(extent.crs())
self.ui.mMapWidget.setSpatialExtent(extent)
self.mSpatialMapExtentInitialized = True
......@@ -1078,6 +1081,8 @@ class TimeSeriesViewer(QgisInterface, QObject):
if len(self.mTimeSeries) == 0:
self.mSpatialMapExtentInitialized = False
def mapWidget(self):
return self.ui.mMapWidget
def saveTimeSeriesDefinition(self):
s = settings()
......
......@@ -103,82 +103,168 @@ class MapLoadingInfoItem(QgsMapCanvasItem):
QApplication.style().drawControl(QStyle.CE_ProgressBar, options, painter)
class MapCanvasInfoItem(QgsMapCanvasItem):
"""
A QgsMapCanvasItem to show text
"""
def __init__(self, mapCanvas):
assert isinstance(mapCanvas, QgsMapCanvas)
super(MapCanvasInfoItem, self).__init__(mapCanvas)
self.mCanvas = mapCanvas
self.mULText = None
self.mLLText = None
self.mLRText = None
self.mURText = None
self.mUCText = None
self.mLCText = None
self.mText = dict()
self.mWrapChar = '\n'
self.mTextFormat = QgsTextFormat()
self.mTextFormat.setSizeUnit(QgsUnitTypes.RenderPixels)
self.mTextFormat.setFont(QFont('Helvetica', pointSize=10))
self.mTextFormat.setColor(QColor('yellow'))
def setWrapChar(self, c:str)->str:
"""
Sets a Wrap Character
:param c:
:return:
"""
self.mWrapChar = c
return self.wrapChar()
self.mPenColor = QColor('yellow')
self.mVisibility = True
def wrapChar(self)->str:
return self.mWrapChar
def setText(self, text:str, alignment:Qt.Alignment=Qt.AlignTop | Qt.AlignHCenter):
self.mText[alignment] = text
def setTextFormat(self, format:QgsTextFormat):
assert isinstance(format, QgsTextFormat)
self.mTextFormat = format
self.updateCanvas()
def textFormat(self)->QgsTextFormat:
"""
Returns the text format.
:return: QgsTextFormat
"""
return self.mTextFormat
def font(self)->QFont:
"""
Returns the font used to write text on the map canvas.
:return: QFont
"""
return self.mTextFormat.font()
def setFont(self, font:QFont):
self.mTextFormat.setFont(font)
def setColor(self, color:QColor):
"""
Sets the map info color
:param color: QColor
"""
self.mPenColor = color
self.mTextFormat.setColor(color)
def color(self)->QColor:
"""
Returns the info text color
:return: QColor
"""
return self.mPenColor
return self.mTextFormat.color()
def setVisibility(self, b:bool):
"""
Sets the visibility of the info item
:param b:
:return:
"""
assert isinstance(b, bool)
old = self.mShow
self.mVisibility = b
if old != b:
self.mCanvas.update()
def paintText(self, painter, text:str, flags, rotation=0):
padding = 5
text = text.replace('\\n', '\n')
text = text.split(self.wrapChar())
def visibility(self)->bool:
"""Returns the info items's visibility"""
return self.mVisibility
nl = len(text)
#text = text.split('\\n')
r = QgsTextRenderer()
def paintText(self, painter, text:str, flags, width=10, color=QColor('black') ):
pen = QPen(Qt.SolidLine)
pen.setWidth(width)
pen.setColor(self.mPenColor)
painter.setBrush(Qt.NoBrush)
painter.setPen(Qt.NoPen)
painter.setRenderHint(QPainter.Antialiasing)
text = text.replace('\\n', '\n')
context = QgsRenderContext()
font = QFont('Helvetica', pointSize=10)
painter.setFont(font)
brush = self.mCanvas.backgroundBrush()
c = brush.color()
c.setAlpha(170)
brush.setColor(c)
painter.setBrush(brush)
painter.setPen(Qt.NoPen)
fm = QFontMetrics(font)
#background = QPolygonF(QRectF(backGroundPos, backGroundSize))
#painter.drawPolygon(background)
painter.setPen(pen)
# taken from QGIS Repo src/core/qgspallabeling.cpp
m2p = QgsMapToPixel(1, 0, 0, 0, 0, 0)
context.setMapToPixel(m2p)
context.setScaleFactor(QgsApplication.desktop().logicalDpiX() / 25.4)
context.setUseAdvancedEffects(True)
context.setPainter(painter)
#context.setExtent(self.mCanvas.extent())
#context.setExpressionContext(self.mCanvas.mapSettings().expressionContext())
rect = painter.viewport()
painter.drawText(rect, flags, text)
#painter.drawText(position, text)
vp = QRectF(painter.viewport())
#rect = self.mCanvas.extent().toRectF()
def clearText(self):
textFormat = self.mTextFormat
assert isinstance(textFormat, QgsTextFormat)
th = r.textHeight(context, textFormat, text, QgsTextRenderer.Rect)
tw = r.textWidth(context, textFormat, text)
self.mULText = self.mUCText = self.mURText = None
self.mLLText = self.mLCText = self.mLRText = None
# area to place the text inside
rect = QRectF()
x = 0.5*vp.width()
y = 0.5*vp.height()
hAlign = QgsTextRenderer.AlignCenter
# horizontal position
if bool(flags & Qt.AlignLeft):
x = padding
hAlign = QgsTextRenderer.AlignLeft
elif bool(flags & Qt.AlignHCenter):
x = 0.5 * vp.width()
hAlign = QgsTextRenderer.AlignCenter
elif bool(flags & Qt.AlignRight):
x = vp.width() - padding
hAlign = QgsTextRenderer.AlignRight
# vertical position
if bool(flags & Qt.AlignTop):
y = padding + th - 0.5* (th / nl)
elif bool(flags & Qt.AlignVCenter):
y = 0.5 * (vp.height() + th)
elif bool(flags & Qt.AlignBottom):
y = vp.height() - padding #- th
poo = QPointF(x, y)
r.drawText(poo, rotation, hAlign, text, context, textFormat)
def setUpperLeft(self, text:str):
self.setText(text, Qt.AlignTop | Qt.AlignLeft)
def setMiddleLeft(self, text: str):
self.setText(text, Qt.AlignVCenter | Qt.AlignLeft)
def setLowerLeft(self, text: str):
self.setText(text, Qt.AlignBottom | Qt.AlignLeft)
def setUpperCenter(self, text:str):
self.setText(text, Qt.AlignTop | Qt.AlignHCenter)
def setMiddleCenter(self, text: str):
self.setText(text, Qt.AlignVCenter | Qt.AlignHCenter)
def setLowerCenter(self, text: str):
self.setText(text, Qt.AlignBottom | Qt.AlignHCenter)
def setUpperRight(self, text:str):
self.setText(text, Qt.AlignTop | Qt.AlignRight)
def setMiddleRight(self, text: str):
self.setText(text, Qt.AlignVCenter | Qt.AlignRight)
def setLowerRight(self, text: str):
self.setText(text, Qt.AlignBottom | Qt.AlignRight)
def clearText(self):
self.mText.clear()
def paint(self, painter, QStyleOptionGraphicsItem=None, QWidget_widget=None):
"""
......@@ -188,26 +274,9 @@ class MapCanvasInfoItem(QgsMapCanvasItem):
:param QWidget_widget:
:return:
"""
h = painter.viewport().height()
w = painter.viewport().width()
if self.mLLText:
self.paintText(painter, self.mLLText, Qt.AlignBottom | Qt.AlignLeft )
if self.mLRText:
self.paintText(painter, self.mLRText, Qt.AlignBottom | Qt.AlignRight)
if self.mULText:
self.paintText(painter, self.mULText, Qt.AlignTop | Qt.AlignRight)
if self.mURText:
self.paintText(painter, self.mURText, Qt.AlignTop | Qt.AlignLeft)
if self.mUCText:
self.paintText(painter, self.mUCText, Qt.AlignTop | Qt.AlignHCenter)
if self.mLCText:
self.paintText(painter, self.mLCText, Qt.AlignBottom | Qt.AlignHCenter)
for alignment, text in self.mText.items():
if isinstance(text, str) and len(text) > 0:
self.paintText(painter, text, alignment)
class MapCanvasMapTools(QObject):
......@@ -431,7 +500,7 @@ class MapCanvas(QgsMapCanvas):
assert isinstance(mapView, MapView)
self.mMapView = mapView
self.mInfoItem.setColor(mapView.mapTextColor())
self.mInfoItem.setTextFormat(mapView.mapTextFormat())
self.addToRefreshPipeLine(mapView.mapBackgroundColor())
self.addToRefreshPipeLine(MapCanvas.Command.UpdateMapItems)
......
This diff is collapsed.
......@@ -18,16 +18,13 @@ class Keys(enum.Enum):
MapSize = 'map_size'
MapUpdateInterval = 'map_update_interval'
MapBackgroundColor = 'map_background_color'
MapTextColor = 'map_text_color'
MapTextFormat = 'map_text_format'
SensorNames = 'sensor_names'
ScreenShotDirectory = 'screen_shot_directory'
RasterSourceDirectory = 'raster_source_directory'
VectorSourceDirectory = 'vector_source_directory'
MapImageExportDirectory = 'map_image_export_directory'
_VALID_KEYS = list(Keys)
_VALID_KEYSTRING = [k.value for k in _VALID_KEYS]
_VALID_KEYNAMES = [k.name for k in _VALID_KEYS]
def defaultValues() -> dict:
"""
......@@ -43,18 +40,35 @@ def defaultValues() -> dict:
d[Keys.RasterSourceDirectory] = str(home)
d[Keys.VectorSourceDirectory] = str(home)
d[Keys.DateTimePrecision] = DateTimePrecision.Day
d[Keys.SensorNames] = dict()
# map visualization
d[Keys.MapUpdateInterval] = 500 # milliseconds
d[Keys.MapSize] = QSize(150, 150)
d[Keys.MapBackgroundColor] = QColor('black')
d[Keys.MapTextColor] = QColor('yellow')
textFormat = QgsTextFormat()
textFormat.setColor(QColor('yellow'))
textFormat.setSizeUnit(QgsUnitTypes.RenderPoints)
textFormat.setFont(QFont('Helvetica'))
textFormat.setSize(11)
buffer = QgsTextBufferSettings()
buffer.setColor(QColor('black'))
buffer.setSize(1)
buffer.setSizeUnit(QgsUnitTypes.RenderPixels)
buffer.setEnabled(True)
textFormat.setBuffer(buffer)
d[Keys.MapTextFormat] = textFormat
# tbd. other settings
return d
_DEFAULTS = defaultValues()
DEFAULT_VALUES = defaultValues()
......@@ -67,52 +81,45 @@ def settings()->QSettings:
return settings
def keyString(obj)->str:
"""
Converts a string or key into the appropriate key string accessor for the QSettings object.
:param obj:
:return:
"""
keyString = None
if isinstance(obj, str):
if obj in _VALID_KEYSTRING:
keyString = _VALID_KEYS[_VALID_KEYSTRING.index(obj)].value
elif obj in _VALID_KEYNAMES:
keyString = _VALID_KEYS[_VALID_KEYNAMES.index(obj)].value
else:
keyString = obj
elif isinstance(obj, Keys):
keyString = obj.value
return keyString
def value(key:Keys, default=None):
"""
Provides direct access to a settings value
:param key: str | Keys
:param key: Keys
:param default: default value, defaults to None
:return: value | None
"""
ks = keyString(key)
assert isinstance(key, Keys)
value = None
try:
value = settings().value(ks, defaultValue=default)
value = settings().value(key.value, defaultValue=default)
if value == QVariant():
value = None
if value and key == Keys.MapTextFormat:
s = ""
except TypeError as error:
value = None
settings().setValue(ks, None)
settings().setValue(key.value, None)
print(error, file=sys.stderr)
return value
def setValue(key, value):
def setValue(key:Keys, value):
"""
Shortcut to save a value into the EOTSV settings
:param key: str | Key
:param value: any value
"""
ks = keyString(key)
settings().setValue(ks, value)
assert isinstance(key, Keys)
if isinstance(value, QgsTextFormat):
value = value.toMimeData()
settings().setValue(key.value, value)
def setValues(values: dict):
......@@ -121,15 +128,9 @@ def setValues(values: dict):
:param values: dict
:return:
"""
S = settings()
assert isinstance(S, QSettings)
assert isinstance(values, dict)
for key, val in values.items():
ks = keyString(key)
if isinstance(ks, str):
S.setValue(ks, values[key])
pass
setValue(key, val)
class SettingsDialog(QDialog, loadUI('settingsdialog.ui')):
......@@ -156,6 +157,8 @@ class SettingsDialog(QDialog, loadUI('settingsdialog.ui')):
self.sbMapSizeY.valueChanged.connect(self.validate)
self.sbMapRefreshIntervall.valueChanged.connect(self.validate)
self.mMapTextFormatButton.changed.connect(self.validate)
assert isinstance(self.buttonBox, QDialogButtonBox)
self.buttonBox.button(QDialogButtonBox.RestoreDefaults).clicked.connect(lambda: self.setValues(defaultValues()))
self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.onAccept)
......@@ -194,7 +197,8 @@ class SettingsDialog(QDialog, loadUI('settingsdialog.ui')):
d[Keys.MapSize] = QSize(self.sbMapSizeX.value(), self.sbMapSizeY.value())
d[Keys.MapUpdateInterval] = self.sbMapRefreshIntervall.value()
d[Keys.MapBackgroundColor] = self.mCanvasColorButton.color()
d[Keys.MapTextColor] = self.mCanvasTextColorButton.color()
d[Keys.MapTextFormat] = self.mMapTextFormatButton.textFormat()
return d
def setValues(self, values: dict):
......@@ -238,8 +242,9 @@ class SettingsDialog(QDialog, loadUI('settingsdialog.ui')):
if checkKey(key, Keys.MapBackgroundColor) and isinstance(value, QColor):
self.mCanvasColorButton.setColor(value)
if checkKey(key, Keys.MapTextColor) and isinstance(value, QColor):
self.mCanvasTextColorButton.setColor(value)
if checkKey(key, Keys.MapTextFormat) and isinstance(value, QgsTextFormat):
self.mMapTextFormatButton.setTextFormat(value)
......
......@@ -180,9 +180,10 @@ class SensorInstrument(QObject):
self.wl = np.asarray(self.wl)
if sensor_name is None:
sensor_name = '{}bands@{}m'.format(self.nb, self.px_size_x)
import eotimeseriesviewer.settings
sensor_name = eotimeseriesviewer.settings.value(self._sensorSettingsKey(), sensor_name)
from eotimeseriesviewer.settings import value, Keys
sensorNames = value(Keys.SensorNames)
sensor_name = sensorNames.get(sid, '{}bands@{}m'.format(self.nb, self.px_size_x))