# -*- coding: utf-8 -*- """ /*************************************************************************** HUB TimeSeriesViewer ------------------- begin : 2017-08-04 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 import os from qgis.core import * from qgis.gui import * from PyQt4.QtCore import * from PyQt4.QtGui import * import numpy as np from timeseriesviewer import * from timeseriesviewer.utils import * class CrosshairStyle(object): def __init__(self, **kwds): self.mColor = QColor.fromRgb(255,0,0, 125) self.mThickness = 1 #in px self.mSize = 1.0 #normalized self.mGap = 0.05 #normalized self.mShowDot = True self.mDotSize = 1 #in px self.mSizePixelBorder = 1 self.mShow = True self.mShowPixelBorder = True self.mShowDistanceMarker = True self.mShowDistanceLabel = True def setColor(self, color): assert isinstance(color, QColor) self.mColor = color def setSize(self, size): self.mSize = self._normalize(size) def setDotSize(self, size): assert size >= 0 self.mDotSize = size def setThickness(self, size): """ Crosshair thickness in px :param size: :return: """ assert size >= 0 self.mThickness = size def setShowPixelBorder(self, b): assert isinstance(b, bool) self.mShowPixelBorder = b def setGap(self, gapSize): """ Set gap size in % [0, 100] or normalized coordinates [0,1] :param gapSize: :return: """ self.mGap = self._normalize(gapSize) def setShowDistanceMarker(self, b): assert isinstance(b, bool) self.mShowDistanceMarker = b def _normalize(self, size): assert size >= 0 and size <= 100 size = float(size) if size > 1: size /= 100 return size def setShowDot(self, b): assert isinstance(b, bool) self.mShowDot = b def setShow(self, b): assert isinstance(b, bool) self.mShow = b def rendererV2(self): """ Returns the vector layer renderer :return: """ registry = QgsSymbolLayerV2Registry.instance() lineMeta = registry.symbolLayerMetadata("SimpleLine") lineLayer = lineMeta.createSymbolLayer({}) lineLayer.setColor(self.mColor) lineLayer.setPenStyle(Qt.SolidLine) lineLayer.setWidth(self.mThickness) lineLayer.setWidthUnit(2) #pixel #lineLayer.setWidth(self.mThickness) """ lineLayer = lineMeta.createSymbolLayer( {'width': '0.26', 'color': self.mColor, 'offset': '0', 'penstyle': 'solid', 'use_custom_dash': '0'}) """ # Replace the default layer with our custom layer symbol = QgsLineSymbolV2([]) symbol.deleteSymbolLayer(0) symbol.appendSymbolLayer(lineLayer) return QgsSingleSymbolRendererV2(symbol) class CrosshairMapCanvasItem(QgsMapCanvasItem): def __init__(self, mapCanvas): assert isinstance(mapCanvas, QgsMapCanvas) super(CrosshairMapCanvasItem, self).__init__(mapCanvas) self.canvas = mapCanvas self.rasterGridLayer = None self.sizePixelBox = 0 self.sizePixelBox = 1 self.mShow = True self.crosshairStyle = CrosshairStyle() self.crosshairStyle.setShow(False) self.setCrosshairStyle(self.crosshairStyle) def setShow(self, b): assert isinstance(b, bool) old = self.mShow self.mShow = b self.crosshairStyle.setShow(b) if old != b: self.canvas.update() def connectRasterGrid(self, qgsRasterLayer): if isinstance(qgsRasterLayer): self.rasterGridLayer = qgsRasterLayer else: self.rasterGridLayer = None def setPixelBox(self, nPx): assert nPx >= 0 assert nPx == 1 or nPx % 3 == 0, 'Size of pixel box must be an odd integer' self.sizePixelBox = nPx def setCrosshairStyle(self, crosshairStyle): assert isinstance(crosshairStyle, CrosshairStyle) self.crosshairStyle = crosshairStyle #apply style self.canvas.update() #self.updateCanvas() def paint(self, painter, QStyleOptionGraphicsItem=None, QWidget_widget=None): if self.mShow and self.crosshairStyle.mShow: #paint the crosshair size = self.canvas.size() m2p = self.canvas.mapSettings().mapToPixel() centerGeo = self.canvas.center() centerPx = self.toCanvasCoordinates(self.canvas.center()) x0 = centerPx.x() * (1.0 - self.crosshairStyle.mSize) y0 = centerPx.y() * (1.0 - self.crosshairStyle.mSize) x1 = size.width() - x0 y1 = size.height() - y0 gap = min([centerPx.x(), centerPx.y()]) * self.crosshairStyle.mGap ml = 5 # marker length in pixel, measured from crosshair line md = int(round(max([1, self.crosshairStyle.mDotSize * 0.5]))) #this is what we want to draw lines = [] polygons = [] lines.append(QLineF(x0, centerPx.y(), centerPx.x() - gap, centerPx.y())) lines.append(QLineF(x1, centerPx.y(), centerPx.x() + gap, centerPx.y())) lines.append(QLineF(centerPx.x(), y0, centerPx.x(), centerPx.y() - gap)) lines.append(QLineF(centerPx.x(), y1, centerPx.x(), centerPx.y() + gap)) if self.crosshairStyle.mShowDistanceMarker: extent = self.canvas.extent() maxD = 0.5 * min([extent.width(), extent.height()]) pred = nicePredecessor(maxD) #exp = int(np.log10(maxD)) #md = 10**exp #marker distance = distance to crosshair center pt = m2p.transform(QgsPoint(centerGeo.x()- pred, centerGeo.y())) line = QLineF((pt + QgsVector(0, ml)).toQPointF(), (pt - QgsVector(0, ml)).toQPointF()) lines.append(line) #todo: add more markers if self.crosshairStyle.mShowDistanceLabel: painter.setFont(QFont('Courier', pointSize=10)) font = painter.font() ptLabel = QPointF(pt.x(), pt.y() + (ml + font.pointSize() + 3)) distUnit = self.canvas.mapSettings().destinationCrs().mapUnits() unitString = str(QgsUnitTypes.encodeUnit(distUnit)) if unitString == 'meters': from timeseriesviewer.utils import scaledUnitString labelText = scaledUnitString(pred, suffix='m') else: labelText = '{}{}'.format(pred, unitString) pen = QPen(Qt.SolidLine) pen.setWidth(self.crosshairStyle.mThickness) pen.setColor(self.crosshairStyle.mColor) brush = self.canvas.backgroundBrush() c = brush.color() c.setAlpha(170) brush.setColor(c) painter.setBrush(brush) painter.setPen(Qt.NoPen) fm = QFontMetrics(font) backGroundSize = QSizeF(fm.size(Qt.TextSingleLine, labelText)) backGroundSize = QSizeF(backGroundSize.width()+3,-1*(backGroundSize.height()+3)) backGroundPos = QPointF(ptLabel.x()-3, ptLabel.y()+3) background = QPolygonF(QRectF(backGroundPos, backGroundSize)) painter.drawPolygon(background) painter.setPen(pen) painter.drawText(ptLabel, labelText) if self.crosshairStyle.mShowDot: p = QRectF() p.setTopLeft(QPointF(centerPx.x() - md, centerPx.y() + md)) p.setBottomRight(QPointF(centerPx.x() + md, centerPx.y() - md)) p = QPolygonF(p) polygons.append(p) if self.crosshairStyle.mShowPixelBorder: rasterLayers = [l for l in self.canvas.layers() if isinstance(l, QgsRasterLayer) and l.isValid()] if len(rasterLayers) > 0: lyr = rasterLayers[0] ns = lyr.width() # ns = number of samples = number of image columns nl = lyr.height() # nl = number of lines ex = lyr.extent() xres = lyr.rasterUnitsPerPixelX() yres = lyr.rasterUnitsPerPixelY() ms = self.canvas.mapSettings() centerPxLyr = ms.mapToLayerCoordinates(lyr, centerGeo) #get center pixel pixel index pxX = int(np.floor((centerPxLyr.x() - ex.xMinimum()) / xres).astype(int)) pxY = int(np.floor((ex.yMaximum() - centerPxLyr.y()) / yres).astype(int)) def px2LayerGeo(x, y): x2 = ex.xMinimum() + (x * xres) y2 = ex.yMaximum() - (y * yres) return QgsPoint(x2,y2) lyrCoord2CanvasPx = lambda x, y, : self.toCanvasCoordinates( ms.layerToMapCoordinates(lyr, px2LayerGeo(x, y))) if pxX >= 0 and pxY >= 0 and \ pxX < ns and pxY < nl: #get pixel edges in map canvas coordinates lyrGeo = px2LayerGeo(pxX, pxY) mapGeo = ms.layerToMapCoordinates(lyr, lyrGeo) canCor = self.toCanvasCoordinates(mapGeo) ul = lyrCoord2CanvasPx(pxX, pxY) ur = lyrCoord2CanvasPx(pxX+1, pxY) lr = lyrCoord2CanvasPx(pxX+1, pxY+1) ll = lyrCoord2CanvasPx(pxX, pxY+1) pixelBorder = QPolygonF() pixelBorder.append(ul) pixelBorder.append(ur) pixelBorder.append(lr) pixelBorder.append(ll) pixelBorder.append(ul) pen = QPen(Qt.SolidLine) pen.setWidth(self.crosshairStyle.mSizePixelBorder) pen.setColor(self.crosshairStyle.mColor) pen.setBrush(self.crosshairStyle.mColor) brush = QBrush(Qt.NoBrush) brush.setColor(self.crosshairStyle.mColor) painter.setBrush(brush) painter.setPen(pen) painter.drawPolygon(pixelBorder) pen = QPen(Qt.SolidLine) pen.setWidth(self.crosshairStyle.mThickness) pen.setColor(self.crosshairStyle.mColor) pen.setBrush(self.crosshairStyle.mColor) brush = QBrush(Qt.NoBrush) brush.setColor(self.crosshairStyle.mColor) painter.setBrush(brush) painter.setPen(pen) for p in polygons: painter.drawPolygon(p) for p in lines: painter.drawLine(p) def nicePredecessor(l): mul = -1 if l < 0 else 1 l = np.abs(l) if l > 1.0: exp = np.fix(np.log10(l)) # normalize to [0.0,1.0] l2 = l / 10 ** (exp) m = np.fix(l2) rest = l2 - m if rest >= 0.5: m += 0.5 return mul * m * 10 ** exp elif l < 1.0 and l > 0: exp = np.fix(np.log10(l)) #normalize to [0.0,1.0] m = l / 10 ** (exp-1) if m >= 5: m = 5.0 else: m = 1.0 return mul * m * 10 ** (exp-1) else: return 0.0 class CrosshairWidget(QWidget, loadUi('crosshairwidget.ui')): sigCrosshairStyleChanged = pyqtSignal(CrosshairStyle) def __init__(self, title='<#>', parent=None): super(CrosshairWidget, self).__init__(parent) self.setupUi(self) #self.crossHairReferenceLayer = CrosshairLayer() #self.crossHairReferenceLayer.connectCanvas(self.crossHairCanvas) self.mapCanvas.setExtent(QgsRectangle(0, 0, 1, 1)) # #QgsMapLayerRegistry.instance().addMapLayer(self.crossHairReferenceLayer) #self.crossHairCanvas.setLayerSet([QgsMapCanvasLayer(self.crossHairReferenceLayer)]) #crs = QgsCoordinateReferenceSystem('EPSG:25832') #self.crossHairCanvas.mapSettings().setDestinationCrs(crs) self.mapCanvasItem = CrosshairMapCanvasItem(self.mapCanvas) self.btnCrosshairColor.colorChanged.connect(self.refreshCrosshairPreview) self.spinBoxCrosshairAlpha.valueChanged.connect(self.refreshCrosshairPreview) self.spinBoxCrosshairThickness.valueChanged.connect(self.refreshCrosshairPreview) self.spinBoxCrosshairSize.valueChanged.connect(self.refreshCrosshairPreview) self.spinBoxCrosshairGap.valueChanged.connect(self.refreshCrosshairPreview) self.spinBoxDotSize.valueChanged.connect(self.refreshCrosshairPreview) self.cbCrosshairShowDot.toggled.connect(self.refreshCrosshairPreview) self.cbShowPixelBoundaries.toggled.connect(self.refreshCrosshairPreview) self.cbShowDistanceMarker.toggled.connect(self.refreshCrosshairPreview) self.refreshCrosshairPreview() def setCanvasColor(self, color): self.mapCanvas.setBackgroundColor(color) self.btnMapCanvasColor.colorChanged.connect(self.onMapCanvasColorChanged) def onMapCanvasColorChanged(self, color): self.sigMapCanvasColorChanged.emit(color) self.refreshCrosshairPreview() def mapCanvasColor(self): return self.btnMapCanvasColor.color() def refreshCrosshairPreview(self, *args): style = self.crosshairStyle() self.mapCanvasItem.setCrosshairStyle(style) #self.crossHairReferenceLayer.setCrosshairStyle(style) #self.crossHairCanvas.refreshAllLayers() self.sigCrosshairStyleChanged.emit(style) def setCrosshairStyle(self, style): assert isinstance(style, CrosshairStyle) self.btnCrosshairColor.setColor(style.mColor) self.spinBoxCrosshairAlpha.setValue(style.mColor.alpha()) self.spinBoxCrosshairThickness.setValue(style.mThickness) self.spinBoxCrosshairSize.setValue(int(style.mSize*100)) self.spinBoxCrosshairGap.setValue(int(style.mGap*100)) self.spinBoxDotSize.setValue(style.mDotSize) self.cbCrosshairShowDot.setChecked(style.mShowDot) self.cbShowPixelBoundaries.setChecked(style.mShowPixelBorder) self.cbShowDistanceMarker.setChecked(style.mShowDistanceMarker) def crosshairStyle(self): style = CrosshairStyle() c = self.btnCrosshairColor.color() c.setAlpha(self.spinBoxCrosshairAlpha.value()) style.setColor(c) style.setThickness(self.spinBoxCrosshairThickness.value()) style.setSize(self.spinBoxCrosshairSize.value()) style.setGap(self.spinBoxCrosshairGap.value()) style.setDotSize(self.spinBoxDotSize.value()) style.setShowDot(self.cbCrosshairShowDot.isChecked()) style.setShowPixelBorder(self.cbShowPixelBoundaries.isChecked()) style.setShowDistanceMarker(self.cbShowDistanceMarker.isChecked()) return style class CrosshairDialog(QgsDialog): @staticmethod def getCrosshairStyle(*args, **kwds): """ Opens a CrosshairDialog. :param args: :param kwds: :return: specified CrosshairStyle if accepted, else None """ d = CrosshairDialog(*args, **kwds) d.exec_() if d.result() == QDialog.Accepted: return d.crosshairStyle() else: return None def __init__(self, parent=None, crosshairStyle=None, mapCanvas=None, title='Specify Crosshair'): super(CrosshairDialog, self).__init__(parent=parent , \ buttons=QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.w = CrosshairWidget(parent=self) self.setWindowTitle(title) self.btOk = QPushButton('Ok') self.btCancel = QPushButton('Cance') buttonBar = QHBoxLayout() #buttonBar.addWidget(self.btCancel) #buttonBar.addWidget(self.btOk) l = self.layout() l.addWidget(self.w) l.addLayout(buttonBar) #self.setLayout(l) if isinstance(mapCanvas, QgsMapCanvas): self.setMapCanvas(mapCanvas) if isinstance(crosshairStyle, CrosshairStyle): self.setCrosshairStyle(crosshairStyle) s = "" def crosshairStyle(self): return self.w.crosshairStyle() def setCrosshairStyle(self, crosshairStyle): assert isinstance(crosshairStyle, CrosshairStyle) self.w.setCrosshairStyle(crosshairStyle) def setMapCanvas(self, mapCanvas): assert isinstance(mapCanvas, QgsMapCanvas) # copy layers canvas = self.w.mapCanvasItem.canvas lyrs = [] for lyr in mapCanvas.layers(): s = "" lyrs = mapCanvas.layers() canvas.setLayerSet([QgsMapCanvasLayer(l) for l in lyrs]) canvas.setDestinationCrs(mapCanvas.mapSettings().destinationCrs()) canvas.setExtent(mapCanvas.extent()) canvas.setCenter(mapCanvas.center()) canvas.setCanvasColor(mapCanvas.canvasColor()) canvas.refresh() canvas.updateMap() canvas.refreshAllLayers() if __name__ == '__main__': import site, sys #add site-packages to sys.path as done by enmapboxplugin.py from timeseriesviewer import sandbox qgsApp = sandbox.initQgisEnvironment() if False: c = QgsMapCanvas() c.setExtent(QgsRectangle(0,0,1,1)) i = CrosshairMapCanvasItem(c) i.setShow(True) s = CrosshairStyle() s.setShow(True) i.setCrosshairStyle(s) c.show() import example.Images lyr = QgsRasterLayer(example.Images.Img_2012_05_09_LE72270652012130EDC00_BOA) QgsMapLayerRegistry.instance().addMapLayer(lyr) refCanvas = QgsMapCanvas() refCanvas.setLayerSet([QgsMapCanvasLayer(lyr)]) refCanvas.setExtent(lyr.extent()) refCanvas.setDestinationCrs(lyr.crs()) refCanvas.show() style = CrosshairDialog.getCrosshairStyle(mapCanvas=refCanvas) qgsApp.exec_() qgsApp.exitQgis()