-
benjamin.jakimow@geo.hu-berlin.de authored
added # -*- coding: uft-8 -*- and # noinspection PyPep8Naming to each module added GPL License statement to each module added from __future__ import absolute_import to each module
benjamin.jakimow@geo.hu-berlin.de authoredadded # -*- coding: uft-8 -*- and # noinspection PyPep8Naming to each module added GPL License statement to each module added from __future__ import absolute_import to each module
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
crosshair.py 19.26 KiB
# -*- 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()